Merge PR #657: Add support for Bluetooth pairing during thing setup

This commit is contained in:
jenkins 2023-11-29 14:13:39 +01:00
commit 89615140eb
16 changed files with 479 additions and 180 deletions

View File

@ -50,7 +50,7 @@ BluetoothDiscoveryReplyImplementation::BluetoothDiscoveryReplyError BluetoothDis
return m_error; return m_error;
} }
QList<QBluetoothDeviceInfo> BluetoothDiscoveryReplyImplementation::discoveredDevices() const QList<QPair<QBluetoothDeviceInfo, QBluetoothHostInfo>> BluetoothDiscoveryReplyImplementation::discoveredDevices() const
{ {
return m_discoveredDevices; return m_discoveredDevices;
} }
@ -63,16 +63,22 @@ void BluetoothDiscoveryReplyImplementation::setError(const BluetoothDiscoveryRep
} }
} }
void BluetoothDiscoveryReplyImplementation::setDiscoveredDevices(const QList<QBluetoothDeviceInfo> &discoveredDevices) void BluetoothDiscoveryReplyImplementation::setDiscoveredDevices(const QList<QPair<QBluetoothDeviceInfo, QBluetoothHostInfo>> &discoveredDevices)
{ {
m_discoveredDevices = discoveredDevices; m_discoveredDevices = discoveredDevices;
} }
void BluetoothDiscoveryReplyImplementation::addDiscoveredDevice(const QBluetoothDeviceInfo &info, const QBluetoothHostInfo &hostInfo)
{
m_discoveredDevices.append(qMakePair<QBluetoothDeviceInfo, QBluetoothHostInfo>(info, hostInfo));
}
void BluetoothDiscoveryReplyImplementation::setFinished() void BluetoothDiscoveryReplyImplementation::setFinished()
{ {
m_finished = true; m_finished = true;
// Note: this makes sure the finished signal will be processed in the next event loop // Note: this makes sure the finished signal will be processed in the next event loop
QTimer::singleShot(0, this, &BluetoothDiscoveryReplyImplementation::finished); QTimer::singleShot(0, this, &BluetoothDiscoveryReplyImplementation::finished);
deleteLater();
} }
} }

View File

@ -33,6 +33,7 @@
#include <QObject> #include <QObject>
#include <QBluetoothDeviceInfo> #include <QBluetoothDeviceInfo>
#include <QBluetoothLocalDevice>
#include "hardware/bluetoothlowenergy/bluetoothdiscoveryreply.h" #include "hardware/bluetoothlowenergy/bluetoothdiscoveryreply.h"
@ -49,15 +50,16 @@ public:
bool isFinished() const override; bool isFinished() const override;
BluetoothDiscoveryReplyError error() const override; BluetoothDiscoveryReplyError error() const override;
QList<QBluetoothDeviceInfo> discoveredDevices() const override; QList<QPair<QBluetoothDeviceInfo, QBluetoothHostInfo> > discoveredDevices() const override;
private: private:
bool m_finished = false; bool m_finished = false;
BluetoothDiscoveryReplyError m_error = BluetoothDiscoveryReplyErrorNoError; BluetoothDiscoveryReplyError m_error = BluetoothDiscoveryReplyErrorNoError;
QList<QBluetoothDeviceInfo> m_discoveredDevices; QList<QPair<QBluetoothDeviceInfo, QBluetoothHostInfo>> m_discoveredDevices;
void setError(const BluetoothDiscoveryReplyError &error); void setError(const BluetoothDiscoveryReplyError &error);
void setDiscoveredDevices(const QList<QBluetoothDeviceInfo> &discoveredDevices); void setDiscoveredDevices(const QList<QPair<QBluetoothDeviceInfo, QBluetoothHostInfo> > &discoveredDevices);
void addDiscoveredDevice(const QBluetoothDeviceInfo &info, const QBluetoothHostInfo &hostInfo);
void setFinished(); void setFinished();
}; };

View File

