// 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 . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "bluetoothdevice.h" #include BluetoothDevice::State BluetoothDevice::state() const { return m_state; } QString BluetoothDevice::name() const { return m_name; } QBluetoothAddress BluetoothDevice::address() const { return m_address; } QString BluetoothDevice::iconName() const { return m_iconName; } QBluetoothHostInfo BluetoothDevice::hostInfo() const { return m_hostInfo; } QString BluetoothDevice::alias() const { return m_alias; } bool BluetoothDevice::setAlias(const QString &alias) { if (!m_deviceInterface->isValid()) return false; return m_deviceInterface->setProperty("Alias", QVariant(alias)); } QString BluetoothDevice::modalias() const { return m_modalias; } quint32 BluetoothDevice::deviceClass() const { return m_deviceClass; } quint16 BluetoothDevice::appearance() const { return m_appearance; } qint16 BluetoothDevice::rssi() const { return m_rssi; } qint16 BluetoothDevice::txPower() const { return m_txPower; } QList BluetoothDevice::uuids() const { return m_uuids; } bool BluetoothDevice::paired() const { return m_paired; } bool BluetoothDevice::connected() const { return m_connected; } bool BluetoothDevice::trusted() const { return m_trusted; } bool BluetoothDevice::setTrusted(const bool &trusted) { if (!m_deviceInterface->isValid()) return false; return m_deviceInterface->setProperty("Trusted", QVariant(trusted)); } bool BluetoothDevice::blocked() const { return m_blocked; } bool BluetoothDevice::setBlocked(const bool &blocked) { if (!m_deviceInterface->isValid()) return false; return m_deviceInterface->setProperty("Blocked", QVariant(blocked)); } bool BluetoothDevice::legacyPairing() const { return m_legacyPairing; } bool BluetoothDevice::servicesResolved() const { return m_servicesResolved; } QList BluetoothDevice::services() const { return m_services; } bool BluetoothDevice::hasService(const QBluetoothUuid &serviceUuid) { foreach (BluetoothGattService *service, m_services) { if (service->uuid() == serviceUuid) { return true; } } return false; } BluetoothGattService *BluetoothDevice::getService(const QBluetoothUuid &serviceUuid) { foreach (BluetoothGattService *service, m_services) { if (service->uuid() == serviceUuid) { return service; } } return nullptr; } BluetoothDevice::BluetoothDevice(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent) : QObject(parent), m_path(path), m_state(Disconnected), m_deviceClass(0), m_appearance(0), m_rssi(0), m_txPower(0), m_paired(false), m_connected(false), m_trusted(false), m_blocked(false), m_legacyPairing(false), m_servicesResolved(false), m_connectWatcher(nullptr), m_disconnectWatcher(nullptr), m_pairingWatcher(nullptr) { // Check DBus connection if (!QDBusConnection::systemBus().isConnected()) { qCWarning(dcBluez()) << "System DBus not connected."; return; } m_deviceInterface = new QDBusInterface(orgBluez, m_path.path(), orgBluezDevice1, QDBusConnection::systemBus(), this); if (!m_deviceInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus thing interface for" << m_path.path(); return; } QDBusConnection::systemBus().connect(orgBluez, m_path.path(), "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList))); processProperties(properties); // Set initial state evaluateCurrentState(); } BluetoothDevice::~BluetoothDevice() { } void BluetoothDevice::processProperties(const QVariantMap &properties) { foreach (const QString &propertyName, properties.keys()) { if (propertyName == "Name") { m_name = properties.value(propertyName).toString(); m_hostInfo.setName(m_name); } else if (propertyName == "Address") { m_address = QBluetoothAddress(properties.value(propertyName).toString()); m_hostInfo.setAddress(m_address); } else if (propertyName == "Icon") { m_iconName = properties.value(propertyName).toString(); } else if (propertyName == "Alias") { setAliasInternally(properties.value(propertyName).toString()); } else if (propertyName == "Modalias") { m_modalias = properties.value(propertyName).toString(); } else if (propertyName == "Class") { m_deviceClass = properties.value(propertyName).toUInt(); } else if (propertyName == "Appearance") { m_appearance = properties.value(propertyName).toUInt(); } else if (propertyName == "RSSI") { setRssiInternally(properties.value(propertyName).toInt()); } else if (propertyName == "TxPower") { setTxPowerInternally(properties.value(propertyName).toInt()); } else if (propertyName == "UUIDs") { QStringList uuidStrings = properties.value(propertyName).toStringList(); m_uuids.clear(); foreach (const QString &uuidString, uuidStrings) { QBluetoothUuid uuid = QBluetoothUuid(QUuid(uuidString)); m_uuids.append(uuid); } } else if (propertyName == "Paired") { setPairedInternally(properties.value(propertyName).toBool()); } else if (propertyName == "Connected") { setConnectedInternally(properties.value(propertyName).toBool()); } else if (propertyName == "Trusted") { setTrustedInternally(properties.value(propertyName).toBool()); } else if (propertyName == "Blocked") { setBlockedInternally(properties.value(propertyName).toBool()); } else if (propertyName == "LegacyPairing") { m_legacyPairing = properties.value(propertyName).toBool(); } else if (propertyName == "ServicesResolved") { setServicesResolvedInternally(properties.value(propertyName).toBool()); } } } void BluetoothDevice::evaluateCurrentState() { if (!connected()) { setStateInternally(Disconnected); } else if (connected() && servicesResolved()) { setStateInternally(Discovered); } } void BluetoothDevice::addServiceInternally(const QDBusObjectPath &path, const QVariantMap &properties) { if (hasService(path)) return; BluetoothGattService *service = new BluetoothGattService(path, properties, this); m_services.append(service); qCDebug(dcBluez()) << "[+]" << service; } bool BluetoothDevice::hasService(const QDBusObjectPath &path) { foreach (BluetoothGattService *service, m_services) { if (service->m_path == path) { return true; } } return false; } BluetoothGattService *BluetoothDevice::getService(const QDBusObjectPath &path) { foreach (BluetoothGattService *service, m_services) { if (service->m_path == path) { return service; } } return nullptr; } void BluetoothDevice::setStateInternally(const BluetoothDevice::State &state) { if (m_state != state) { m_state = state; emit stateChanged(m_state); } } void BluetoothDevice::setAliasInternally(const QString &alias) { if (m_alias != alias) { m_alias = alias; emit aliasChanged(m_alias); } } void BluetoothDevice::setRssiInternally(const qint16 &rssi) { if (m_rssi != rssi) { m_rssi = rssi; emit rssiChanged(m_rssi); } } void BluetoothDevice::setTxPowerInternally(const qint16 &txPower) { if (m_txPower != txPower) { m_txPower = txPower; emit txPowerChanged(m_txPower); } } void BluetoothDevice::setPairedInternally(const bool &paired) { if (m_paired != paired) { m_paired = paired; emit pairedChanged(m_paired); // Paired, if services not resolved if (!m_servicesResolved) { setStateInternally(Discovering); } else { setStateInternally(Discovered); } } } void BluetoothDevice::setConnectedInternally(const bool &connected) { if (m_connected != connected) { m_connected = connected; emit connectedChanged(m_connected); if (m_connected) { setStateInternally(Connected); if (!m_servicesResolved) setStateInternally(Discovering); } else { setStateInternally(Disconnected); } } } void BluetoothDevice::setTrustedInternally(const bool &trusted) { if (m_trusted != trusted) { m_trusted = trusted; emit trustedChanged(m_trusted); } } void BluetoothDevice::setBlockedInternally(const bool &blocked) { if (m_blocked != blocked) { m_blocked = blocked; emit blockedChanged(m_blocked); } } void BluetoothDevice::setServicesResolvedInternally(const bool &servicesResolved) { if (m_servicesResolved != servicesResolved) { m_servicesResolved = servicesResolved; emit servicesResolvedChanged(m_servicesResolved); if (m_servicesResolved) setStateInternally(Discovered); // Note: if unresolved, the thing is about to disconnect } } void BluetoothDevice::onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties) { if (interface != orgBluezDevice1) return; qCDebug(dcBluez()) << "BluetoothDevice:" << m_name << m_address << "properties changed" << interface << changedProperties << invalidatedProperties; processProperties(changedProperties); } void BluetoothDevice::onConnectDeviceFinished(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) { setStateInternally(Disconnected); qCWarning(dcBluez()) << "Could not connect device" << m_address.toString() << reply.error().name() << reply.error().message(); } call->deleteLater(); m_connectWatcher = nullptr; } void BluetoothDevice::onDisconnectDeviceFinished(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) qCWarning(dcBluez()) << "Could not disconnect device" << m_address.toString() << reply.error().name() << reply.error().message(); evaluateCurrentState(); call->deleteLater(); m_disconnectWatcher = nullptr; } void BluetoothDevice::onPairingFinished(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) qCWarning(dcBluez()) << "Could not pair device" << m_address.toString() << reply.error().name() << reply.error().message(); evaluateCurrentState(); call->deleteLater(); m_pairingWatcher = nullptr; } void BluetoothDevice::onCancelPairingFinished(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) qCWarning(dcBluez()) << "Could not cancel pairing" << m_address.toString() << reply.error().name() << reply.error().message(); evaluateCurrentState(); call->deleteLater(); } bool BluetoothDevice::connectDevice() { if (!m_deviceInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus thing interface for" << m_path.path(); return false; } if (connected() || state() == Connecting || m_connectWatcher) return true; setStateInternally(Connecting); QDBusPendingCall connectingCall = m_deviceInterface->asyncCall("Connect"); m_connectWatcher = new QDBusPendingCallWatcher(connectingCall, this); connect(m_connectWatcher, &QDBusPendingCallWatcher::finished, this, &BluetoothDevice::onConnectDeviceFinished); return true; } bool BluetoothDevice::disconnectDevice() { if (!m_deviceInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus thing interface for" << m_path.path(); return false; } if (!connected() || state() == Disconnecting || m_disconnectWatcher) return true; setStateInternally(Disconnecting); QDBusPendingCall disconnectingCall = m_deviceInterface->asyncCall("Disconnect"); m_disconnectWatcher = new QDBusPendingCallWatcher(disconnectingCall, this); connect(m_disconnectWatcher, &QDBusPendingCallWatcher::finished, this, &BluetoothDevice::onDisconnectDeviceFinished); return true; } bool BluetoothDevice::disconnectDeviceBlocking() { if (!m_deviceInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus thing interface for" << m_path.path(); return false; } if (!connected() || state() == Disconnecting) return true; qCWarning(dcBluez()) << "Disconnecting blocking" << this; QDBusPendingReply reply = m_deviceInterface->call("Disconnect"); reply.waitForFinished(); if(reply.isError()) { qCWarning(dcBluez()) << reply.error(); return false; } return true; } bool BluetoothDevice::requestPairing() { if (!m_deviceInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus thing interface for" << m_path.path(); return false; } if (paired() || state() == Pairing || m_pairingWatcher) return true; setStateInternally(Pairing); QDBusPendingCall pairCall = m_deviceInterface->asyncCall("Pair"); m_pairingWatcher = new QDBusPendingCallWatcher(pairCall, this); connect(m_pairingWatcher, &QDBusPendingCallWatcher::finished, this, &BluetoothDevice::onPairingFinished); return true; } bool BluetoothDevice::cancelPairingRequest() { if (!m_deviceInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus thing interface for" << m_path.path(); return false; } if (!paired() || state() == Unpairing) return true; setStateInternally(Unpairing); QDBusPendingCall cancelPairingCall = m_deviceInterface->asyncCall("CancelPairing"); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(cancelPairingCall, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothDevice::onPairingFinished); return true; } QDebug operator<<(QDebug debug, BluetoothDevice *thing) { debug.noquote().nospace() << "BluetoothDevice(" << thing->name() << ", " << thing->address() << ") "; return debug; }