Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ set(SOURCES
qbledescriptor.cpp
qbledevice.cpp
qbleservice.cpp
qblelocalcharacteristic.cpp
qblelocalservice.cpp
qblelocalapplication.cpp
)

set(HEADERS
Expand All @@ -49,6 +52,9 @@ set(HEADERS
qbledescriptor.h
qbledevice.h
qbleservice.h
qblelocalcharacteristic.h
qblelocalservice.h
qblelocalapplication.h
)

target_sources(qble PUBLIC ${SOURCES} ${HEADERS})
114 changes: 114 additions & 0 deletions qblelocalapplication.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "qblelocalapplication.h"
#include "qblelocalservice.h"
#include "qblelocalcharacteristic.h"

#include <QDBusMetaType>
#include <QDBusInterface>
#include <QDBusPendingCall>
#include <QDBusPendingReply>
#include <QDebug>

static const char *BLUEZ_SERVICE = "org.bluez";
static const char *GATT_MANAGER_IFACE = "org.bluez.GattManager1";
static const char *GATT_SERVICE_IFACE = "org.bluez.GattService1";
static const char *GATT_CHRC_IFACE = "org.bluez.GattCharacteristic1";

QBLELocalApplication::QBLELocalApplication(const QString &path, QObject *parent)
: QObject(parent), m_path(path), m_bus(QDBusConnection::systemBus())
{
qDBusRegisterMetaType<InterfaceList>();
qDBusRegisterMetaType<ManagedObjectList>();

if (!m_bus.registerObject(m_path, this, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllProperties)) {
qCritical() << Q_FUNC_INFO << "Failed to register GATT application object at" << m_path << ":" << m_bus.lastError().message();
} else {
qDebug() << Q_FUNC_INFO << "GATT application registered at" << m_path;
}
}

QBLELocalApplication::~QBLELocalApplication()
{
m_bus.unregisterObject(m_path);
}

QDBusObjectPath QBLELocalApplication::path() const
{
return QDBusObjectPath(m_path);
}

void QBLELocalApplication::addService(QBLELocalService *service)
{
m_services.append(service);
}

bool QBLELocalApplication::registerWithAdapter(const QString &adapterPath)
{
qDebug() << Q_FUNC_INFO << "Registering GATT application with adapter" << adapterPath;

QDBusInterface gattManager(BLUEZ_SERVICE, adapterPath, GATT_MANAGER_IFACE, m_bus);
if (!gattManager.isValid()) {
qWarning() << Q_FUNC_INFO << "GattManager1 not available on" << adapterPath << ":" << gattManager.lastError().message();
return false;
}

QDBusPendingCall reply = gattManager.asyncCall(
QStringLiteral("RegisterApplication"),
QVariant::fromValue(path()),
QVariantMap());

QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this,
[adapterPath](QDBusPendingCallWatcher *w) {
QDBusPendingReply<> reply = *w;
if (reply.isError()) {
qWarning() << "GATT RegisterApplication failed on" << adapterPath << ":" << reply.error().message();
} else {
qInfo() << "GATT application successfully registered with" << adapterPath;
}
w->deleteLater();
});

return true;
}

void QBLELocalApplication::unregisterFromAdapter(const QString &adapterPath)
{
qDebug() << Q_FUNC_INFO << "Unregistering GATT application from" << adapterPath;

QDBusInterface gattManager(BLUEZ_SERVICE, adapterPath, GATT_MANAGER_IFACE, m_bus);
if (gattManager.isValid()) {
gattManager.asyncCall(QStringLiteral("UnregisterApplication"), QVariant::fromValue(path()));
}
}