@ -33,6 +33,20 @@
namespace nymeaserver { namespace nymeaserver {
BluetoothLowEnergyDeviceImplementation::BluetoothLowEnergyDeviceImplementation(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType, QObject *parent) :
BluetoothLowEnergyDevice(parent),
m_deviceInfo(deviceInfo)
{
m_controller = new QLowEnergyController(address(), this);
m_controller->setRemoteAddressType(addressType);
connect(m_controller, &QLowEnergyController::connected, this, &BluetoothLowEnergyDeviceImplementation::onConnected);
connect(m_controller, &QLowEnergyController::disconnected, this, &BluetoothLowEnergyDeviceImplementation::onDisconnected);
connect(m_controller, &QLowEnergyController::discoveryFinished, this, &BluetoothLowEnergyDeviceImplementation::onServiceDiscoveryFinished);
connect(m_controller, &QLowEnergyController::stateChanged, this, &BluetoothLowEnergyDeviceImplementation::onStateChanged);
connect(m_controller, SIGNAL(error(QLowEnergyController::Error)), this, SLOT(onDeviceError(QLowEnergyController::Error)));
}
QString BluetoothLowEnergyDeviceImplementation::name() const QString BluetoothLowEnergyDeviceImplementation::name() const
{ {
return m_deviceInfo.name(); return m_deviceInfo.name();
@ -48,21 +62,8 @@ QLowEnergyController *BluetoothLowEnergyDeviceImplementation::controller() const
return m_controller; return m_controller;
} }
BluetoothLowEnergyDeviceImplementation::BluetoothLowEnergyDeviceImplementation(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType, QObject *parent) :
BluetoothLowEnergyDevice(parent),
m_deviceInfo(deviceInfo)
{
m_controller = new QLowEnergyController(address(), this);
m_controller->setRemoteAddressType(addressType);
connect(m_controller, &QLowEnergyController::connected, this, &BluetoothLowEnergyDeviceImplementation::onConnected); void BluetoothLowEnergyDeviceImplementation::setConnected(bool connected)
connect(m_controller, &QLowEnergyController::disconnected, this, &BluetoothLowEnergyDeviceImplementation::onDisconnected);
connect(m_controller, &QLowEnergyController::discoveryFinished, this, &BluetoothLowEnergyDeviceImplementation::onServiceDiscoveryFinished);
connect(m_controller, &QLowEnergyController::stateChanged, this, &BluetoothLowEnergyDeviceImplementation::onStateChanged);
connect(m_controller, SIGNAL(error(QLowEnergyController::Error)), this, SLOT(onDeviceError(QLowEnergyController::Error)));
}
void BluetoothLowEnergyDeviceImplementation::setConnected(const bool &connected)
{ {
if (m_connected != connected) { if (m_connected != connected) {
m_connected = connected; m_connected = connected;
@ -71,19 +72,6 @@ void BluetoothLowEnergyDeviceImplementation::setConnected(const bool &connected)
} }
} }
void BluetoothLowEnergyDeviceImplementation::setEnabled(const bool &enabled)
{
m_enabled = enabled;
if (!m_enabled) {
m_controller->disconnectFromDevice();
} else {
if (m_autoConnecting) {
m_controller->connectToDevice();
}
}
}
void BluetoothLowEnergyDeviceImplementation::onConnected() void BluetoothLowEnergyDeviceImplementation::onConnected()
{ {
setConnected(true); setConnected(true);
@ -162,8 +150,7 @@ QList<QBluetoothUuid> BluetoothLowEnergyDeviceImplementation::serviceUuids() con
void BluetoothLowEnergyDeviceImplementation::onDeviceError(const QLowEnergyController::Error &error) void BluetoothLowEnergyDeviceImplementation::onDeviceError(const QLowEnergyController::Error &error)
{ {
if (connected()) qCWarning(dcBluetooth()) << "Device error:" << name() << address().toString() << ": " << error << m_controller->errorString();
qCWarning(dcBluetooth()) << "Device error:" << name() << address().toString() << ": " << error << m_controller->errorString();
emit errorOccurred(error); emit errorOccurred(error);
} }

View File

@ -75,10 +75,7 @@ private:
bool m_discovered = false; bool m_discovered = false;
bool m_enabled = true; bool m_enabled = true;
void setConnected(const bool &connected); void setConnected(bool connected);
// Methods called from BluetoothLowEnergyManager
void setEnabled(const bool &enabled);
private slots: private slots:
void onConnected(); void onConnected();

View File

@ -30,10 +30,13 @@
#include "bluetoothlowenergymanagerimplementation.h" #include "bluetoothlowenergymanagerimplementation.h"
#include "loggingcategories.h" #include "loggingcategories.h"
#include "nymeabluetoothagent.h"
#include "bluetoothpairingjobimplementation.h"
#include <QDBusConnection> #include <QDBusConnection>
#include <QDBusConnectionInterface> #include <QDBusConnectionInterface>
#include <QDBusReply> #include <QDBusReply>
#include <QDBusInterface>
namespace nymeaserver { namespace nymeaserver {
@ -54,85 +57,114 @@ BluetoothLowEnergyManagerImplementation::BluetoothLowEnergyManagerImplementation
return; return;
} }
// Check which bluetooth adapter are available m_agent = new NymeaBluetoothAgent(this);
QList<QBluetoothHostInfo> bluetoothAdapters = QBluetoothLocalDevice::allDevices();
if (bluetoothAdapters.isEmpty()) {
qCWarning(dcBluetooth()) << "No bluetooth adapter found. Resource not available.";
return;
}
// Create a scanner for each adapter foreach (const QBluetoothHostInfo &hostInfo, QBluetoothLocalDevice::allDevices()) {
foreach (const QBluetoothHostInfo &hostInfo, bluetoothAdapters) { qCDebug(dcBluetooth()) << "Enabling bluetooth adapter:" << hostInfo.name() << hostInfo.address().toString();
qCDebug(dcBluetooth()) << "Using adapter:" << hostInfo.name() << hostInfo.address().toString();
QBluetoothLocalDevice localDevice(hostInfo.address()); QBluetoothLocalDevice localDevice(hostInfo.address());
localDevice.powerOn(); localDevice.powerOn();
localDevice.setHostMode(QBluetoothLocalDevice::HostDiscoverable); localDevice.setHostMode(QBluetoothLocalDevice::HostDiscoverable);
QBluetoothDeviceDiscoveryAgent *discoveryAgent = new QBluetoothDeviceDiscoveryAgent(hostInfo.address(), this);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &BluetoothLowEnergyManagerImplementation::onDeviceDiscovered);
connect(discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)), this, SLOT(onDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error)));
m_bluetoothDiscoveryAgents.append(discoveryAgent);
} }
// Discovery timer, interval depends on discovery call
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, &BluetoothLowEnergyManagerImplementation::onDiscoveryTimeout);
// Reconnect timer
connect(m_reconnectTimer, &PluginTimer::timeout, this, &BluetoothLowEnergyManagerImplementation::onReconnectTimeout);
qCDebug(dcHardware()) << "-->" << name() << "created successfully."; qCDebug(dcHardware()) << "-->" << name() << "created successfully.";
m_available = true; m_available = true;
} }
BluetoothDiscoveryReply *BluetoothLowEnergyManagerImplementation::discoverDevices(int interval) BluetoothDiscoveryReply *BluetoothLowEnergyManagerImplementation::discoverDevices(int timeout)
{ {
// Create the reply for this discovery request BluetoothDiscoveryReplyImplementation *reply = new BluetoothDiscoveryReplyImplementation(this);
QPointer<BluetoothDiscoveryReplyImplementation> reply = new BluetoothDiscoveryReplyImplementation(this);
if (!available()) { if (!available()) {
qCWarning(dcBluetooth()) << "is not avilable."; qCWarning(dcBluetooth()) << "is not avilable.";
reply->setError(BluetoothDiscoveryReplyImplementation::BluetoothDiscoveryReplyErrorNotAvailable); reply->setError(BluetoothDiscoveryReplyImplementation::BluetoothDiscoveryReplyErrorNotAvailable);
reply->setFinished(); reply->setFinished();
return reply.data(); return reply;
} }
if (!enabled()) { if (!enabled()) {
qCWarning(dcBluetooth()) << "is not enabled."; qCWarning(dcBluetooth()) << "is not enabled.";
reply->setError(BluetoothDiscoveryReplyImplementation::BluetoothDiscoveryReplyErrorNotEnabled); reply->setError(BluetoothDiscoveryReplyImplementation::BluetoothDiscoveryReplyErrorNotEnabled);
reply->setFinished(); reply->setFinished();
return reply.data(); return reply;
} }
if (!m_currentReply.isNull()) { if (QBluetoothLocalDevice::allDevices().isEmpty()) {
qCWarning(dcBluetooth()) << "resource busy. There is already a discovery running."; qCWarning(dcBluetooth()) << "No Bluetooth adapter available.";
reply->setError(BluetoothDiscoveryReplyImplementation::BluetoothDiscoveryReplyErrorBusy); reply->setError(BluetoothDiscoveryReplyImplementation::BluetoothDiscoveryReplyErrorNotAvailable);
reply->setFinished(); reply->setFinished();
return reply.data(); return reply;
} }
m_currentReply = reply; // Create a scanner for each adapter
m_discoveredDevices.clear(); foreach (const QBluetoothHostInfo &hostInfo, QBluetoothLocalDevice::allDevices()) {
qCDebug(dcBluetooth()) << "Starting discovery on adapter:" << hostInfo.name() << hostInfo.address().toString();
QBluetoothLocalDevice localDevice(hostInfo.address());
localDevice.powerOn();
localDevice.setHostMode(QBluetoothLocalDevice::HostDiscoverable);
QBluetoothDeviceDiscoveryAgent *discoveryAgent = new QBluetoothDeviceDiscoveryAgent(hostInfo.address(), reply);
discoveryAgent->setLowEnergyDiscoveryTimeout(qMax(5000, qMin(30000, timeout)));
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, reply, [=](const QBluetoothDeviceInfo &info){
// Note: only show low energy devices
if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
qCDebug(dcBluetooth()) << "device discovered" << info.name() << info.address().toString();
reply->addDiscoveredDevice(info, hostInfo);
}
});
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, reply, [=](){
qCDebug(dcBluetooth()) << "Discovery finished";
reply->setFinished();
});
// connect(discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)), this, SLOT(onDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error)));
// Start discovery on all adapters
qCDebug(dcBluetooth()) << "Start bluetooth discovery";
foreach (QBluetoothDeviceDiscoveryAgent *discoveryAgent, m_bluetoothDiscoveryAgents) {
discoveryAgent->start(); discoveryAgent->start();
} }
// Prevent blocking the hardware resource from plugins return reply;
int finalInterval = interval; }
if (finalInterval > 30000) {
qCWarning(dcBluetooth()) << "Discovery interval out of range. Reset to 30 seconds."; BluetoothPairingJob *BluetoothLowEnergyManagerImplementation::pairDevice(const QBluetoothAddress &device, const QBluetoothAddress &adapter)
finalInterval = 30000; {
qCDebug(dcBluetooth()) << "pairDevice";
BluetoothPairingJobImplementation *job = new BluetoothPairingJobImplementation(m_agent, device, this);
QBluetoothLocalDevice *localDevice = nullptr;
if (adapter.isNull()) {
localDevice = new QBluetoothLocalDevice(this);
} else {
localDevice = new QBluetoothLocalDevice(adapter, this);
} }
if (finalInterval <= 0) { if (!localDevice->isValid()) {
qCWarning(dcBluetooth()) << "Discovery interval out of range. Reset to 5 seconds."; qCWarning(dcBluetooth()) << "Local device" << adapter.toString() << "is not valid";
finalInterval = 5000; job->finish(false);
return job;
}
localDevice->requestPairing(device, QBluetoothLocalDevice::AuthorizedPaired);
connect(localDevice, &QBluetoothLocalDevice::error, job, [=](QBluetoothLocalDevice::Error error){
qCDebug(dcBluetooth()) << "Pairing error" << error;
job->finish(false);
localDevice->deleteLater();
});
connect(localDevice, &QBluetoothLocalDevice::pairingFinished, job, [=](const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing){
qCDebug(dcBluetooth()) << "Pairing finished" << address.toString() << pairing;
job->finish(true);
localDevice->deleteLater();
});
return job;
}
void BluetoothLowEnergyManagerImplementation::unpairDevice(const QBluetoothAddress &device, const QBluetoothAddress &adapter)
{
QBluetoothLocalDevice *localDevice = nullptr;
if (adapter.isNull()) {
localDevice = new QBluetoothLocalDevice(this);
} else {
localDevice = new QBluetoothLocalDevice(adapter, this);
} }
m_timer->start(finalInterval); if (!localDevice->isValid()) {
return reply.data(); qCWarning(dcBluetooth()) << "Local device" << adapter.toString() << "is not valid";
return;
}
localDevice->requestPairing(device, QBluetoothLocalDevice::Unpaired);
} }
BluetoothLowEnergyDevice *BluetoothLowEnergyManagerImplementation::registerDevice(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType) BluetoothLowEnergyDevice *BluetoothLowEnergyManagerImplementation::registerDevice(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType)
@ -182,17 +214,17 @@ void BluetoothLowEnergyManagerImplementation::setEnabled(bool enabled)
return; return;
} }
bool success = false; foreach (QBluetoothHostInfo info, QBluetoothLocalDevice::allDevices()) {
if (enabled) { QBluetoothLocalDevice localDevice(info.address());
success = enable(); if (enabled) {
} else { localDevice.setHostMode(QBluetoothLocalDevice::HostConnectable);
success = disable(); } else {
localDevice.setHostMode(QBluetoothLocalDevice::HostPoweredOff);
}
} }
if (success) { m_enabled = enabled;
m_enabled = enabled; emit enabledChanged(m_enabled);
emit enabledChanged(m_enabled);
}
} }
void BluetoothLowEnergyManagerImplementation::onReconnectTimeout() void BluetoothLowEnergyManagerImplementation::onReconnectTimeout()
@ -205,72 +237,4 @@ void BluetoothLowEnergyManagerImplementation::onReconnectTimeout()
} }
} }
void BluetoothLowEnergyManagerImplementation::onDiscoveryTimeout()
{
// Stop discovery on all adapters
qCDebug(dcBluetooth()) << "Stop bluetooth discovery";
foreach (QBluetoothDeviceDiscoveryAgent *discoveryAgent, m_bluetoothDiscoveryAgents) {
discoveryAgent->stop();
}
qCDebug(dcBluetooth()) << "Discovery finished. Found" << m_discoveredDevices.count() << "bluetooth devices.";
if (m_currentReply.isNull()) {
qCWarning(dcBluetooth()) << "Reply does not exist any more. Please don't delete the reply before it has finished.";
m_currentReply.clear();
return;
}
m_currentReply->setError(BluetoothDiscoveryReply::BluetoothDiscoveryReplyErrorNoError);
m_currentReply->setDiscoveredDevices(m_discoveredDevices);
m_currentReply->setFinished();
m_currentReply.clear();
}
void BluetoothLowEnergyManagerImplementation::onDeviceDiscovered(const QBluetoothDeviceInfo &deviceInfo)
{
// Add the device to the list if not already added
bool alreadyAdded = false;
foreach (const QBluetoothDeviceInfo &device, m_discoveredDevices) {
if (device.address() == deviceInfo.address()) {
alreadyAdded = true;
}
}
// Note: only show low energy devices
if (!alreadyAdded && deviceInfo.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
qCDebug(dcBluetooth()) << "device discovered" << deviceInfo.name() << deviceInfo.address().toString();
m_discoveredDevices.append(deviceInfo);
}
}
void BluetoothLowEnergyManagerImplementation::onDiscoveryError(const QBluetoothDeviceDiscoveryAgent::Error &error)
{
QBluetoothDeviceDiscoveryAgent *discoveryAgent = static_cast<QBluetoothDeviceDiscoveryAgent *>(sender());
qCWarning(dcBluetooth()) << "Discovery error:" << error << discoveryAgent->errorString();
}
bool BluetoothLowEnergyManagerImplementation::enable()
{
if (!available()) {
qCWarning(dcBluetooth()) << "Bluetooth hardware not available. Cannot enable Hardware resource";
return false;
}
foreach (QPointer<BluetoothLowEnergyDeviceImplementation> bluetoothDevice, m_devices) {
bluetoothDevice->setEnabled(true);
}
qCDebug(dcBluetooth()) << "Hardware resource enabled.";
return true;
}
bool BluetoothLowEnergyManagerImplementation::disable()
{
foreach (QPointer<BluetoothLowEnergyDeviceImplementation> bluetoothDevice, m_devices) {
bluetoothDevice->setEnabled(false);
}
qCDebug(dcBluetooth()) << "Hardware resource disabled.";
return true;
}
} }

