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