summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@qt.io>2018-08-31 14:56:58 +0200
committerAlex Blasche <alexander.blasche@qt.io>2018-09-14 12:15:04 +0000
commitba0988639b7951798002be10d6868718d56141f6 (patch)
tree7d4ac2c429d9b1291fdeefad7cf53ef07eb1f585 /src
parentaf07a801cb85094d2ab4a45244d5172e64f8ff7b (diff)
Implement handling of BTLE Battery services
Since BlueZ 5.48 battery services are no longer exposed via the generic GATT interface but have their own dedicated Battery1 interface. This patch transforms the dedicated interface back into the previous behavior. Essentially we are emulating the old interface to ensure that the QLowEnergyService user does not have to distinguish. Fixes: QTBUG-70222 Change-Id: Ib9fef41cf16f7562f169f51ee45b19f52de6a0c0 Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/bluetooth/bluez/battery1.cpp26
-rw-r--r--src/bluetooth/bluez/battery1_p.h51
-rw-r--r--src/bluetooth/bluez/bluez.pri2
-rwxr-xr-xsrc/bluetooth/bluez/generate1
-rw-r--r--src/bluetooth/bluez/org.bluez.Battery1.xml7
-rw-r--r--src/bluetooth/qlowenergycontroller_bluezdbus.cpp222
-rw-r--r--src/bluetooth/qlowenergycontroller_bluezdbus_p.h6
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