View File

@ -46,6 +46,8 @@
namespace nymeaserver { namespace nymeaserver {
class NymeaBluetoothAgent;
class BluetoothLowEnergyManagerImplementation : public BluetoothLowEnergyManager class BluetoothLowEnergyManagerImplementation : public BluetoothLowEnergyManager
{ {
Q_OBJECT Q_OBJECT
@ -55,9 +57,11 @@ class BluetoothLowEnergyManagerImplementation : public BluetoothLowEnergyManager
public: public:
explicit BluetoothLowEnergyManagerImplementation(PluginTimer *reconnectTimer, QObject *parent = nullptr); explicit BluetoothLowEnergyManagerImplementation(PluginTimer *reconnectTimer, QObject *parent = nullptr);
BluetoothDiscoveryReply *discoverDevices(int interval = 5000) override; BluetoothDiscoveryReply *discoverDevices(int timeout = 5000) override;
// Bluetooth device registration methods // Bluetooth device registration methods
BluetoothPairingJob *pairDevice(const QBluetoothAddress &device, const QBluetoothAddress &adapter) override;
void unpairDevice(const QBluetoothAddress &device, const QBluetoothAddress &adapter) override;
BluetoothLowEnergyDevice *registerDevice(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType = QLowEnergyController::RandomAddress) override; BluetoothLowEnergyDevice *registerDevice(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType = QLowEnergyController::RandomAddress) override;
void unregisterDevice(BluetoothLowEnergyDevice *bluetoothDevice) override; void unregisterDevice(BluetoothLowEnergyDevice *bluetoothDevice) override;
@ -67,27 +71,18 @@ public:
protected: protected:
void setEnabled(bool enabled) override; void setEnabled(bool enabled) override;
private slots:
void onReconnectTimeout();
private: private:
PluginTimer *m_reconnectTimer = nullptr; PluginTimer *m_reconnectTimer = nullptr;
QTimer *m_timer = nullptr;
QList<QPointer<BluetoothLowEnergyDeviceImplementation>> m_devices; QList<QPointer<BluetoothLowEnergyDeviceImplementation>> m_devices;
bool m_available = false; bool m_available = false;
bool m_enabled = false; bool m_enabled = false;
NymeaBluetoothAgent *m_agent = nullptr;
QList<QBluetoothDeviceDiscoveryAgent *> m_bluetoothDiscoveryAgents;
QList<QBluetoothDeviceInfo> m_discoveredDevices;
QPointer<BluetoothDiscoveryReplyImplementation> m_currentReply;
private slots:
void onReconnectTimeout();
void onDeviceDiscovered(const QBluetoothDeviceInfo &deviceInfo);
void onDiscoveryError(const QBluetoothDeviceDiscoveryAgent::Error &error);
void onDiscoveryTimeout();
public slots:
bool enable();
bool disable();
}; };

View File

@ -0,0 +1,52 @@
#include "bluetoothpairingjobimplementation.h"
#include "nymeabluetoothagent.h"
namespace nymeaserver
{
BluetoothPairingJobImplementation::BluetoothPairingJobImplementation(NymeaBluetoothAgent *agent, const QBluetoothAddress &address, QObject *parent)
: BluetoothPairingJob{address, parent},
m_agent{agent},
m_address{address}
{
connect(m_agent, &NymeaBluetoothAgent::passKeyRequested, this, [address, this](const QBluetoothAddress &addr){
if (address != addr) {
// Not for us...
return;
}
emit passKeyRequested();
});
connect(m_agent, &NymeaBluetoothAgent::displayPinCode, this, [address, this](const QBluetoothAddress &addr, const QString &pinCode){
if (address != addr) {
// Not for us...
return;
}
emit displayPinCode(pinCode);
});
}
bool BluetoothPairingJobImplementation::isFinished() const
{
return m_finished;
}
bool BluetoothPairingJobImplementation::success() const
{
return m_success;
}
void BluetoothPairingJobImplementation::passKeyEntered(const QString passKey)
{
m_agent->passKeyEntered(m_address, passKey);
}
void BluetoothPairingJobImplementation::finish(bool success)
{
m_finished = true;
m_success = success;
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(bool, success));
}
}

View File

@ -0,0 +1,38 @@
#ifndef BLUETOOTHPAIRINGJOBIMPLEMENTATION_H
#define BLUETOOTHPAIRINGJOBIMPLEMENTATION_H
#include "hardware/bluetoothlowenergy/bluetoothlowenergymanager.h"
#include <QObject>
namespace nymeaserver
{
class NymeaBluetoothAgent;
class BluetoothLowEnergyManagerImplementation;
class BluetoothPairingJobImplementation : public BluetoothPairingJob
{
Q_OBJECT
public:
explicit BluetoothPairingJobImplementation(NymeaBluetoothAgent *agent, const QBluetoothAddress &address, QObject *parent = nullptr);
bool isFinished() const override;
bool success() const override;
void passKeyEntered(const QString passKey) override;
private:
friend BluetoothLowEnergyManagerImplementation;
void finish(bool success);
private:
bool m_finished = false;
bool m_success = false;
NymeaBluetoothAgent *m_agent = nullptr;
QBluetoothAddress m_address;
};
}
#endif // BLUETOOTHPAIRINGJOBIMPLEMENTATION_H

View File

@ -0,0 +1,146 @@
#include "nymeabluetoothagent.h"
#include <QDBusConnection>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcBluetooth)
namespace nymeaserver
{
NymeaBluetoothAgentAdapter::NymeaBluetoothAgentAdapter(NymeaBluetoothAgent *agent, QObject *parent):
QObject{parent},
m_agent{agent}
{
}
QString NymeaBluetoothAgentAdapter::RequestPinCode(const QDBusObjectPath &device, const QDBusMessage &message)
{
qCDebug(dcBluetooth) << "RequestPinCode" << device.path() << message.arguments();
message.setDelayedReply(true);
m_agent->onRequestPassKey(device, message);
return 0;
}
void NymeaBluetoothAgentAdapter::DisplayPinCode(const QDBusObjectPath &device, const QString &pinCode)
{
qCDebug(dcBluetooth) << "DisplayPinCode" << device.path() << pinCode;
m_agent->onDisplayPinCode(device, pinCode);
}
quint32 NymeaBluetoothAgentAdapter::RequestPasskey(const QDBusObjectPath &device, const QDBusMessage &message)
{
qCDebug(dcBluetooth) << "RequestPasskey" << device.path() << message.arguments();
message.setDelayedReply(true);
m_agent->onRequestPassKey(device, message);
return 0;
}
void NymeaBluetoothAgentAdapter::DisplayPasskey(const QDBusObjectPath &device, quint32 passKey, quint16 entered)
{
qCDebug(dcBluetooth) << "DisplayPasskey" << device.path() << passKey << entered;
// Not using "entered" value which would update whenever the user enters a (wrong) pin on the other end...
m_agent->onDisplayPinCode(device, QString("%1").arg(passKey, 6, QChar('0')));
}
void NymeaBluetoothAgentAdapter::RequestConfirmation(const QDBusObjectPath &device, quint32 passKey, const QDBusMessage &message)
{
qCDebug(dcBluetooth) << "RequestConfirmation" << device.path() << passKey << message.arguments();
// TODO: Not implemented
qCWarning(dcBluetooth()) << "Request confirmation pairing mechanism is not implemented.";
}
void NymeaBluetoothAgentAdapter::RequestAuthorization(const QDBusObjectPath &device, const QDBusMessage &message)
{
qCDebug(dcBluetooth) << "RequestAuthorization" << device.path() << message.arguments();
// TODO: Not implemented
qCWarning(dcBluetooth()) << "RequestAuthorization mechanism is not implemented.";
}
void NymeaBluetoothAgentAdapter::AuthorizeService(const QDBusObjectPath &device, const QString &uuid, const QDBusMessage &message)
{
qCDebug(dcBluetooth) << "AuthorizeService" << device.path() << uuid << message.arguments();
// TODO: Not implemented
qCWarning(dcBluetooth()) << "AuthorizeService mechanism is not implemented.";
}
void NymeaBluetoothAgentAdapter::Cancel()
{
qCDebug(dcBluetooth()) << "Cancel called on bluetooth agent";
}
void NymeaBluetoothAgentAdapter::Release()
{
qCDebug(dcBluetooth()) << "Release called on bluetooth agent";
}
NymeaBluetoothAgent::NymeaBluetoothAgent(QObject *parent)
: QObject{parent}
{
m_adapter = new NymeaBluetoothAgentAdapter(this);
bool success = QDBusConnection::systemBus().registerObject("/nymea/bluetoothagent", m_adapter, QDBusConnection::ExportScriptableContents);
qCInfo(dcBluetooth) << "Registered Bluetooth pairing agent" << success;
QDBusMessage message = QDBusMessage::createMethodCall("org.bluez", "/org/bluez", "org.bluez.AgentManager1", "RegisterAgent");
message << QVariant::fromValue(QDBusObjectPath("/nymea/bluetoothagent"));
message << "KeyboardDisplay";
QDBusMessage registerReply = QDBusConnection::systemBus().call(message);
if (!registerReply.errorName().isEmpty()) {
qCWarning(dcBluetooth()) << "Error registering pairing agent:" << registerReply.errorMessage();
} else {
qCDebug(dcBluetooth()) << "Pairing agent registered.";
}
}
void NymeaBluetoothAgent::passKeyEntered(const QBluetoothAddress &address, const QString passKey)
{
if (!m_pendingPairings.contains(address.toString())) {
qCWarning(dcBluetooth()) << "No ongoing pairing process for" << address.toString();
return;
}
qCInfo(dcBluetooth()) << "Providing passkey to" << address.toString() << passKey;
QDBusMessage message = m_pendingPairings.take(address.toString());
message << static_cast<quint32>(passKey.toUInt());
QDBusConnection::systemBus().send(message);
}
QBluetoothAddress NymeaBluetoothAgent::deviceForPath(const QDBusObjectPath &path)
{
// qdbus --system org.bluez /org/bluez/hci0/dev_00_1A_22_0B_12_EB org.freedesktop.DBus.Properties.Get org.bluez.Device1 Address
QDBusMessage message = QDBusMessage::createMethodCall("org.bluez", path.path(), "org.freedesktop.DBus.Properties", "Get");
message << "org.bluez.Device1" << "Address";
QDBusMessage reply = QDBusConnection::systemBus().call(message);
if (!reply.errorName().isEmpty()) {
qCWarning(dcBluetooth()) << "Error reading Address property for" << path.path() << reply.errorMessage();
return QBluetoothAddress();
}
if (reply.arguments().count() != 1) {
qCWarning(dcBluetooth) << "Read property reply received an unexpected argument count";
return QBluetoothAddress();
}
return QBluetoothAddress(reply.arguments().at(0).value<QDBusVariant>().variant().toString());
}
void NymeaBluetoothAgent::onRequestPassKey(const QDBusObjectPath &path, const QDBusMessage &message)
{
QBluetoothAddress address = deviceForPath(path);
qCDebug(dcBluetooth()) << "RequestPassKey" << path.path() << address;
m_pendingPairings[address.toString()] = message.createReply();
emit passKeyRequested(address);
}
void NymeaBluetoothAgent::onDisplayPinCode(const QDBusObjectPath &path, const QString &pinCode)
{
QBluetoothAddress address = deviceForPath(path);
qCDebug(dcBluetooth()) << "RequestPassKey" << path.path() << address;
emit displayPinCode(address, pinCode);
}
}

View File

@ -0,0 +1,64 @@
#ifndef NYMEABLUETOOTHAGENT_H
#define NYMEABLUETOOTHAGENT_H
#include <QObject>
#include <QDBusObjectPath>
#include <QDBusMessage>
#include <QBluetoothAddress>
namespace nymeaserver
{
class NymeaBluetoothAgent;
class NymeaBluetoothAgentAdapter: public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.bluez.Agent1")
public:
explicit NymeaBluetoothAgentAdapter(NymeaBluetoothAgent *agent, QObject *parent = nullptr);
public slots:
Q_SCRIPTABLE QString RequestPinCode(const QDBusObjectPath &device, const QDBusMessage &message);
Q_SCRIPTABLE void DisplayPinCode(const QDBusObjectPath &device, const QString &pinCode);
Q_SCRIPTABLE quint32 RequestPasskey(const QDBusObjectPath &device, const QDBusMessage &message);
Q_SCRIPTABLE void DisplayPasskey(const QDBusObjectPath &device, quint32 passKey, quint16 entered);
Q_SCRIPTABLE void RequestConfirmation(const QDBusObjectPath &device, quint32 passKey, const QDBusMessage &message);
Q_SCRIPTABLE void RequestAuthorization(const QDBusObjectPath &device, const QDBusMessage &message);
Q_SCRIPTABLE void AuthorizeService(const QDBusObjectPath &device, const QString &uuid, const QDBusMessage &message);
Q_SCRIPTABLE void Cancel();
Q_SCRIPTABLE void Release();
private:
NymeaBluetoothAgent *m_agent = nullptr;
};
class NymeaBluetoothAgent : public QObject
{
Q_OBJECT
public:
explicit NymeaBluetoothAgent(QObject *parent = nullptr);
void passKeyEntered(const QBluetoothAddress &address, const QString passKey);
signals:
void passKeyRequested(const QBluetoothAddress &address);
void displayPinCode(const QBluetoothAddress &address, const QString &pinCode);
private:
friend class NymeaBluetoothAgentAdapter;
QBluetoothAddress deviceForPath(const QDBusObjectPath &path);
void onRequestPassKey(const QDBusObjectPath &path, const QDBusMessage &message);
void onDisplayPinCode(const QDBusObjectPath &path, const QString &pinCode);
NymeaBluetoothAgentAdapter *m_adapter = nullptr;
QHash<QString, QDBusMessage> m_pendingPairings;
};
}
#endif // NYMEABLUETOOTHAGENT_H

