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 GenericAccessService and GenericAccessServicePrivate classes. 7 : */ 8 : 9 : #include <qtpokit/genericaccessservice.h> 10 : #include "genericaccessservice_p.h" 11 : 12 : #include <QtEndian> 13 : 14 : /*! 15 : * \class GenericAccessService 16 : * 17 : * The GenericAccessService class accesses the `Generic Access` service of Pokit devices. 18 : * 19 : * \cond internal 20 : * \pokitApi Pokit API 1.00 (and 0.02) claims support for the `Generic Access` (`0x1800`) service, 21 : * however the neither the Pokit Meter, nor the Pokit Pro eport any support for this service, but 22 : * both report support for an undocumented `Generic Attribute` (`0x1801`) service instead. 23 : * \endcond 24 : */ 25 : 26 : /// UUID of the "Generic Access" service. 27 : const QBluetoothUuid GenericAccessService:: 28 : serviceUuid(QBluetoothUuid::ServiceClassUuid::GenericAccess); 29 : 30 : /// \struct GenericAccessService::CharacteristicUuids 31 : /// \brief Characteristics available via the `Generic Access` service. 32 : 33 : /// UUID of the `Generic Access` service's `Device Name` characterstic. 34 : const QBluetoothUuid GenericAccessService::CharacteristicUuids:: 35 : deviceName(QBluetoothUuid::CharacteristicType::DeviceName); 36 : 37 : /// UUID of the `Generic Access` service's `Appearance` characterstic. 38 : const QBluetoothUuid GenericAccessService::CharacteristicUuids:: 39 : appearance(QBluetoothUuid::CharacteristicType::Appearance); 40 : 41 : /*! 42 : * Constructs a new Pokit service with \a parent. 43 : */ 44 180 : GenericAccessService::GenericAccessService(QLowEnergyController * const controller, QObject * parent) 45 180 : : AbstractPokitService(new GenericAccessServicePrivate(controller, this), parent) 46 : { 47 : 48 180 : } 49 : 50 : /*! 51 : * \cond internal 52 : * Constructs a new Pokit service with \a parent, and private implementation \a d. 53 : */ 54 0 : GenericAccessService::GenericAccessService( 55 0 : GenericAccessServicePrivate * const d, QObject * const parent) 56 0 : : AbstractPokitService(d, parent) 57 : { 58 : 59 0 : } 60 : /// \endcond 61 : 62 : /*! 63 : * Destroys this GenericAccessService object. 64 : */ 65 162 : GenericAccessService::~GenericAccessService() 66 : { 67 : 68 162 : } 69 : 70 18 : bool GenericAccessService::readCharacteristics() 71 : { 72 12 : const bool r1 = readDeviceNameCharacteristic(); 73 12 : const bool r2 = readAppearanceCharacteristic(); 74 18 : return (r1 && r2); 75 : } 76 : 77 : /*! 78 : * Read the `Generic Access` service's `Appearance` characteristic. 79 : * 80 : * Returns `true` if the read request is succesfully queued, `false` otherwise (ie if the 81 : * underlying controller it not yet connected to the Pokit device, or the device's services have 82 : * not yet been discovered). 83 : * 84 : * Emits appearanceRead() if/when the characteristic has been read successfully. 85 : */ 86 30 : bool GenericAccessService::readAppearanceCharacteristic() 87 : { 88 : Q_D(GenericAccessService); 89 36 : return d->readCharacteristic(CharacteristicUuids::appearance); 90 : } 91 : 92 : /*! 93 : * Read the `Generic Access` service's `Device Name` characteristic. 94 : * 95 : * Returns `true` if the read request is succesfully queued, `false` otherwise (ie if the 96 : * underlying controller it not yet connected to the Pokit device, or the device's services have 97 : * not yet been discovered). 98 : * 99 : * Emits deviceNameRead() if/when the characteristic has been read successfully. 100 : */ 101 30 : bool GenericAccessService::readDeviceNameCharacteristic() 102 : { 103 : Q_D(GenericAccessService); 104 36 : return d->readCharacteristic(CharacteristicUuids::deviceName); 105 : } 106 : 107 : /*! 108 : * Returns the most recent value of the `Generic Access` services's `Appearance` characteristic. 109 : * 110 : * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is 111 : * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), `0xFFFF` 112 : * is returned. 113 : * 114 : * \note Pokit's Bluetooth API suggests the device's `Appearance` will always be 0 aka "Unknown 115 : * Appearance", so this is probably not a very useful characteristic if you already know you are 116 : * dealing with a Pokit device. 117 : */ 118 18 : quint16 GenericAccessService::appearance() const 119 : { 120 : Q_D(const GenericAccessService); 121 : const QLowEnergyCharacteristic characteristic = 122 18 : d->getCharacteristic(CharacteristicUuids::appearance); 123 18 : return (characteristic.isValid()) ? GenericAccessServicePrivate::parseAppearance(characteristic.value()) 124 18 : : std::numeric_limits<quint16>::max(); 125 18 : } 126 : 127 : /*! 128 : * Returns the most recent value of the `Generic Access` services's `Device Name` characteristic. 129 : * 130 : * The returned value, if any, is from the underlying Bluetooth stack's cache. If no such value is 131 : * currently available (ie the serviceDetailsDiscovered signal has not been emitted yet), then a 132 : * null QString is returned. 133 : */ 134 18 : QString GenericAccessService::deviceName() const 135 : { 136 : Q_D(const GenericAccessService); 137 : const QLowEnergyCharacteristic characteristic = 138 18 : d->getCharacteristic(CharacteristicUuids::deviceName); 139 36 : return (characteristic.isValid()) ? QString::fromUtf8(characteristic.value()) : QString(); 140 18 : } 141 : 142 : /*! 143 : * Set's the Pokit device's name to \a name. 144 : * 145 : * Returns `true` if the write request was successfully queued, `false` otherwise. 146 : * 147 : * Emits deviceNameWritten() if/when the \a name has been set. 148 : */ 149 18 : bool GenericAccessService::setDeviceName(const QString &name) 150 : { 151 : Q_D(const GenericAccessService); 152 : const QLowEnergyCharacteristic characteristic = 153 18 : d->getCharacteristic(CharacteristicUuids::deviceName); 154 18 : if (!characteristic.isValid()) { 155 : return false; 156 : } 157 : 158 : const QByteArray value = name.toUtf8(); 159 0 : if (value.length() > 11) { 160 0 : qCWarning(d->lc).noquote() << tr("Device name \"%1\" is too long (%2 > 11 bytes): 0x%3") 161 0 : .arg(name).arg(value.length()).arg(QLatin1String(value.toHex())); 162 0 : return false; 163 : } 164 : 165 0 : d->service->writeCharacteristic(characteristic, value); 166 0 : return (d->service->error() != QLowEnergyService::ServiceError::CharacteristicWriteError); 167 18 : } 168 : 169 : 170 : /*! 171 : * \fn GenericAccessService::appearanceRead 172 : * 173 : * This signal is emitted when the `Appearance` characteristic has been read successfully. 174 : * 175 : * \see readAppearanceCharacteristic 176 : * \see appearance 177 : */ 178 : 179 : /*! 180 : * \fn GenericAccessService::deviceNameRead 181 : * 182 : * This signal is emitted when the `Device Name` characteristic has been read successfully. 183 : * 184 : * \see readDeviceName 185 : */ 186 : 187 : /*! 188 : * \fn GenericAccessService::deviceNameWritten 189 : * 190 : * This signal is emitted when the `Device Name` characteristic has been written successfully. 191 : * 192 : * \see setDeviceName 193 : */ 194 : 195 : /*! 196 : * \cond internal 197 : * \class GenericAccessServicePrivate 198 : * 199 : * The GenericAccessServicePrivate class provides private implementation for GenericAccessService. 200 : */ 201 : 202 : /*! 203 : * \internal 204 : * Constructs a new GenericAccessServicePrivate object with public implementation \a q. 205 : */ 206 120 : GenericAccessServicePrivate::GenericAccessServicePrivate( 207 180 : QLowEnergyController * controller, GenericAccessService * const q) 208 180 : : AbstractPokitServicePrivate(GenericAccessService::serviceUuid, controller, q) 209 : { 210 : 211 120 : } 212 : 213 : /*! 214 : * Parses the `Appearance` \a value. Returns `0xFFFF` if not valid. 215 : */ 216 144 : quint16 GenericAccessServicePrivate::parseAppearance(const QByteArray &value) 217 : { 218 176 : if (!checkSize(QLatin1String("Appearance"), value, 2, 2)) { 219 : return std::numeric_limits<quint16>::max(); 220 : } 221 : const quint16 appearance = qFromLittleEndian<quint16>(value); 222 108 : qCDebug(lc).noquote() << tr("Appearance: %1.").arg(appearance); 223 18 : return appearance; 224 : } 225 : 226 : /*! 227 : * Implements AbstractPokitServicePrivate::characteristicRead to parse \a value, then emit a 228 : * specialised signal, for each supported \a characteristic. 229 : */ 230 18 : void GenericAccessServicePrivate::characteristicRead(const QLowEnergyCharacteristic &characteristic, 231 : const QByteArray &value) 232 : { 233 18 : AbstractPokitServicePrivate::characteristicRead(characteristic, value); 234 : 235 : Q_Q(GenericAccessService); 236 18 : if (characteristic.uuid() == GenericAccessService::CharacteristicUuids::appearance) { 237 0 : emit q->appearanceRead(parseAppearance(value)); 238 0 : return; 239 : } 240 : 241 18 : if (characteristic.uuid() == GenericAccessService::CharacteristicUuids::deviceName) { 242 0 : const QString deviceName = QString::fromUtf8(value); 243 0 : qCDebug(lc).noquote() << tr("Device name: \"%1\"").arg(deviceName); 244 0 : emit q->deviceNameRead(deviceName); 245 : return; 246 0 : } 247 : 248 54 : qCWarning(lc).noquote() << tr("Unknown characteristic read for Generic Access service") 249 24 : << serviceUuid << characteristic.name() << characteristic.uuid(); 250 : } 251 : 252 : /*! 253 : * Implements AbstractPokitServicePrivate::characteristicWritten to parse \a newValue, then emit a 254 : * specialised signal, for each supported \a characteristic. 255 : */ 256 18 : void GenericAccessServicePrivate::characteristicWritten(const QLowEnergyCharacteristic &characteristic, 257 : const QByteArray &newValue) 258 : { 259 18 : AbstractPokitServicePrivate::characteristicWritten(characteristic, newValue); 260 : 261 : Q_Q(GenericAccessService); 262 18 : if (characteristic.uuid() == GenericAccessService::CharacteristicUuids::appearance) { 263 0 : qCWarning(lc).noquote() << tr("Appearance haracteristic is read-only, but somehow written") 264 0 : << serviceUuid << characteristic.name() << characteristic.uuid(); 265 0 : return; 266 : } 267 : 268 18 : if (characteristic.uuid() == GenericAccessService::CharacteristicUuids::deviceName) { 269 0 : emit q->deviceNameWritten(); 270 0 : return; 271 : } 272 : 273 54 : qCWarning(lc).noquote() << tr("Unknown characteristic written for Generic Access service") 274 24 : << serviceUuid << characteristic.name() << characteristic.uuid(); 275 : } 276 : 277 : /// \endcond