311 lines
11 KiB
C++
311 lines
11 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright (C) 2013 - 2024, nymea GmbH
|
|
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
|
|
*
|
|
* This file is part of nymea-plugins.
|
|
*
|
|
* nymea-plugins is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* nymea-plugins is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "bluetoothmanager.h"
|
|
|
|
#include <QDBusObjectPath>
|
|
#include <QDBusArgument>
|
|
#include <QDBusMetaType>
|
|
|
|
BluetoothManager::BluetoothManager(QObject *parent) :
|
|
QObject(parent),
|
|
m_available(false)
|
|
{
|
|
qDBusRegisterMetaType<InterfaceList>();
|
|
qDBusRegisterMetaType<ManagedObjectList>();
|
|
|
|
// Check DBus connection
|
|
if (!QDBusConnection::systemBus().isConnected()) {
|
|
qCWarning(dcBluez()) << "System DBus not connected.";
|
|
return;
|
|
}
|
|
|
|
// Get notification when bluez appears/disappears on DBus
|
|
m_serviceWatcher = new QDBusServiceWatcher(orgBluez, QDBusConnection::systemBus(), QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration, this);
|
|
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &BluetoothManager::serviceRegistered);
|
|
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &BluetoothManager::serviceUnregistered);
|
|
|
|
m_objectManagerInterface = new QDBusInterface(orgBluez, "/", orgFreedesktopDBusObjectManager, QDBusConnection::systemBus(), this);
|
|
if (!m_objectManagerInterface->isValid()) {
|
|
qCWarning(dcBluez()) << "Invalid DBus ObjectManager interface.";
|
|
return;
|
|
}
|
|
|
|
QDBusConnection::systemBus().connect(orgBluez, "/", orgFreedesktopDBusObjectManager, "InterfacesAdded", this, SLOT(onInterfaceAdded(QDBusObjectPath,InterfaceList)));
|
|
QDBusConnection::systemBus().connect(orgBluez, "/", orgFreedesktopDBusObjectManager, "InterfacesRemoved", this, SLOT(onInterfaceRemoved(QDBusObjectPath,QStringList)));
|
|
|
|
init();
|
|
}
|
|
|
|
QList<BluetoothAdapter *> BluetoothManager::adapters() const
|
|
{
|
|
return m_adapters;
|
|
}
|
|
|
|
bool BluetoothManager::isAvailable() const
|
|
{
|
|
return m_available;
|
|
}
|
|
|
|
void BluetoothManager::init()
|
|
{
|
|
// Get current object from org.bluez
|
|
QDBusMessage query = m_objectManagerInterface->call("GetManagedObjects");
|
|
if(query.type() != QDBusMessage::ReplyMessage) {
|
|
qCWarning(dcBluez()) << "Could not initialize BluetoothManager:" << query.errorName() << query.errorMessage();
|
|
return;
|
|
}
|
|
|
|
const QDBusArgument &argument = query.arguments().at(0).value<QDBusArgument>();
|
|
ManagedObjectList objectList = qdbus_cast<ManagedObjectList>(argument);
|
|
processObjectList(objectList);
|
|
|
|
if (!m_adapters.isEmpty())
|
|
setAvailable(true);
|
|
|
|
qCDebug(dcBluez()) << "BluetoothManager initialized successfully.";
|
|
}
|
|
|
|
void BluetoothManager::clean()
|
|
{
|
|
// Delete all adapter objects
|
|
// Note: devices, services and characteristic objects will be removed throug parent relation
|
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
|
m_adapters.removeOne(adapter);
|
|
emit adapterRemoved(adapter);
|
|
adapter->deleteLater();
|
|
}
|
|
|
|
m_adapters.clear();
|
|
|
|
setAvailable(false);
|
|
}
|
|
|
|
void BluetoothManager::setAvailable(const bool &available)
|
|
{
|
|
if (m_available != available) {
|
|
m_available = available;
|
|
emit availableChanged(m_available);
|
|
}
|
|
}
|
|
|
|
void BluetoothManager::processObjectList(const ManagedObjectList &objectList)
|
|
{
|
|
foreach (const QDBusObjectPath &objectPath, objectList.keys()) {
|
|
InterfaceList interfaceList = objectList.value(objectPath);
|
|
processInterfaceList(objectPath, interfaceList);
|
|
}
|
|
}
|
|
|
|
void BluetoothManager::processInterfaceList(const QDBusObjectPath &objectPath, const InterfaceList &interfaceList)
|
|
{
|
|
// Note: object hierarchy: first add adapters, than devices, services, characteristics and finally descriptors
|
|
|
|
// Adapter interface
|
|
foreach (const QString &interface, interfaceList.keys()) {
|
|
if (interface == orgBluezAdapter1) {
|
|
QVariantMap properties = interfaceList.value(interface);
|
|
// Check if this adapter already added
|
|
if (!adapterAlreadyAdded(objectPath)) {
|
|
BluetoothAdapter *adapter = new BluetoothAdapter(objectPath, properties, this);
|
|
m_adapters.append(adapter);
|
|
emit adapterAdded(adapter);
|
|
qCDebug(dcBluez()) << "[+]" << adapter;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Device interface
|
|
foreach (const QString &interface, interfaceList.keys()) {
|
|
if (interface == orgBluezDevice1) {
|
|
QVariantMap properties = interfaceList.value(interface);
|
|
// Find adapter for this thing and add the thing internally
|
|
if (properties.contains("Adapter")) {
|
|
QDBusObjectPath adapterObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Adapter"));
|
|
BluetoothAdapter *adapter = findAdapter(adapterObjectPath);
|
|
if (adapter)
|
|
adapter->addDeviceInternally(objectPath, properties);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// GATT Service interface
|
|
foreach (const QString &interface, interfaceList.keys()) {
|
|
if (interface == orgBluezGattService1) {
|
|
QVariantMap properties = interfaceList.value(interface);
|
|
// Find thing for this service and add the service internally
|
|
if (properties.contains("Device")) {
|
|
QDBusObjectPath deviceObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Device"));
|
|
BluetoothDevice *thing = findDevice(deviceObjectPath);
|
|
if (thing)
|
|
thing->addServiceInternally(objectPath, properties);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// GATT Characteristic interface
|
|
foreach (const QString &interface, interfaceList.keys()) {
|
|
if (interface == orgBluezGattCharacteristic1) {
|
|
QVariantMap properties = interfaceList.value(interface);
|
|
// Find service for this characteristic
|
|
if (properties.contains("Service")) {
|
|
QDBusObjectPath serviceObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Service"));
|
|
BluetoothGattService *service = findService(serviceObjectPath);
|
|
if (service) {
|
|
qCDebug(dcBluez()) << "Add characteristic" << serviceObjectPath.path() << properties << service;
|
|
service->addCharacteristicInternally(objectPath, properties);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// GATT Descriptor interface
|
|
foreach (const QString &interface, interfaceList.keys()) {
|
|
if (interface == orgBluezGattDescriptor1) {
|
|
QVariantMap properties = interfaceList.value(interface);
|
|
// Find characteristic for this desciptor
|
|
if (properties.contains("Characteristic")) {
|
|
QDBusObjectPath characterisitcObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Characteristic"));
|
|
BluetoothGattCharacteristic *characteristic = findCharacteristic(characterisitcObjectPath);
|
|
if (characteristic) {
|
|
qCDebug(dcBluez()) << "Add descriptor" << characterisitcObjectPath.path() << properties << characteristic;
|
|
characteristic->addDescriptorInternally(objectPath, properties);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
bool BluetoothManager::adapterAlreadyAdded(const QDBusObjectPath &objectPath)
|
|
{
|
|
foreach (BluetoothAdapter *existingAdapter, m_adapters) {
|
|
if (existingAdapter->m_path == objectPath) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
BluetoothAdapter *BluetoothManager::findAdapter(const QDBusObjectPath &objectPath)
|
|
{
|
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
|
if (adapter->m_path == objectPath) {
|
|
return adapter;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
BluetoothDevice *BluetoothManager::findDevice(const QDBusObjectPath &objectPath)
|
|
{
|
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
|
foreach (BluetoothDevice *thing, adapter->devices()) {
|
|
if (thing->m_path == objectPath) {
|
|
return thing;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
BluetoothGattService *BluetoothManager::findService(const QDBusObjectPath &objectPath)
|
|
{
|
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
|
foreach (BluetoothDevice *thing, adapter->devices()) {
|
|
if (thing->hasService(objectPath)) {
|
|
return thing->getService(objectPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
BluetoothGattCharacteristic *BluetoothManager::findCharacteristic(const QDBusObjectPath &objectPath)
|
|
{
|
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
|
foreach (BluetoothDevice *thing, adapter->devices()) {
|
|
foreach (BluetoothGattService *service, thing->services()) {
|
|
if (service->hasCharacteristic(objectPath)) {
|
|
return service->getCharacteristic(objectPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void BluetoothManager::serviceRegistered(const QString &serviceName)
|
|
{
|
|
qCDebug(dcBluez()) << "BluetoothManager: service registered" << serviceName;
|
|
init();
|
|
}
|
|
|
|
void BluetoothManager::serviceUnregistered(const QString &serviceName)
|
|
{
|
|
qCDebug(dcBluez()) << "BluetoothManager: service unregistered" << serviceName;
|
|
if (serviceName == orgBluez)
|
|
clean();
|
|
}
|
|
|
|
void BluetoothManager::onInterfaceAdded(const QDBusObjectPath &objectPath, const InterfaceList &interfaceList)
|
|
{
|
|
//qCDebug(dcBluez()) << "Interface added" << objectPath.path();
|
|
processInterfaceList(objectPath, interfaceList);
|
|
}
|
|
|
|
void BluetoothManager::onInterfaceRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces)
|
|
{
|
|
//qCDebug(dcBluez()) << "Interface removed" << objectPath.path() << interfaces;
|
|
|
|
// Adapter removed
|
|
if (interfaces.contains(orgBluezAdapter1)) {
|
|
BluetoothAdapter *adapter = findAdapter(objectPath);
|
|
qCDebug(dcBluez()) << "[-]" << adapter;
|
|
if (adapter) {
|
|
m_adapters.removeOne(adapter);
|
|
emit adapterRemoved(adapter);
|
|
adapter->deleteLater();
|
|
}
|
|
}
|
|
|
|
// Device removed
|
|
if (interfaces.contains(orgBluezDevice1)) {
|
|
// Find adapter for this thing
|
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
|
if (adapter->hasDevice(objectPath)) {
|
|
adapter->removeDeviceInternally(objectPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|