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