View File

@ -58,6 +58,8 @@ RESOURCES += $$top_srcdir/icons.qrc \
$$top_srcdir/data/debug-interface/debug-interface.qrc $$top_srcdir/data/debug-interface/debug-interface.qrc
HEADERS += nymeacore.h \ HEADERS += nymeacore.h \
hardware/bluetoothlowenergy/bluetoothpairingjobimplementation.h \
hardware/bluetoothlowenergy/nymeabluetoothagent.h \
hardware/network/macaddressdatabasereplyimpl.h \ hardware/network/macaddressdatabasereplyimpl.h \
hardware/serialport/serialportmonitor.h \ hardware/serialport/serialportmonitor.h \
hardware/zwave/zwavehardwareresourceimplementation.h \ hardware/zwave/zwavehardwareresourceimplementation.h \
@ -165,6 +167,8 @@ HEADERS += nymeacore.h \
SOURCES += nymeacore.cpp \ SOURCES += nymeacore.cpp \
hardware/bluetoothlowenergy/bluetoothpairingjobimplementation.cpp \
hardware/bluetoothlowenergy/nymeabluetoothagent.cpp \
hardware/network/macaddressdatabasereplyimpl.cpp \ hardware/network/macaddressdatabasereplyimpl.cpp \
hardware/serialport/serialportmonitor.cpp \ hardware/serialport/serialportmonitor.cpp \
hardware/zwave/zwavehardwareresourceimplementation.cpp \ hardware/zwave/zwavehardwareresourceimplementation.cpp \

