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