feat(bluetooth): read the model number and firmware revision from the Device Information service

pull/18/head
reionwong 2 weeks ago
parent 4192424bb7
commit 81e9fe3ea6

@ -16,6 +16,7 @@ inline const QString adapterInterface = QStringLiteral("org.bluez.Adapter1");
inline const QString deviceInterface = QStringLiteral("org.bluez.Device1");
inline const QString propertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");
inline const QString objectManagerInterface = QStringLiteral("org.freedesktop.DBus.ObjectManager");
inline const QString gattCharacteristicInterface = QStringLiteral("org.bluez.GattCharacteristic1");
inline const QString agentPath = QStringLiteral("/org/cutefish/BluetoothAgent");
inline const QString agentManagerPath = QStringLiteral("/org/bluez");
inline const QString agentManagerInterface = QStringLiteral("org.bluez.AgentManager1");

@ -587,6 +587,44 @@ void BluetoothManager::mediaCommand(BluetoothObject *device, const QString &comm
fail(tr("This device has no media player."));
}
void BluetoothManager::readDeviceInformation(BluetoothObject *device)
{
if (!device || object(device->path()) != device || !device->connected())
return;
static const QString modelNumberUuid = QStringLiteral("00002a24-0000-1000-8000-00805f9b34fb");
static const QString firmwareRevisionUuid =
QStringLiteral("00002a26-0000-1000-8000-00805f9b34fb");
QPointer<BluetoothObject> guard(device);
for (auto it = m_objects.cbegin(); it != m_objects.cend(); ++it) {
if (!it.key().startsWith(device->path() + QLatin1Char('/'))
|| !it.value()->hasInterface(gattCharacteristicInterface))
continue;
const QString uuid =
it.value()->value(gattCharacteristicInterface, QStringLiteral("UUID")).toString()
.toLower();
const bool model = uuid == modelNumberUuid;
if (!model && uuid != firmwareRevisionUuid)
continue;
call(it.key(), gattCharacteristicInterface, QStringLiteral("ReadValue"),
{QVariantMap {}},
[guard, model](const QDBusMessage &reply) {
if (!guard || reply.type() == QDBusMessage::ErrorMessage)
return;
// Some devices pad the string with NUL bytes.
QByteArray bytes = reply.arguments().value(0).toByteArray();
const qsizetype end = bytes.indexOf('\0');
if (end >= 0)
bytes.truncate(end);
const QString text = QString::fromUtf8(bytes).trimmed();
if (model)
guard->setModelNumber(text);
else
guard->setFirmwareRevision(text);
},
Errors::Ignore);
}
}
void BluetoothManager::setAgentEnabled(bool enabled)
{
if (m_agentEnabled == enabled)

@ -85,6 +85,7 @@ public:
const QVariantList &arguments = {});
void discovery(BluetoothObject *adapter, bool start);
void mediaCommand(BluetoothObject *device, const QString &command);
void readDeviceInformation(BluetoothObject *device);
void fail(const QString &message);
using Completion = std::function<void(const QDBusMessage &)>;
// Background refreshes race object removal; their failures are not the user's concern.

@ -15,6 +15,11 @@ QVariant BluetoothObject::value(const QString &property) const
return m_interfaces.value(isAdapter() ? adapterInterface : deviceInterface).value(property);
}
QVariant BluetoothObject::value(const QString &interface, const QString &property) const
{
return m_interfaces.value(interface).value(property);
}
QString BluetoothObject::address() const
{
return value(QStringLiteral("Address")).toString();
@ -205,6 +210,11 @@ void BluetoothObject::mediaCommand(const QString &command)
m_manager->mediaCommand(this, command);
}
void BluetoothObject::readDeviceInformation()
{
m_manager->readDeviceInformation(this);
}
void BluetoothObject::update(const QString &interface, const QVariantMap &properties,
const QStringList &invalidated)
{
@ -239,3 +249,19 @@ void BluetoothObject::setBusy(bool busy)
m_busy = busy;
emit changed();
}
void BluetoothObject::setModelNumber(const QString &modelNumber)
{
if (m_modelNumber == modelNumber)
return;
m_modelNumber = modelNumber;
emit changed();
}
void BluetoothObject::setFirmwareRevision(const QString &firmwareRevision)
{
if (m_firmwareRevision == firmwareRevision)
return;
m_firmwareRevision = firmwareRevision;
emit changed();
}

