LCOV - code coverage report
Current view: top level - src/lib - genericaccessservice.cpp (source / functions) Hit Total Coverage
Project: QtPokit Lines: 46 67 68.7 %
Version: Functions: 14 18 77.8 %

          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         170 : GenericAccessService::GenericAccessService(QLowEnergyController * const controller, QObject * parent)
      45         170 :     : AbstractPokitService(new GenericAccessServicePrivate(controller, this), parent)
      46             : {
      47             : 
      48         170 : }
      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         153 : GenericAccessService::~GenericAccessService()
      66             : {
      67             : 
      68         153 : }
      69             : 
      70          17 : bool GenericAccessService::readCharacteristics()
      71             : {
      72          11 :     const bool r1 = readDeviceNameCharacteristic();
      73          11 :     const bool r2 = readAppearanceCharacteristic();
      74          17 :     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          28 : bool GenericAccessService::readAppearanceCharacteristic()
      87             : {
      88             :     Q_D(GenericAccessService);
      89          34 :     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          28 : bool GenericAccessService::readDeviceNameCharacteristic()
     102             : {
     103             :     Q_D(GenericAccessService);
     104          34 :     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          17 : quint16 GenericAccessService::appearance() const
     119             : {
     120             :     Q_D(const GenericAccessService);
     121             :     const QLowEnergyCharacteristic characteristic =
     122          17 :         d->getCharacteristic(CharacteristicUuids::appearance);
     123          17 :     return (characteristic.isValid()) ? d->parseAppearance(characteristic.value())
     124          17 :         : std::numeric_limits<quint16>::max();
     125          17 : }
     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          17 : QString GenericAccessService::deviceName() const
     135             : {
     136             :     Q_D(const GenericAccessService);
     137             :     const QLowEnergyCharacteristic characteristic =
     138          17 :         d->getCharacteristic(CharacteristicUuids::deviceName);
     139          34 :     return (characteristic.isValid()) ? QString::fromUtf8(characteristic.value()) : QString();
     140          17 : }
     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          17 : bool GenericAccessService::setDeviceName(const QString &name)
     150             : {
     151             :     Q_D(const GenericAccessService);
     152             :     const QLowEnergyCharacteristic characteristic =
     153          17 :         d->getCharacteristic(CharacteristicUuids::deviceName);
     154          17 :     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          17 : }
     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         110 : GenericAccessServicePrivate::GenericAccessServicePrivate(
     207         170 :     QLowEnergyController * controller, GenericAccessService * const q)
     208         170 :     : AbstractPokitServicePrivate(GenericAccessService::serviceUuid, controller, q)
     209             : {
     210             : 
     211         110 : }
     212             : 
     213             : /*!
     214             :  * Parses the `Appearance` \a value. Returns `0xFFFF` if not valid.
     215             :  */
     216         136 : quint16 GenericAccessServicePrivate::parseAppearance(const QByteArray &value)
     217             : {
     218         160 :     if (!checkSize(QLatin1String("Appearance"), value, 2, 2)) {
     219             :         return std::numeric_limits<quint16>::max();
     220             :     }
     221             :     const quint16 appearance = qFromLittleEndian<quint16>(value);
     222         102 :     qCDebug(lc).noquote() << tr("Appearance: %1.").arg(appearance);
     223          12 :     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          17 : void GenericAccessServicePrivate::characteristicRead(const QLowEnergyCharacteristic &characteristic,
     231             :                                               const QByteArray &value)
     232             : {
     233          17 :     AbstractPokitServicePrivate::characteristicRead(characteristic, value);
     234             : 
     235             :     Q_Q(GenericAccessService);
     236          17 :     if (characteristic.uuid() == GenericAccessService::CharacteristicUuids::appearance) {
     237           0 :         emit q->appearanceRead(parseAppearance(value));
     238           0 :         return;
     239             :     }
     240             : 
     241          17 :     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          51 :     qCWarning(lc).noquote() << tr("Unknown characteristic read for Generic Access service")
     249          17 :         << 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          17 : void GenericAccessServicePrivate::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
     257             :                                                  const QByteArray &newValue)
     258             : {
     259          17 :     AbstractPokitServicePrivate::characteristicWritten(characteristic, newValue);
     260             : 
     261             :     Q_Q(GenericAccessService);
     262          17 :     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          17 :     if (characteristic.uuid() == GenericAccessService::CharacteristicUuids::deviceName) {
     269           0 :         emit q->deviceNameWritten();
     270           0 :         return;
     271             :     }
     272             : 
     273          51 :     qCWarning(lc).noquote() << tr("Unknown characteristic written for Generic Access service")
     274          17 :         << serviceUuid << characteristic.name() << characteristic.uuid();
     275             : }
     276             : 
     277             : /// \endcond

Generated by: LCOV version 1.14