View File

@ -33,6 +33,7 @@
#include <QObject> #include <QObject>
#include <QBluetoothDeviceInfo> #include <QBluetoothDeviceInfo>
#include <QBluetoothHostInfo>
#include "libnymea.h" #include "libnymea.h"
@ -54,7 +55,7 @@ public:
virtual bool isFinished() const = 0; virtual bool isFinished() const = 0;
virtual BluetoothDiscoveryReplyError error() const = 0; virtual BluetoothDiscoveryReplyError error() const = 0;
virtual QList<QBluetoothDeviceInfo> discoveredDevices() const = 0; virtual QList<QPair<QBluetoothDeviceInfo, QBluetoothHostInfo>> discoveredDevices() const = 0;
signals: signals:
void finished(); void finished();

View File

@ -66,6 +66,7 @@ BluetoothLowEnergyManager::BluetoothLowEnergyManager(QObject *parent) :
{ {
} }
/*! This method enables / disables this hardwareresource for all plugins. This method is available on the D-Bus. This can be useful if a Bluetooth LE server /*! This method enables / disables this hardwareresource for all plugins. This method is available on the D-Bus. This can be useful if a Bluetooth LE server
needs access to the hardware. By disabling the bluetooth support, nymea will not allow to use the hardware until it gets reenabled. needs access to the hardware. By disabling the bluetooth support, nymea will not allow to use the hardware until it gets reenabled.
*/ */
@ -73,3 +74,16 @@ void BluetoothLowEnergyManager::EnableBluetooth(bool enabled)
{ {
setEnabled(enabled); setEnabled(enabled);
} }
BluetoothPairingJob::BluetoothPairingJob(const QBluetoothAddress &address, QObject *parent):
QObject(parent),
m_address(address)
{
}
QBluetoothAddress BluetoothPairingJob::address() const
{
return m_address;
}

