nymea-plugins/nuki/bluez/bluetoothdevice.cpp

549 lines
15 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 "bluetoothdevice.h"
#include <QDBusPendingReply>
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<QBluetoothUuid> 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<BluetoothGattService *> 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<void> 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<void> 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<void> 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<void> 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<void> 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;
}