ManagedObjectList QBLELocalApplication::GetManagedObjects()
{
ManagedObjectList result;

for (QBLELocalService *svc : m_services) {
// --- Service entry ---
InterfaceList svcInterfaces;
QVariantMap svcProps;
svcProps.insert(QStringLiteral("UUID"), svc->uuid());
svcProps.insert(QStringLiteral("Primary"), svc->primary());
svcProps.insert(QStringLiteral("Characteristics"),
QVariant::fromValue(svc->characteristicPaths()));
svcInterfaces.insert(QLatin1String(GATT_SERVICE_IFACE), svcProps);
result.insert(svc->path(), svcInterfaces);

// --- Characteristic entries ---
for (QBLELocalCharacteristic *ch : svc->characteristics()) {
InterfaceList chInterfaces;
QVariantMap chProps;
chProps.insert(QStringLiteral("Service"), QVariant::fromValue(ch->servicePath()));
chProps.insert(QStringLiteral("UUID"), ch->uuid());
chProps.insert(QStringLiteral("Flags"), ch->flags());
chProps.insert(QStringLiteral("Descriptors"),
QVariant::fromValue(ch->descriptorPaths()));
chInterfaces.insert(QLatin1String(GATT_CHRC_IFACE), chProps);
result.insert(ch->path(), chInterfaces);
}
}

return result;
}
69 changes: 69 additions & 0 deletions qblelocalapplication.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#ifndef QBLELOCALAPPLICATION_H
#define QBLELOCALAPPLICATION_H

#include <QObject>
#include <QList>
#include <QString>
#include <QtDBus>

class QBLELocalService;
class QBLELocalCharacteristic;

// D-Bus type aliases required by the BlueZ ObjectManager interface
typedef QMap<QString, QVariantMap> InterfaceList;
typedef QMap<QDBusObjectPath, InterfaceList> ManagedObjectList;

Q_DECLARE_METATYPE(InterfaceList)
Q_DECLARE_METATYPE(ManagedObjectList)

/**
* @brief GATT application root that implements org.freedesktop.DBus.ObjectManager.
*
* BlueZ calls GetManagedObjects() on this object to enumerate all GATT services
* and characteristics offered by the application.
*
* Usage:
* 1. Create a QBLELocalApplication.
* 2. Add QBLELocalService instances with addService().
* 3. Call registerWithAdapter() with the BlueZ adapter D-Bus path.
* 4. BlueZ will then advertise and handle the registered services.
*
* The application is registered on the system D-Bus at the path provided in the
* constructor (default: "/uk/co/piggz/amazfish/gatt").
*/
class QBLELocalApplication : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.freedesktop.DBus.ObjectManager")

public:
explicit QBLELocalApplication(const QString &path = QStringLiteral("/uk/co/piggz/amazfish/gatt"), QObject *parent = nullptr);
~QBLELocalApplication() override;

QDBusObjectPath path() const;
void addService(QBLELocalService *service);

/**
* Register this application with the BlueZ GattManager1 on the given adapter.
* @param adapterPath e.g. "/org/bluez/hci0" from AmazfishConfig::localAdapter()
* @return true on success
*/
bool registerWithAdapter(const QString &adapterPath);

/**
* Unregister this application from the BlueZ GattManager1.
* Call this on shutdown or when the adapter disappears.
*/
void unregisterFromAdapter(const QString &adapterPath);

public Q_SLOTS:
// Called by BlueZ to enumerate all GATT objects managed by this application
ManagedObjectList GetManagedObjects();

private:
QString m_path;
QList<QBLELocalService *> m_services;
QDBusConnection m_bus;
};

#endif // QBLELOCALAPPLICATION_H
60 changes: 60 additions & 0 deletions qblelocalcharacteristic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "qblelocalcharacteristic.h"
#include "qblelocalservice.h"

#include <QDebug>

QBLELocalCharacteristic::QBLELocalCharacteristic(QDBusConnection bus, unsigned int index, const QString &uuid, const QStringList &flags, QBLELocalService *service, QObject *parent)
: QObject(parent), m_uuid(uuid), m_flags(flags), m_service(service)
{
m_path = service->path().path() + QStringLiteral("/char") + QString::number(index);
bus.registerObject(m_path, this, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllProperties);
}

QDBusObjectPath QBLELocalCharacteristic::path() const
{
return QDBusObjectPath(m_path);
}

QDBusObjectPath QBLELocalCharacteristic::servicePath() const
{
return m_service->path();
}

QString QBLELocalCharacteristic::uuid() const
{
return m_uuid;
}

