Line data Source code
1 : // SPDX-FileCopyrightText: 2022-2025 Paul Colby <git@colby.id.au>
2 : // SPDX-License-Identifier: LGPL-3.0-or-later
3 :
4 : /*!
5 : * \file
6 : * Defines the PokitDevice and PokitDevicePrivate classes.
7 : */
8 :
9 : #include <qtpokit/pokitdevice.h>
10 :
11 : #include <qtpokit/calibrationservice.h>
12 : #include <qtpokit/dataloggerservice.h>
13 : #include <qtpokit/deviceinfoservice.h>
14 : #include <qtpokit/dsoservice.h>
15 : #include <qtpokit/multimeterservice.h>
16 : #include <qtpokit/statusservice.h>
17 :
18 : #include "pokitdevice_p.h"
19 :
20 : #include <QMutexLocker>
21 :
22 : /*!
23 : * \class PokitDevice
24 : *
25 : * The PokitDevice class simplifies Pokit device access.
26 : *
27 : * It does this by wrapping QLowEnergyController to provide:
28 : * * convenient Pokit service factory methods (dataLogger(), deviceInformation(), dso(),
29 : multimeter() and status()); and
30 : * * consistent debug logging of QLowEnergyController events.
31 : *
32 : * But this class is entirely optional, in that all features of all other QtPokit classes can be
33 : * used wihtout this class. It's just a (meaningful) convenience.
34 : */
35 :
36 : /*!
37 : * Constructs a new Pokit device controller wrapper for \a deviceInfo, with \a parent.
38 : *
39 : * Though not strictly necessary, \a deviceInfo should normally come from a
40 : * PokitDiscoveryAgent instance (or a QBluetoothDeviceDiscoveryAgent), otherwise connection
41 : * is likely to fail with QLowEnergyController::UnknownRemoteDeviceError.
42 : */
43 1530 : PokitDevice::PokitDevice(const QBluetoothDeviceInfo &deviceInfo, QObject *parent)
44 2278 : : QObject(parent), d_ptr(new PokitDevicePrivate(this))
45 748 : {
46 748 : Q_D(PokitDevice);
47 2278 : d->setController(QLowEnergyController::createCentral(deviceInfo, this));
48 2278 : }
49 :
50 : /*!
51 : * Constructs a new Pokit device controller wrapper for \a controller, with \a parent.
52 : */
53 765 : PokitDevice::PokitDevice(QLowEnergyController *controller, QObject *parent)
54 1139 : : QObject(parent), d_ptr(new PokitDevicePrivate(this))
55 824 : {
56 824 : Q_D(PokitDevice);
57 1589 : d->setController(controller);
58 1589 : }
59 :
60 : /*!
61 : * \cond internal
62 : * Constructs a new Pokit device controller wrapper with \a parent, and private implementation \a d.
63 : *
64 : * Derived classes using this constructor should use PokitDevicePrivate::setController to assign
65 : * the BLE controller as some point.
66 : */
67 0 : PokitDevice::PokitDevice(PokitDevicePrivate * const d, QObject * const parent)
68 0 : : QObject(parent), d_ptr(d)
69 0 : {
70 :
71 0 : }
72 : /// \endcond
73 :
74 : /*!
75 : * Destroys this PokitDevice object.
76 : */
77 3825 : PokitDevice::~PokitDevice()
78 1572 : {
79 3867 : delete d_ptr;
80 5397 : }
81 :
82 : /*!
83 : * Returns a non-const pointer to the controller used to access the Pokit device.
84 : */
85 2880 : QLowEnergyController * PokitDevice::controller()
86 1438 : {
87 1438 : Q_D(PokitDevice);
88 4318 : return d->controller;
89 1438 : }
90 :
91 : /*!
92 : * Returns a const pointer to the controller used to access the Pokit device.
93 : */
94 45 : const QLowEnergyController * PokitDevice::controller() const
95 52 : {
96 52 : Q_D(const PokitDevice);
97 97 : return d->controller;
98 52 : }
99 :
100 : /// \cond
101 : #define QTPOKIT_INTERNAL_GET_SERVICE(typeName, varName) \
102 1996 : Q_D(PokitDevice); \
103 1996 : const QMutexLocker scopedLock(&d->varName##Mutex);\
104 1996 : if (d->varName == nullptr) { \
105 1060 : d->varName = new typeName(d->controller); \
106 1060 : } \
107 1996 : return d->varName \
108 : /// \endcond
109 :
110 : /*!
111 : * Returns a pointer to a CalibrationService instance that uses this device's controller for access.
112 : *
113 : * This is a convenience function, that always returns the same pointer (for this PokitDevice
114 : * instance), but the service itself is lazily created (in a threadsafe manner) on the first
115 : * invocation of this function.
116 : */
117 180 : CalibrationService * PokitDevice::calibration()
118 208 : {
119 644 : QTPOKIT_INTERNAL_GET_SERVICE(CalibrationService, calibration);
120 208 : }
121 :
122 : /*!
123 : * Returns a pointer to a DataLoggerService instance that uses this device's controller for access.
124 : *
125 : * This is a convenience function, that always returns the same pointer (for this PokitDevice
126 : * instance), but the service itself is lazily created (in a threadsafe manner) on the first
127 : * invocation of this function.
128 : */
129 180 : DataLoggerService * PokitDevice::dataLogger()
130 208 : {
131 644 : QTPOKIT_INTERNAL_GET_SERVICE(DataLoggerService, dataLogger);
132 208 : }
133 :
134 : /*!
135 : * Returns a pointer to DeviceInformationService instance that uses this device's controller for
136 : * access.
137 : *
138 : * This is a convenience function, that always returns the same pointer (for this PokitDevice
139 : * instance), but the service itself is lazily created (in a threadsafe manner) on the first
140 : * invocation of this function.
141 : */
142 1260 : DeviceInfoService * PokitDevice::deviceInformation()
143 736 : {
144 3788 : QTPOKIT_INTERNAL_GET_SERVICE(DeviceInfoService, deviceInfo);
145 736 : }
146 :
147 : /*!
148 : * Returns a pointer to DsoService instance that uses this device's controller for access.
149 : *
150 : * This is a convenience function, that always returns the same pointer (for this PokitDevice
151 : * instance), but the service itself is lazily created (in a threadsafe manner) on the first
152 : * invocation of this function.
153 : */
154 180 : DsoService * PokitDevice::dso()
155 208 : {
156 644 : QTPOKIT_INTERNAL_GET_SERVICE(DsoService, dso);
157 208 : }
158 :
159 : /*!
160 : * Returns a pointer to MultimeterService instance that uses this device's controller for access.
161 : *
162 : * This is a convenience function, that always returns the same pointer (for this PokitDevice
163 : * instance), but the service itself is lazily created (in a threadsafe manner) on the first
164 : * invocation of this function.
165 : */
166 180 : MultimeterService * PokitDevice::multimeter()
167 208 : {
168 644 : QTPOKIT_INTERNAL_GET_SERVICE(MultimeterService, multimeter);
169 208 : }
170 :
171 : /*!
172 : * Returns a pointer to StatusService instance that uses this device's controller for access.
173 : *
174 : * This is a convenience function, that always returns the same pointer (for this PokitDevice
175 : * instance), but the service itself is lazily created (in a threadsafe manner) on the first
176 : * invocation of this function.
177 : */
178 630 : StatusService * PokitDevice::status()
179 428 : {
180 1954 : QTPOKIT_INTERNAL_GET_SERVICE(StatusService, status);
181 428 : }
182 : #undef QTPOKIT_INTERNAL_GET_SERVICE
183 :
184 : /*!
185 : * Returns a human-readable name for the \a uuid service, or a null QString if unknonw.
186 : *
187 : * This is equivalent to QBluetoothUuid::serviceClassToString() but for services provided by Pokit
188 : * devices.
189 : */
190 450 : QString PokitDevice::serviceToString(const QBluetoothUuid &uuid)
191 520 : {
192 520 : static const QHash<QBluetoothUuid, QString> hash{
193 589 : { CalibrationService::serviceUuid, tr("Calibration") },
194 565 : { DataLoggerService::serviceUuid, tr("Data Logger") },
195 565 : { DsoService::serviceUuid, tr("DSO") },
196 565 : { MultimeterService::serviceUuid, tr("Multimeter") },
197 565 : { StatusService::ServiceUuids::pokitMeter, tr("Status (Pokit Meter)") },
198 565 : { StatusService::ServiceUuids::pokitPro, tr("Status (Pokit Pro)") },
199 520 : { DeviceInfoService::serviceUuid,
200 610 : QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::DeviceInformation) },
201 :
202 : // The following are not specifically supported by this library, but strings provided for nicer debug output.
203 520 : { QBluetoothUuid::ServiceClassUuid::GenericAccess,
204 610 : QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::GenericAccess) },
205 520 : { QBluetoothUuid::ServiceClassUuid::GenericAttribute,
206 609 : QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid::GenericAttribute) },
207 599 : { QBluetoothUuid(QStringLiteral("1d14d6ee-fd63-4fa1-bfa4-8f47b42119f0")), tr("OTA Firmware Update") },
208 1514 : };
209 990 : return hash.value(uuid);
210 520 : }
211 :
212 : /*!
213 : * Returns a human-readable name for the \a uuid characteristic, or a null QString if unknown.
214 : *
215 : * This is equivalent to QBluetoothUuid::characteristicToString() but for characteristics provided
216 : * by Pokit devices.
217 : */
218 990 : QString PokitDevice::charcteristicToString(const QBluetoothUuid &uuid)
219 1144 : {
220 1144 : static const QHash<QBluetoothUuid, QString> hash{
221 1213 : { CalibrationService::CharacteristicUuids::temperature, tr("Temperature") },
222 1189 : { CalibrationService::CharacteristicUuids::getParam, tr("Get Param") },
223 1189 : { CalibrationService::CharacteristicUuids::setParam, tr("Set Param") },
224 :
225 1189 : { DataLoggerService::CharacteristicUuids::metadata, tr("Metadata") },
226 1189 : { DataLoggerService::CharacteristicUuids::reading, tr("Reading") },
227 1189 : { DataLoggerService::CharacteristicUuids::settings, tr("Settings") },
228 :
229 1189 : { DsoService::CharacteristicUuids::metadata, tr("Metadata") },
230 1189 : { DsoService::CharacteristicUuids::reading, tr("Reading") },
231 1189 : { DsoService::CharacteristicUuids::settings, tr("Settings") },
232 :
233 1189 : { MultimeterService::CharacteristicUuids::reading, tr("Reading") },
234 1189 : { MultimeterService::CharacteristicUuids::settings, tr("Settings") },
235 :
236 1189 : { StatusService::CharacteristicUuids::deviceCharacteristics, tr("Device Characteristics") },
237 1189 : { StatusService::CharacteristicUuids::flashLed, tr("Flash LED") },
238 1189 : { StatusService::CharacteristicUuids::name, tr("Name") },
239 1189 : { StatusService::CharacteristicUuids::status, tr("Status") },
240 1189 : { StatusService::CharacteristicUuids::torch, tr("Torch") },
241 1189 : { StatusService::CharacteristicUuids::buttonPress, tr("Button Press") },
242 :
243 1144 : { DeviceInfoService::CharacteristicUuids::firmwareRevision,
244 1234 : QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::FirmwareRevisionString) },
245 1144 : { DeviceInfoService::CharacteristicUuids::hardwareRevision,
246 1234 : QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::HardwareRevisionString) },
247 1144 : { DeviceInfoService::CharacteristicUuids::manufacturerName,
248 1234 : QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ManufacturerNameString) },
249 1144 : { DeviceInfoService::CharacteristicUuids::modelNumber,
250 1234 : QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::ModelNumberString) },
251 1144 : { DeviceInfoService::CharacteristicUuids::softwareRevision,
252 1234 : QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::SoftwareRevisionString) },
253 1144 : { DeviceInfoService::CharacteristicUuids::serialNumber,
254 1234 : QBluetoothUuid::characteristicToString(QBluetoothUuid::CharacteristicType::SerialNumberString) },
255 :
256 : // The next two are not specifically supported by this library, but strings provided for nicer debug output.
257 1223 : { QBluetoothUuid(QStringLiteral("f7bf3564-fb6d-4e53-88a4-5e37e0326063")), tr("OTA Control") },
258 1223 : { QBluetoothUuid(QStringLiteral("984227f3-34fc-4045-a5d0-2c581f81a153")), tr("OTA Data Transfer") },
259 3353 : };
260 2178 : return hash.value(uuid);
261 1144 : }
262 :
263 : /*!
264 : * \cond internal
265 : * \class PokitDevicePrivate
266 : *
267 : * The PokitDevicePrivate class provides private implementation for PokitDevice.
268 : */
269 :
270 : /*!
271 : * Constructs a new PokitDevicePrivate object with public implementation \a q.
272 : */
273 3417 : PokitDevicePrivate::PokitDevicePrivate(PokitDevice * const q) : q_ptr(q)
274 1572 : {
275 :
276 3867 : }
277 :
278 : /*!
279 : * Sets \a newController to be used for accessing Pokit devices.
280 : *
281 : * If a controller has already been set (and is not the same pointer), then the previous controller
282 : * will be disconnected, and replaced with \a newController.
283 : *
284 : * This function will not take ownership of the new controller. The caller is responsible for
285 : * ensuring that \a newContorller remains valid for the lifetime of this instance, or until this
286 : * function is used again to replace \a newController with another one (which may be a nullptr).
287 : *
288 : * \see controller
289 : * \see PokitDevice::controller()
290 : */
291 2610 : void PokitDevicePrivate::setController(QLowEnergyController * newController)
292 1726 : {
293 4336 : if (newController == this->controller) {
294 1970 : qCDebug(lc).noquote() << tr("Controller already set to:") << newController;
295 1476 : return;
296 868 : }
297 :
298 2613 : if (this->controller) {
299 240 : qCDebug(lc).noquote() << tr("Disconnecting signals from previous controller:")
300 0 : << controller;
301 201 : disconnect(this->controller, nullptr, this, nullptr);
302 66 : }
303 :
304 3120 : qCDebug(lc).noquote() << tr("Setting new controller:") << newController;
305 2613 : this->controller = newController;
306 2613 : if (!newController) {
307 44 : return; // Don't bother continuing to connect if new controller is null.
308 44 : }
309 :
310 2960 : qCDebug(lc).noquote() << tr(R"(Set new controller "%1" (%2) at (%3).)").arg(
311 0 : controller->remoteName(), controller->remoteDeviceUuid().toString(),
312 0 : controller->remoteAddress().toString());
313 :
314 2479 : connect(controller, &QLowEnergyController::connected,
315 1221 : this, &PokitDevicePrivate::connected);
316 :
317 2479 : connect(controller, &QLowEnergyController::connectionUpdated,
318 1221 : this, &PokitDevicePrivate::connectionUpdated);
319 :
320 2479 : connect(controller, &QLowEnergyController::disconnected,
321 1221 : this, &PokitDevicePrivate::disconnected);
322 :
323 2479 : connect(controller, &QLowEnergyController::discoveryFinished,
324 1221 : this, &PokitDevicePrivate::discoveryFinished);
325 :
326 :
327 2479 : connect(controller,
328 333 : #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
329 333 : QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error),
330 : #else
331 481 : &QLowEnergyController::errorOccurred,
332 481 : #endif
333 1221 : this, &PokitDevicePrivate::errorOccurred);
334 :
335 :
336 2479 : connect(controller, &QLowEnergyController::serviceDiscovered,
337 1221 : this, &PokitDevicePrivate::serviceDiscovered);
338 :
339 2479 : connect(controller, &QLowEnergyController::stateChanged,
340 1628 : this, &PokitDevicePrivate::stateChanged);
341 814 : }
342 :
343 : /*!
344 : * Handle connected signals.
345 : */
346 135 : void PokitDevicePrivate::connected() const
347 66 : {
348 201 : if (controller == nullptr) {
349 292 : qCCritical(lc).noquote() << tr("PokitDevicePrivate::connected slot invoked without a controller.");
350 108 : return; // Just to avoid the nullptr dereference below.
351 44 : }
352 80 : qCDebug(lc).noquote() << tr(R"(Connected to "%1" (%2) at (%3).)").arg(
353 0 : controller->remoteName(), controller->remoteDeviceUuid().toString(),
354 0 : controller->remoteAddress().toString());
355 22 : }
356 :
357 : /*!
358 : * Handle connectionUpdated signals.
359 : */
360 45 : void PokitDevicePrivate::connectionUpdated(const QLowEnergyConnectionParameters &newParameters) const
361 52 : {
362 110 : qCDebug(lc).noquote() << tr("Connection updated:") << newParameters.latency()
363 0 : << newParameters.minimumInterval() << newParameters.maximumInterval()
364 0 : << newParameters.supervisionTimeout();
365 97 : }
366 :
367 : /*!
368 : * Handle disconnected signals.
369 : */
370 45 : void PokitDevicePrivate::disconnected() const
371 52 : {
372 110 : qCDebug(lc).noquote() << tr("Device disconnected.");
373 97 : }
374 :
375 : /*!
376 : * Handle discoveryFinished signals.
377 : */
378 45 : void PokitDevicePrivate::discoveryFinished() const
379 52 : {
380 110 : qCDebug(lc).noquote() << tr("Service discovery finished.");
381 97 : }
382 :
383 : /*!
384 : * Handle error signals.
385 : */
386 45 : void PokitDevicePrivate::errorOccurred(QLowEnergyController::Error newError) const
387 52 : {
388 110 : qCDebug(lc).noquote() << tr("Controller error:") << newError;
389 97 : }
390 :
391 : /*!
392 : * Handle serviceDiscovered signals.
393 : */
394 45 : void PokitDevicePrivate::serviceDiscovered(const QBluetoothUuid &newService) const
395 52 : {
396 110 : qCDebug(lc).noquote() << tr(R"(Service discovered: %1 "%2")")
397 0 : .arg(newService.toString(), PokitDevice::serviceToString(newService));
398 97 : }
399 :
400 : /*!
401 : * Handle stateChanged signals.
402 : */
403 45 : void PokitDevicePrivate::stateChanged(QLowEnergyController::ControllerState state) const
404 52 : {
405 110 : qCDebug(lc).noquote() << tr("State changed to:") << state;
406 97 : }
407 :
408 : /// \endcond
|