View File

@ -45,6 +45,31 @@
#include "libnymea.h" #include "libnymea.h"
class NymeaBluetoothAgent;
class LIBNYMEA_EXPORT BluetoothPairingJob: public QObject
{
Q_OBJECT
public:
explicit BluetoothPairingJob(const QBluetoothAddress &address, QObject *parent = nullptr);
virtual ~BluetoothPairingJob() = default;
QBluetoothAddress address() const;
virtual bool isFinished() const = 0;
virtual bool success() const = 0;
virtual void passKeyEntered(const QString passKey) = 0;
signals:
void finished(bool success);
void passKeyRequested();
void displayPinCode(const QString &pinCode);
private:
QBluetoothAddress m_address;
};
class LIBNYMEA_EXPORT BluetoothLowEnergyManager : public HardwareResource class LIBNYMEA_EXPORT BluetoothLowEnergyManager : public HardwareResource
{ {
Q_OBJECT Q_OBJECT
@ -53,14 +78,18 @@ public:
explicit BluetoothLowEnergyManager(QObject *parent = nullptr); explicit BluetoothLowEnergyManager(QObject *parent = nullptr);
virtual ~BluetoothLowEnergyManager() = default; virtual ~BluetoothLowEnergyManager() = default;
virtual BluetoothDiscoveryReply *discoverDevices(int interval = 5000) = 0; virtual BluetoothDiscoveryReply *discoverDevices(int interval = 5000) = 0;
// Bluetooth device registration methods // Bluetooth device registration methods
virtual BluetoothPairingJob *pairDevice(const QBluetoothAddress &device, const QBluetoothAddress &adapter) = 0;
virtual void unpairDevice(const QBluetoothAddress &device, const QBluetoothAddress &adapter) = 0;
virtual BluetoothLowEnergyDevice *registerDevice(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType = QLowEnergyController::RandomAddress) = 0; virtual BluetoothLowEnergyDevice *registerDevice(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType = QLowEnergyController::RandomAddress) = 0;
virtual void unregisterDevice(BluetoothLowEnergyDevice *bluetoothDevice) = 0; virtual void unregisterDevice(BluetoothLowEnergyDevice *bluetoothDevice) = 0;
public slots: public slots:
Q_SCRIPTABLE void EnableBluetooth(bool enabled); Q_SCRIPTABLE void EnableBluetooth(bool enabled);
}; };
#endif // BLUETOOTHLOWENERGYMANAGER_H #endif // BLUETOOTHLOWENERGYMANAGER_H

View File

@ -7,8 +7,8 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"
JSON_PROTOCOL_VERSION_MAJOR=8 JSON_PROTOCOL_VERSION_MAJOR=8
JSON_PROTOCOL_VERSION_MINOR=1 JSON_PROTOCOL_VERSION_MINOR=1
JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
LIBNYMEA_API_VERSION_MAJOR=7 LIBNYMEA_API_VERSION_MAJOR=8
LIBNYMEA_API_VERSION_MINOR=4 LIBNYMEA_API_VERSION_MINOR=0
LIBNYMEA_API_VERSION_PATCH=0 LIBNYMEA_API_VERSION_PATCH=0
LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}" LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}"

View File

@ -14,7 +14,7 @@
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QObject> #include <QObject>
extern "C" const QString libnymea_api_version() { return QString("7.4.0");} extern "C" const QString libnymea_api_version() { return QString("8.0.0");}
Q_DECLARE_LOGGING_CATEGORY(dcMock) Q_DECLARE_LOGGING_CATEGORY(dcMock)
Q_LOGGING_CATEGORY(dcMock, "Mock") Q_LOGGING_CATEGORY(dcMock, "Mock")