@ -29,6 +29,10 @@ class BluetoothObject : public QObject
Q_PROPERTY(QStringList uuids READ uuids NOTIFY changed)
Q_PROPERTY(QString adapterPath READ adapterPath NOTIFY changed)
Q_PROPERTY(bool busy READ busy NOTIFY changed)
// From the device's Device Information service; empty until readDeviceInformation()
// finds them on a connected device.
Q_PROPERTY(QString modelNumber READ modelNumber NOTIFY changed)
Q_PROPERTY(QString firmwareRevision READ firmwareRevision NOTIFY changed)
public:
BluetoothObject(const QString &path, BluetoothManager *manager);
@ -53,6 +57,10 @@ public:
bool busy() const { return m_busy; }
QString modelNumber() const { return m_modelNumber; }
QString firmwareRevision() const { return m_firmwareRevision; }
bool isAdapter() const;
bool isDevice() const;
@ -61,6 +69,7 @@ public:
quint64 revision(const QString &interface, const QString &property) const;
QVariant value(const QString &property) const;
QVariant value(const QString &interface, const QString &property) const;
void setName(const QString &name);
void setPowered(bool enabled);
void setDiscoverable(bool enabled);
@ -71,6 +80,8 @@ public:
const QStringList &invalidated = {});
void removeInterface(const QString &interface);
void setBusy(bool busy);
void setModelNumber(const QString &modelNumber);
void setFirmwareRevision(const QString &firmwareRevision);
Q_INVOKABLE void connectToDevice();
Q_INVOKABLE void disconnectFromDevice();
Q_INVOKABLE void pair();
@ -81,6 +92,7 @@ public:
Q_INVOKABLE void startDiscovery();
Q_INVOKABLE void stopDiscovery();
Q_INVOKABLE void mediaCommand(const QString &command);
Q_INVOKABLE void readDeviceInformation();
signals:
void changed();
@ -92,4 +104,6 @@ private:
QMap<QString, QMap<QString, quint64>> m_revisions;
quint64 m_revision = 0;
bool m_busy = false;
QString m_modelNumber;
QString m_firmwareRevision;
};

@ -86,6 +86,11 @@ public:
set(message.path(), deviceInterface, QStringLiteral("Connected"),
message.member() == QStringLiteral("Connect"));
connection.send(message.createReply());
} else if (message.member() == QStringLiteral("ReadValue")) {
const auto value = objects.value(QDBusObjectPath(message.path()))
.value(QStringLiteral("org.bluez.GattCharacteristic1"))
.value(QStringLiteral("Value"));
connection.send(message.createReply(QVariantList {value.toByteArray()}));
} else if (message.member() == QStringLiteral("RemoveDevice")) {
remove(qvariant_cast<QDBusObjectPath>(args[0]).path(), {deviceInterface});
connection.send(message.createReply());

@ -199,6 +199,32 @@ private slots:
QCOMPARE(fake->lastPath, adapter);
}
void deviceInformation()
{
const QString service = device + QStringLiteral("/service000e");
const QString characteristic = QStringLiteral("org.bluez.GattCharacteristic1");
const auto add = [&](const QString &name, const QString &uuid, const QByteArray &value) {
fake->add(service + name,
{{characteristic, {{"UUID", uuid}, {"Value", value}}}});
};
add(QStringLiteral("/char000f"), QStringLiteral("00002a29-0000-1000-8000-00805f9b34fb"),
"Logitech");
add(QStringLiteral("/char0011"), QStringLiteral("00002a24-0000-1000-8000-00805f9b34fb"),
"Pebble K390");
add(QStringLiteral("/char0015"), QStringLiteral("00002A26-0000-1000-8000-00805F9B34FB"),
QByteArray("RBK80.00_0011\0\0", 15));
QTRY_VERIFY(manager->object(service + QStringLiteral("/char0015")));
auto *entry = manager->object(device);
entry->readDeviceInformation();
QVERIFY(!fake->calls.contains(QStringLiteral("ReadValue")));
fake->set(device, deviceInterface, QStringLiteral("Connected"), true);
QTRY_VERIFY(entry->connected());
entry->readDeviceInformation();
QTRY_COMPARE(entry->modelNumber(), QStringLiteral("Pebble K390"));
QTRY_COMPARE(entry->firmwareRevision(), QStringLiteral("RBK80.00_0011"));
QCOMPARE(fake->calls.count(QStringLiteral("ReadValue")), 2);
}
void pairThroughDefaultAgent()
{
QVERIFY(!manager->agentEnabled());

Loading…
Cancel
Save