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

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

Generated by: LCOV version 1.14