diff options
-rw-r--r-- | src/bluetooth/bluez/battery1.cpp | 26 | ||||
-rw-r--r-- | src/bluetooth/bluez/battery1_p.h | 51 | ||||
-rw-r--r-- | src/bluetooth/bluez/bluez.pri | 2 | ||||
-rwxr-xr-x | src/bluetooth/bluez/generate | 1 | ||||
-rw-r--r-- | src/bluetooth/bluez/org.bluez.Battery1.xml | 7 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_bluezdbus.cpp | 222 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_bluezdbus_p.h | 6 |
7 files changed, 299 insertions, 16 deletions
diff --git a/src/bluetooth/bluez/battery1.cpp b/src/bluetooth/bluez/battery1.cpp new file mode 100644 index 00000000..f23cc205 --- /dev/null +++ b/src/bluetooth/bluez/battery1.cpp @@ -0,0 +1,26 @@ +/* + * This file was generated by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -p battery1_p.h:battery1.cpp org.bluez.Battery1.xml + * + * qdbusxml2cpp is Copyright (C) 2018 The Qt Company Ltd. + * + * This is an auto-generated file. + * This file may have been hand-edited. Look for HAND-EDIT comments + * before re-generating it. + */ + +#include "battery1_p.h" + +/* + * Implementation of interface class OrgBluezBattery1Interface + */ + +OrgBluezBattery1Interface::OrgBluezBattery1Interface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) + : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) +{ +} + +OrgBluezBattery1Interface::~OrgBluezBattery1Interface() +{ +} + diff --git a/src/bluetooth/bluez/battery1_p.h b/src/bluetooth/bluez/battery1_p.h new file mode 100644 index 00000000..035382df --- /dev/null +++ b/src/bluetooth/bluez/battery1_p.h @@ -0,0 +1,51 @@ +/* + * This file was generated by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -p battery1_p.h:battery1.cpp org.bluez.Battery1.xml + * + * qdbusxml2cpp is Copyright (C) 2018 The Qt Company Ltd. + * + * This is an auto-generated file. + * Do not edit! All changes made to it will be lost. + */ + +#ifndef BATTERY1_P_H +#define BATTERY1_P_H + +#include <QtCore/QObject> +#include <QtCore/QByteArray> +#include <QtCore/QList> +#include <QtCore/QMap> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QVariant> +#include <QtDBus/QtDBus> + +/* + * Proxy class for interface org.bluez.Battery1 + */ +class OrgBluezBattery1Interface: public QDBusAbstractInterface +{ + Q_OBJECT +public: + static inline const char *staticInterfaceName() + { return "org.bluez.Battery1"; } + +public: + OrgBluezBattery1Interface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr); + + ~OrgBluezBattery1Interface(); + + Q_PROPERTY(uchar Percentage READ percentage) + inline uchar percentage() const + { return qvariant_cast< uchar >(property("Percentage")); } + +public Q_SLOTS: // METHODS +Q_SIGNALS: // SIGNALS +}; + +namespace org { + namespace bluez { + typedef ::OrgBluezBattery1Interface Battery1; + } +} +#endif diff --git a/src/bluetooth/bluez/bluez.pri b/src/bluetooth/bluez/bluez.pri index 4201f104..af7f0e0c 100644 --- a/src/bluetooth/bluez/bluez.pri +++ b/src/bluetooth/bluez/bluez.pri @@ -22,6 +22,7 @@ HEADERS += bluez/manager_p.h \ bluez/gattchar1_p.h \ bluez/gattdesc1_p.h \ bluez/gattservice1_p.h \ + bluez/battery1_p.h \ bluez/bluez_data_p.h \ bluez/hcimanager_p.h \ bluez/remotedevicemanager_p.h \ @@ -51,6 +52,7 @@ SOURCES += bluez/manager.cpp \ bluez/gattchar1.cpp \ bluez/gattdesc1.cpp \ bluez/gattservice1.cpp \ + bluez/battery1.cpp \ bluez/hcimanager.cpp \ bluez/remotedevicemanager.cpp \ bluez/bluetoothmanagement.cpp diff --git a/src/bluetooth/bluez/generate b/src/bluetooth/bluez/generate index 73d3b3f5..b3db63ff 100755 --- a/src/bluetooth/bluez/generate +++ b/src/bluetooth/bluez/generate @@ -24,4 +24,5 @@ qdbusxml2cpp -p obex_transfer1_bluez5_p.h:obex_transfer1_bluez5_p.h org.bluez.ob qdbusxml2cpp -p gattchar1_p.h:gattchar1.cpp org.bluez.GattCharacteristic1.xml qdbusxml2cpp -p gattdesc1_p.h:gattdesc1.cpp org.bluez.GattDescriptor1.xml qdbusxml2cpp -p gattservice1_p.h:gattservice1.cpp org.bluez.GattService1.xml +qdbusxml2cpp -p battery1_p.h:battery1.cpp org.bluez.Battery1.xml diff --git a/src/bluetooth/bluez/org.bluez.Battery1.xml b/src/bluetooth/bluez/org.bluez.Battery1.xml new file mode 100644 index 00000000..efa0309d --- /dev/null +++ b/src/bluetooth/bluez/org.bluez.Battery1.xml @@ -0,0 +1,7 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.bluez.Battery1"> + <property name="Percentage" type="y" access="read"></property> + </interface> +</node> diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp index 73fd94ae..e881a1b9 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus.cpp +++ b/src/bluetooth/qlowenergycontroller_bluezdbus.cpp @@ -42,9 +42,9 @@ #include "bluez/bluez5_helper_p.h" #include "bluez/device1_bluez5_p.h" #include "bluez/gattservice1_p.h" - #include "bluez/gattchar1_p.h" #include "bluez/gattdesc1_p.h" +#include "bluez/battery1_p.h" #include "bluez/objectmanager_p.h" #include "bluez/properties_p.h" @@ -120,6 +120,53 @@ void QLowEnergyControllerPrivateBluezDBus::devicePropertiesChanged( } } } + } else if (interface == QStringLiteral("org.bluez.Battery1")) { + qCDebug(QT_BT_BLUEZ) << "######" << interface << changedProperties; + if (changedProperties.contains(QStringLiteral("Percentage"))) { + // if battery service is discovered and ClientCharConfig is enabled + // emit characteristicChanged() signal + const QBluetoothUuid uuid(QBluetoothUuid::BatteryService); + if (!serviceList.contains(uuid) || !dbusServices.contains(uuid) + || !dbusServices[uuid].hasBatteryService + || dbusServices[uuid].batteryInterface.isNull()) + return; + + QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(uuid); + if (serviceData->state != QLowEnergyService::ServiceDiscovered) + return; + + QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData>::iterator iter; + iter = serviceData->characteristicList.begin(); + while (iter != serviceData->characteristicList.end()) { + auto &charData = iter.value(); + if (charData.uuid != QBluetoothUuid::BatteryLevel) + continue; + + // Client Characteristic Notification enabled? + bool cccActive = false; + for (const QLowEnergyServicePrivate::DescData &descData : qAsConst(charData.descriptorList)) { + if (descData.uuid != QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)) + continue; + if (descData.value == QByteArray::fromHex("0100") + || descData.value == QByteArray::fromHex("0200")) { + cccActive = true; + break; + } + } + + const QByteArray newValue(1, char(dbusServices[uuid].batteryInterface->percentage())); + qCDebug(QT_BT_BLUEZ) << "Battery1 char update" << cccActive + << charData.value.toHex() << "->" << newValue.toHex(); + if (cccActive && newValue != charData.value) { + qCDebug(QT_BT_BLUEZ) << "Property update for Battery1"; + charData.value = newValue; + QLowEnergyCharacteristic ch(serviceData, iter.key()); + emit serviceData->characteristicChanged(ch, newValue); + } + + break; + } + } } } @@ -358,10 +405,50 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() Q_Q(QLowEnergyController); + auto setupServicePrivate = [&, q]( + QLowEnergyService::ServiceType type, const QBluetoothUuid &uuid, const QString &path){ + QSharedPointer<QLowEnergyServicePrivate> priv = QSharedPointer<QLowEnergyServicePrivate>::create(); + priv->uuid = uuid; + priv->type = type; // we make a guess we cannot validate + priv->setController(this); + + GattService serviceContainer; + serviceContainer.servicePath = path; + if (uuid == QBluetoothUuid::BatteryService) + serviceContainer.hasBatteryService = true; + + serviceList.insert(priv->uuid, priv); + dbusServices.insert(priv->uuid, serviceContainer); + + emit q->serviceDiscovered(priv->uuid); + }; + const ManagedObjectList managedObjectList = reply.value(); const QString servicePathPrefix = device->path().append(QStringLiteral("/service")); for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) { const InterfaceList &ifaceList = it.value(); + + if (!it.key().path().startsWith(device->path())) + continue; + + // Since Bluez 5.48 battery services (0x180f) are no longer exposed + // as generic services under servicePathPrefix. + // A dedicated org.bluez.Battery1 interface is exposed. Here we are going to revert + // Bettery1 to the generic pattern. + if (it.key().path() == device->path()) { + // find Battery1 service + for (InterfaceList::const_iterator battIter = ifaceList.constBegin(); battIter != ifaceList.constEnd(); ++battIter) { + const QString &iface = battIter.key(); + if (iface == QStringLiteral("org.bluez.Battery1")) { + qCDebug(QT_BT_BLUEZ) << "Found dedicated Battery service -> emulating generic btle access"; + setupServicePrivate(QLowEnergyService::PrimaryService, + QBluetoothUuid::BatteryService, + it.key().path()); + } + } + continue; + } + if (!it.key().path().startsWith(servicePathPrefix)) continue; @@ -372,21 +459,10 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() QScopedPointer<OrgBluezGattService1Interface> service(new OrgBluezGattService1Interface( QStringLiteral("org.bluez"),it.key().path(), QDBusConnection::systemBus(), this)); - - QSharedPointer<QLowEnergyServicePrivate> priv = QSharedPointer<QLowEnergyServicePrivate>::create(); - priv->uuid = QBluetoothUuid(service->uUID()); - service->primary() - ? priv->type = QLowEnergyService::PrimaryService - : priv->type = QLowEnergyService::IncludedService; - priv->setController(this); - - GattService serviceContainer; - serviceContainer.servicePath = it.key().path(); - - serviceList.insert(priv->uuid, priv); - dbusServices.insert(priv->uuid, serviceContainer); - - emit q->serviceDiscovered(priv->uuid); + setupServicePrivate(service->primary() + ? QLowEnergyService::PrimaryService + : QLowEnergyService::IncludedService, + QBluetoothUuid(service->uUID()), it.key().path()); } } } @@ -395,6 +471,57 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServices() emit q->discoveryFinished(); } +void QLowEnergyControllerPrivateBluezDBus::discoverBatteryServiceDetails( + GattService &dbusData, QSharedPointer<QLowEnergyServicePrivate> serviceData) +{ + // This process exists to work around the fact that Battery services (0x180f) + // are not mapped as generic services but use the Battery1 interface. + // Artificial chararacteristics and descriptors are created to emulate the generic behavior. + + auto batteryService = QSharedPointer<OrgBluezBattery1Interface>::create( + QStringLiteral("org.bluez"), dbusData.servicePath, + QDBusConnection::systemBus()); + dbusData.batteryInterface = batteryService; + + serviceData->startHandle = runningHandle++; //service start handle + + // Create BatteryLevel char + QLowEnergyHandle indexHandle = runningHandle++; // char handle index + QLowEnergyServicePrivate::CharData charData; + + charData.valueHandle = runningHandle++; + charData.properties.setFlag(QLowEnergyCharacteristic::Read); + charData.properties.setFlag(QLowEnergyCharacteristic::Notify); + charData.uuid = QBluetoothUuid::BatteryLevel; + charData.value = QByteArray(1, char(batteryService->percentage())); + + // Create the descriptors for the BatteryLevel + // They are hardcoded although CCC may change + QLowEnergyServicePrivate::DescData descData; + QLowEnergyHandle descriptorHandle = runningHandle++; + descData.uuid = QBluetoothUuid::ClientCharacteristicConfiguration; + descData.value = QByteArray::fromHex("0000"); // all configs off + charData.descriptorList.insert(descriptorHandle, descData); + + descriptorHandle = runningHandle++; + descData.uuid = QBluetoothUuid::CharacteristicPresentationFormat; + //for details see Characteristic Presentation Format Vol3, Part G 3.3.3.5 + // unsigend 8 bit, exp=1, org.bluetooth.unit.percentage, namespace & description + // bit order: little endian + descData.value = QByteArray::fromHex("0400ad27011131"); + charData.descriptorList.insert(descriptorHandle, descData); + + descriptorHandle = runningHandle++; + descData.uuid = QBluetoothUuid::ReportReference; + descData.value = QByteArray::fromHex("0401"); + charData.descriptorList.insert(descriptorHandle, descData); + + serviceData->characteristicList[indexHandle] = charData; + serviceData->endHandle = runningHandle++; + + serviceData->setState(QLowEnergyService::ServiceDiscovered); +} + void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoothUuid &service) { if (!serviceList.contains(service) || !dbusServices.contains(service)) { @@ -410,6 +537,12 @@ void QLowEnergyControllerPrivateBluezDBus::discoverServiceDetails(const QBluetoo GattService &dbusData = dbusServices[service]; dbusData.characteristics.clear(); + if (dbusData.hasBatteryService) { + qCDebug(QT_BT_BLUEZ) << "Triggering Battery1 service discovery on " << dbusData.servicePath; + discoverBatteryServiceDetails(dbusData, serviceData); + return; + } + QDBusPendingReply<ManagedObjectList> reply = managerBluez->GetManagedObjects(); reply.waitForFinished(); if (reply.isError()) { @@ -971,6 +1104,20 @@ void QLowEnergyControllerPrivateBluezDBus::readCharacteristic( qCWarning(QT_BT_BLUEZ) << "Reading non-readable char" << charHandle; } + const GattService &gattService = dbusServices[service->uuid]; + if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) { + // Reread from dbus interface and write to local cache + const QByteArray newValue(1, char(gattService.batteryInterface->percentage())); + quint16 result = updateValueOfCharacteristic(charHandle, newValue, false); + if (result > 0) { + QLowEnergyCharacteristic ch(service, charHandle); + emit service->characteristicRead(ch, newValue); + } else { + service->setError(QLowEnergyService::CharacteristicReadError); + } + return; + } + GattJob job; job.flags = GattJob::JobFlags({GattJob::CharRead}); job.service = service; @@ -994,6 +1141,17 @@ void QLowEnergyControllerPrivateBluezDBus::readDescriptor( if (!charDetails.descriptorList.contains(descriptorHandle)) return; + const GattService &gattService = dbusServices[service->uuid]; + if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) { + auto descriptor = descriptorForHandle(descriptorHandle); + if (descriptor.isValid()) + emit service->descriptorRead(descriptor, descriptor.value()); + else + service->setError(QLowEnergyService::DescriptorReadError); + + return; + } + GattJob job; job.flags = GattJob::JobFlags({GattJob::DescRead}); job.service = service; @@ -1017,6 +1175,14 @@ void QLowEnergyControllerPrivateBluezDBus::writeCharacteristic( } if (role == QLowEnergyController::CentralRole) { + const GattService &gattService = dbusServices[service->uuid]; + if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) { + //Battery1 interface is readonly + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + + GattJob job; job.flags = GattJob::JobFlags({GattJob::CharWrite}); job.service = service; @@ -1043,6 +1209,30 @@ void QLowEnergyControllerPrivateBluezDBus::writeDescriptor( return; if (role == QLowEnergyController::CentralRole) { + const GattService &gattService = dbusServices[service->uuid]; + if (gattService.hasBatteryService && !gattService.batteryInterface.isNull()) { + auto descriptor = descriptorForHandle(descriptorHandle); + if (!descriptor.isValid()) + return; + + if (descriptor.uuid() == QBluetoothUuid::ClientCharacteristicConfiguration) { + if (newValue == QByteArray::fromHex("0000") + || newValue == QByteArray::fromHex("0100") + || newValue == QByteArray::fromHex("0200")) { + quint16 result = updateValueOfDescriptor(charHandle, descriptorHandle, newValue, false); + if (result > 0) + emit service->descriptorWritten(descriptor, newValue); + else + emit service->setError(QLowEnergyService::DescriptorWriteError); + + } + } else { + service->setError(QLowEnergyService::DescriptorWriteError); + } + + return; + } + GattJob job; job.flags = GattJob::JobFlags({GattJob::DescWrite}); job.service = service; diff --git a/src/bluetooth/qlowenergycontroller_bluezdbus_p.h b/src/bluetooth/qlowenergycontroller_bluezdbus_p.h index 4c0f5b74..4c169d0b 100644 --- a/src/bluetooth/qlowenergycontroller_bluezdbus_p.h +++ b/src/bluetooth/qlowenergycontroller_bluezdbus_p.h @@ -58,6 +58,7 @@ #include <QtDBus/QDBusObjectPath> class OrgBluezAdapter1Interface; +class OrgBluezBattery1Interface; class OrgBluezDevice1Interface; class OrgBluezGattCharacteristic1Interface; class OrgBluezGattDescriptor1Interface; @@ -155,6 +156,9 @@ private: { QString servicePath; QVector<GattCharacteristic> characteristics; + + bool hasBatteryService = false; + QSharedPointer<OrgBluezBattery1Interface> batteryInterface; }; QHash<QBluetoothUuid, GattService> dbusServices; @@ -183,6 +187,8 @@ private: bool jobPending = false; void prepareNextJob(); + void discoverBatteryServiceDetails(GattService &dbusData, + QSharedPointer<QLowEnergyServicePrivate> serviceData); }; QT_END_NAMESPACE |