Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2023 Paul Colby <git@colby.id.au>
2 : // SPDX-License-Identifier: LGPL-3.0-or-later
3 :
4 : #include "devicecommand.h"
5 :
6 : #include <qtpokit/abstractpokitservice.h>
7 : #include <qtpokit/pokitdevice.h>
8 : #include <qtpokit/pokitdiscoveryagent.h>
9 :
10 : /*!
11 : * \class DeviceCommand
12 : *
13 : * The AbstractCommand class extends AbstractCommand to add a PokitDevice instance.
14 : */
15 :
16 : /*!
17 : * Construct a new DeviceCommand object with \a parent.
18 : */
19 6576 : DeviceCommand::DeviceCommand(QObject * const parent) : AbstractCommand(parent)
20 : {
21 :
22 6576 : }
23 :
24 : /*!
25 : * Begins scanning for the Pokit device.
26 : */
27 36 : bool DeviceCommand::start()
28 : {
29 72 : qCInfo(lc).noquote() << ((deviceToScanFor.isNull())
30 64 : ? tr("Looking for first available Pokit device...")
31 76 : : tr(R"(Looking for device "%1"...)").arg(deviceToScanFor));
32 36 : discoveryAgent->start();
33 36 : return true;
34 : }
35 :
36 : /*!
37 : * Disconnects the underlying Pokit device, and sets \a exitCode to be return to the OS once the
38 : * disconnection has taken place.
39 : */
40 474 : void DeviceCommand::disconnect(int exitCode)
41 : {
42 474 : qCDebug(lc).noquote() << tr("Disconnecting Pokit device...");
43 : Q_ASSERT(device);
44 : Q_ASSERT(device->controller());
45 474 : exitCodeOnDisconnect = exitCode;
46 474 : device->controller()->disconnectFromDevice();
47 474 : }
48 :
49 : /*!
50 : * \fn virtual AbstractPokitService * DeviceCommand::getService() = 0
51 : *
52 : * Returns a Pokit service object for the derived command class. This will be called by
53 : * deviceDiscovered() when the requested Pokit device has been found, after which
54 : * deviceDiscovered() will connect the returned service's common signals, and kick off the
55 : * device's connection process.
56 : */
57 :
58 : #define DOKIT_CLI_IF_LESS_THAN_RETURN(value, ns, label) \
59 : if (value <= ns::maxValue(label).toUInt()) { \
60 : return label; \
61 : }
62 :
63 : /**
64 : * \fn template<typename T> static T DeviceCommand::minRange(const quint32 maxValue)
65 : *
66 : * Returns the lowest \a T range that can measure at least up to \a maxValue, or AutoRange if no such range is
67 : * available.
68 : *
69 : * \tparam T Range enumerator to evaluate ranges for. Must be one of:
70 : * * PokitMeter::CurrentRange
71 : * * PokitMeter::ResistanceRange
72 : * * PokitMeter::VoltageRange
73 : * * PokitPro::CapacitanceRange
74 : * * PokitPro::CurrentRange
75 : * * PokitPro::ResistanceRange
76 : * * PokitPro::VoltageRange
77 : *
78 : * \cond Doxygen has "only very limited support for member specialization at the moment", so hide these from Doxygen.
79 : * Specifically, if we don't hide them, then Doxygen (at least the current version: v1.9.6) sees the following
80 : * specialisations as new, public, non-static members.
81 : */
82 :
83 : /*!
84 : * Returns the lowest PokitMeter::CurrentRange value that can measure at least up to \a maxValue, or AutoRange if
85 : * the Pokit Meter cannot measure as high as \a maxValue.
86 : */
87 0 : template<> PokitMeter::CurrentRange DeviceCommand::minRange(const quint32 maxValue)
88 : {
89 0 : if (maxValue == 0) return PokitMeter::CurrentRange::AutoRange;
90 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_10mA)
91 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_30mA)
92 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_150mA)
93 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_300mA)
94 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::CurrentRange::_2A)
95 : return PokitMeter::CurrentRange::AutoRange;
96 : }
97 :
98 : /*!
99 : * Returns the lowest PokitMeter::ResistanceRange value that can measure at least up to \a maxValue, or AutoRange if
100 : * the Pokit Meter cannot measure as high as \a maxValue.
101 : */
102 0 : template<> PokitMeter::ResistanceRange DeviceCommand::minRange(const quint32 maxValue)
103 : {
104 0 : if (maxValue == 0) return PokitMeter::ResistanceRange::AutoRange;
105 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_160)
106 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_330)
107 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_890)
108 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_1K5)
109 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_10K)
110 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_100K)
111 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_470K)
112 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::ResistanceRange::_1M)
113 : return PokitMeter::ResistanceRange::AutoRange;
114 : }
115 :
116 : /*!
117 : * Returns the lowest PokitMeter::VoltageRange value that can measure at least up to \a maxValue, or AutoRange if
118 : * the Pokit Meter cannot measure as high as \a maxValue.
119 : */
120 0 : template<> PokitMeter::VoltageRange DeviceCommand::minRange(const quint32 maxValue)
121 : {
122 0 : if (maxValue == 0) return PokitMeter::VoltageRange::AutoRange;
123 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_300mV)
124 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_2V)
125 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_6V)
126 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_12V)
127 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_30V)
128 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitMeter, PokitMeter::VoltageRange::_60V)
129 : return PokitMeter::VoltageRange::AutoRange;
130 : }
131 :
132 : /*!
133 : * Returns the lowest PokitPro::CapacitanceRange value that can measure at least up to \a maxValue, or AutoRange if
134 : * the Pokit Pro cannot measure as high as \a maxValue.
135 : */
136 0 : template<> PokitPro::CapacitanceRange DeviceCommand::minRange(const quint32 maxValue)
137 : {
138 0 : if (maxValue == 0) return PokitPro::CapacitanceRange::AutoRange;
139 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CapacitanceRange::_100nF)
140 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CapacitanceRange::_10uF)
141 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CapacitanceRange::_1mF)
142 : return PokitPro::CapacitanceRange::AutoRange;
143 : }
144 :
145 : /*!
146 : * Returns the lowest PokitPro::CurrentRange value that can measure at least up to \a maxValue, or AutoRange if
147 : * the Pokit Pro cannot measure as high as \a maxValue.
148 : */
149 0 : template<> PokitPro::CurrentRange DeviceCommand::minRange(const quint32 maxValue)
150 : {
151 0 : if (maxValue == 0) return PokitPro::CurrentRange::AutoRange;
152 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_500uA)
153 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_2mA)
154 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_10mA)
155 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_125mA)
156 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_300mA)
157 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_3A)
158 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::CurrentRange::_10A)
159 : return PokitPro::CurrentRange::AutoRange;
160 : }
161 :
162 : /*!
163 : * Returns the lowest PokitPro::ResistanceRange value that can measure at least up to \a maxValue, or AutoRange if
164 : * the Pokit Pro cannot measure as high as \a maxValue.
165 : */
166 0 : template<> PokitPro::ResistanceRange DeviceCommand::minRange(const quint32 maxValue)
167 : {
168 0 : if (maxValue == 0) return PokitPro::ResistanceRange::AutoRange;
169 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_30)
170 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_75)
171 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_400)
172 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_5K)
173 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_10K)
174 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_15K)
175 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_40K)
176 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_500K)
177 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_700K)
178 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_1M)
179 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::ResistanceRange::_3M)
180 : return PokitPro::ResistanceRange::AutoRange;
181 : }
182 :
183 : /*!
184 : * Returns the lowest PokitPro::VoltageRange value that can measure at least up to \a maxValue, or AutoRange if
185 : * the Pokit Pro cannot measure as high as \a maxValue.
186 : */
187 0 : template<> PokitPro::VoltageRange DeviceCommand::minRange(const quint32 maxValue)
188 : {
189 0 : if (maxValue == 0) return PokitPro::VoltageRange::AutoRange;
190 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_250mV)
191 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_2V)
192 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_10V)
193 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_30V)
194 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_60V)
195 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_125V)
196 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_400V)
197 0 : DOKIT_CLI_IF_LESS_THAN_RETURN(maxValue, PokitPro, PokitPro::VoltageRange::_600V)
198 : return PokitPro::VoltageRange::AutoRange;
199 : }
200 :
201 : /// \endcond
202 :
203 : /*!
204 : * Returns the \a product's lowest capacitance range that can measure at least up to \a maxValue, or AutoRange if no
205 : * such range is available.
206 : *
207 : * \note Since Pokit Meters do not support capacitance measurement, \a product should not be PokitProduct::PokitMeter.
208 : *
209 : * \see minRange<PokitPro::CapacitanceRange>
210 : */
211 0 : quint8 DeviceCommand::minCapacitanceRange(const PokitProduct product, const quint32 maxValue)
212 : {
213 0 : switch (product) {
214 : case PokitProduct::PokitMeter:
215 : Q_ASSERT_X(false, "DeviceCommand::minCapacitanceRange", "Pokit Meter has no capacitance support.");
216 : return 255;
217 0 : case PokitProduct::PokitPro:
218 0 : return +minRange<PokitPro::CapacitanceRange>(maxValue);
219 : }
220 : Q_ASSERT_X(false, "DeviceCommand::minCapacitanceRange", "Unknown PokitProduct enum value");
221 : return 255;
222 : }
223 :
224 : /*!
225 : * Returns the \a product's lowest current range that can measure at least up to \a maxValue, or AutoRange if no
226 : * such range is available.
227 : *
228 : * \see DeviceCommand::minRange<PokitMeter::CurrentRange>(const quint32 maxValue)
229 : * \see minRange<PokitPro::CurrentRange>(const quint32 maxValue)
230 : */
231 0 : quint8 DeviceCommand::minCurrentRange(const PokitProduct product, const quint32 maxValue)
232 : {
233 0 : switch (product) {
234 0 : case PokitProduct::PokitMeter:
235 0 : return +minRange<PokitMeter::CurrentRange>(maxValue);
236 0 : case PokitProduct::PokitPro:
237 0 : return +minRange<PokitPro::CurrentRange>(maxValue);
238 : }
239 : Q_ASSERT_X(false, "DeviceCommand::minCurrentRange", "Unknown PokitProduct enum value");
240 : return 255;
241 : }
242 :
243 : /*!
244 : * Returns the \a product's lowest resistance range that can measure at least up to \a maxValue, or AutoRange if no
245 : * such range is available.
246 : *
247 : * \see DeviceCommand::minRange<PokitMeter::ResistanceRange>(const quint32 maxValue)
248 : * \see minRange<PokitPro::ResistanceRange>(const quint32 maxValue)
249 : */
250 0 : quint8 DeviceCommand::minResistanceRange(const PokitProduct product, const quint32 maxValue)
251 : {
252 0 : switch (product) {
253 0 : case PokitProduct::PokitMeter:
254 0 : return +minRange<PokitMeter::ResistanceRange>(maxValue);
255 0 : case PokitProduct::PokitPro:
256 0 : return +minRange<PokitPro::ResistanceRange>(maxValue);
257 : }
258 : Q_ASSERT_X(false, "DeviceCommand::minResistanceRange", "Unknown PokitProduct enum value");
259 : return 255;
260 : }
261 :
262 : /*!
263 : t
264 : * Returns the \a product's lowest voltage range that can measure at least up to \a maxValue, or AutoRange if no
265 : * such range is available.
266 : *
267 : * \see DeviceCommand::minRange<PokitMeter::VoltageRange>(const quint32 maxValue)
268 : * \see minRange<PokitPro::VoltageRange>(const quint32 maxValue)
269 : */
270 0 : quint8 DeviceCommand::minVoltageRange(const PokitProduct product, const quint32 maxValue)
271 : {
272 0 : switch (product) {
273 0 : case PokitProduct::PokitMeter:
274 0 : return +minRange<PokitMeter::VoltageRange>(maxValue);
275 0 : case PokitProduct::PokitPro:
276 0 : return +minRange<PokitPro::VoltageRange>(maxValue);
277 : }
278 : Q_ASSERT_X(false, "DeviceCommand::minVoltageRange", "Unknown PokitProduct enum value");
279 : return 255;
280 : }
281 :
282 : #undef DOKIT_CLI_IF_LESS_THAN_RETURN
283 :
284 :
285 : /*!
286 : * Handles controller error events. This base implementation simply logs \a error and then exits
287 : * with `EXIT_FAILURE`. Derived classes may override this slot to implement their own error
288 : * handing if desired.
289 : */
290 36 : void DeviceCommand::controllerError(QLowEnergyController::Error error)
291 : {
292 108 : qCWarning(lc).noquote() << tr("Bluetooth controller error:") << error;
293 36 : QCoreApplication::exit(EXIT_FAILURE);
294 36 : }
295 :
296 : /*!
297 : * Handles devics disconnection events. This base implementation simply logs and exits the
298 : * application (via QCoreApplication::exit) with the current exitCodeOnDisconnect value, which is
299 : * initialise to `EXIT_FAILURE` in the constructor, but should be set to `EXIT_SUCESS` if/when
300 : * the derived command class has completed its actions and requested the disconnection (as opposed
301 : * to a spontaneous disconnection on error).
302 : */
303 18 : void DeviceCommand::deviceDisconnected()
304 : {
305 18 : qCDebug(lc).noquote() << tr("Pokit device disconnected. Exiting with code %1.")
306 0 : .arg(exitCodeOnDisconnect);
307 18 : QCoreApplication::exit(exitCodeOnDisconnect);
308 18 : }
309 :
310 : /*!
311 : * Handles service error events. This base implementation simply logs \a error and then exits
312 : * with `EXIT_FAILURE`. Derived classes may override this slot to implement their own error
313 : * handing if desired.
314 : *
315 : * \note As this base class does not construct services (derived classed do), its up to the derived
316 : * classed to connect this slot to the relevant service's error signal if desired.
317 : */
318 36 : void DeviceCommand::serviceError(const QLowEnergyService::ServiceError error)
319 : {
320 108 : qCWarning(lc).noquote() << tr("Bluetooth service error:") << error;
321 36 : QCoreApplication::exit(EXIT_FAILURE);
322 36 : }
323 :
324 : /*!
325 : * Handles service detail discovery events. This base implementation simply logs the event, and
326 : * nothing more. Derived classes may (usually do) override this slot to provide their own processing
327 : * when a services details have been discovered.
328 : */
329 348 : void DeviceCommand::serviceDetailsDiscovered()
330 : {
331 348 : qCDebug(lc).noquote() << tr("Service details discovered.");
332 348 : }
333 :
334 : /*!
335 : * Checks if \a info is the device (if any) we're looking for, and if so, create a contoller and
336 : * service, and begins connecting to the device.
337 : */
338 18 : void DeviceCommand::deviceDiscovered(const QBluetoothDeviceInfo &info)
339 : {
340 : Q_ASSERT(isPokitProduct(info));
341 :
342 18 : if (device) {
343 0 : qCDebug(lc).noquote() << tr(R"(Ignoring additional Pokit device "%1" (%2) at (%3).)")
344 0 : .arg(info.name(), info.deviceUuid().toString(), info.address().toString());
345 0 : return;
346 : }
347 :
348 54 : if ((deviceToScanFor.isEmpty()) || (deviceToScanFor == info.name()) ||
349 72 : ((!info.address().isNull()) && (info.address() == QBluetoothAddress(deviceToScanFor))) ||
350 36 : ((!info.deviceUuid().isNull()) && (info.deviceUuid() == QBluetoothUuid(deviceToScanFor))))
351 : {
352 0 : qCDebug(lc).noquote() << tr(R"(Found Pokit device "%1" (%2) at (%3).)")
353 0 : .arg(info.name(), info.deviceUuid().toString(), info.address().toString());
354 0 : discoveryAgent->stop();
355 :
356 0 : device = new PokitDevice(info, this);
357 0 : connect(device->controller(), &QLowEnergyController::disconnected,
358 : this, &DeviceCommand::deviceDisconnected);
359 0 : connect(device->controller(),
360 : #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
361 : QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error),
362 : #else
363 : &QLowEnergyController::errorOccurred,
364 : #endif
365 : this, &DeviceCommand::controllerError, Qt::QueuedConnection);
366 :
367 0 : AbstractPokitService * const service = getService();
368 0 : service->setPokitProduct(pokitProduct(info));
369 :
370 : Q_ASSERT(service);
371 0 : connect(service, &AbstractPokitService::serviceDetailsDiscovered,
372 : this, &DeviceCommand::serviceDetailsDiscovered);
373 0 : connect(service, &AbstractPokitService::serviceErrorOccurred,
374 : this, &DeviceCommand::serviceError);
375 :
376 0 : qCDebug(lc).noquote() << tr(R"(Connecting to %1 device "%2" (%3) at (%4).)").arg(
377 0 : toString(service->pokitProduct()), info.name(), info.deviceUuid().toString(), info.address().toString());
378 0 : device->controller()->connectToDevice();
379 0 : return;
380 : }
381 :
382 18 : qCDebug(lc).noquote() << tr(R"(Ignoring non-matching Pokit device "%1" (%2) at (%3).)")
383 0 : .arg(info.name(), info.deviceUuid().toString(), info.address().toString());
384 3 : return;
385 : }
386 :
387 : /*!
388 : * Checks that the requested device was discovered, and if not, reports and error and exits.
389 : */
390 36 : void DeviceCommand::deviceDiscoveryFinished()
391 : {
392 36 : if (!device) {
393 72 : qCWarning(lc).noquote() << ((deviceToScanFor.isNull())
394 64 : ? tr("Failed to find any Pokit device.")
395 76 : : tr(R"(Failed to find device "%1".)").arg(deviceToScanFor));
396 36 : QCoreApplication::exit(EXIT_FAILURE);
397 : }
398 36 : }
|