QStringList QBLELocalCharacteristic::flags() const
{
return m_flags;
}

QList<QDBusObjectPath> QBLELocalCharacteristic::descriptorPaths() const
{
return {};
}

// Default implementations — subclasses override what they need

QByteArray QBLELocalCharacteristic::ReadValue(const QVariantMap &)
{
qWarning() << Q_FUNC_INFO << "Default ReadValue called — override in subclass";
return QByteArray();
}

void QBLELocalCharacteristic::WriteValue(const QByteArray &value, const QVariantMap &)
{
qDebug() << Q_FUNC_INFO << "Default WriteValue — emitting valueWritten";
emit valueWritten(value);
}

void QBLELocalCharacteristic::StartNotify()
{
qDebug() << Q_FUNC_INFO << "StartNotify called";
}

void QBLELocalCharacteristic::StopNotify()
{
qDebug() << Q_FUNC_INFO << "StopNotify called";
}
52 changes: 52 additions & 0 deletions qblelocalcharacteristic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#ifndef QBLELOCALCHARACTERISTIC_H
#define QBLELOCALCHARACTERISTIC_H

#include <QObject>
#include <QtDBus>

// Forward declaration
class QBLELocalService;

/**
* @brief Server-side GATT characteristic exposed over D-Bus using the BlueZ GATT API.
*
* Implements org.bluez.GattCharacteristic1. Subclass this and override
* ReadValue() and/or WriteValue() to respond to requests from the remote
* BLE device (e.g. an InfiniTime watch acting as GATT client).
*/
class QBLELocalCharacteristic : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.bluez.GattCharacteristic1")
Q_PROPERTY(QDBusObjectPath Service READ servicePath)
Q_PROPERTY(QString UUID READ uuid)
Q_PROPERTY(QStringList Flags READ flags)
Q_PROPERTY(QList<QDBusObjectPath> Descriptors READ descriptorPaths)

public:
explicit QBLELocalCharacteristic(QDBusConnection bus, unsigned int index, const QString &uuid, const QStringList &flags, QBLELocalService *service, QObject *parent = nullptr);

QDBusObjectPath path() const;
QDBusObjectPath servicePath() const;
QString uuid() const;
QStringList flags() const;
QList<QDBusObjectPath> descriptorPaths() const; // empty for now

Q_SIGNALS:
void valueWritten(const QByteArray &value);

public Q_SLOTS:
// BlueZ GATT server callbacks — override in subclass as needed
virtual QByteArray ReadValue(const QVariantMap &options);
virtual void WriteValue(const QByteArray &value, const QVariantMap &options);
virtual void StartNotify();
virtual void StopNotify();

private:
QString m_path;
QString m_uuid;
QStringList m_flags;
QBLELocalService *m_service;
};

#endif // QBLELOCALCHARACTERISTIC_H
45 changes: 45 additions & 0 deletions qblelocalservice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "qblelocalservice.h"
#include "qblelocalcharacteristic.h"

#include <QDebug>

QBLELocalService::QBLELocalService(QDBusConnection bus, unsigned int index, const QString &uuid, const QString &appPath, QObject *parent)
: QObject(parent), m_uuid(uuid)
{
m_path = appPath + QStringLiteral("/service") + QString::number(index);
bus.registerObject(m_path, this, QDBusConnection::ExportAdaptors | QDBusConnection::ExportAllProperties);
}

QDBusObjectPath QBLELocalService::path() const
{
return QDBusObjectPath(m_path);
}

void QBLELocalService::addCharacteristic(QBLELocalCharacteristic *ch)
{
m_characteristics.append(ch);
}

QList<QDBusObjectPath> QBLELocalService::characteristicPaths() const
{
QList<QDBusObjectPath> result;
for (QBLELocalCharacteristic *ch : m_characteristics) {
result.append(ch->path());
}
return result;
}

QList<QBLELocalCharacteristic *> QBLELocalService::characteristics() const
{
return m_characteristics;
}

QString QBLELocalService::uuid() const
{
return m_uuid;
}

bool QBLELocalService::primary() const
{
return true;
}
Loading