diff --git a/debian/control b/debian/control index 1726cb72..3cae4a8b 100644 --- a/debian/control +++ b/debian/control @@ -27,6 +27,7 @@ Architecture: any Depends: libqt5network5, libqt5gui5, libqt5sql5, + libqt5bluetooth5, libguh1 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} diff --git a/guh.pri b/guh.pri index 97a8856f..1bafb986 100644 --- a/guh.pri +++ b/guh.pri @@ -14,6 +14,12 @@ QT+= network QMAKE_CXXFLAGS += -Werror CONFIG += c++11 +# Check for Bluetoot LE support (Qt >= 5.4.0) +!contains(QT_VERSION, ^5\\.[0-3]\\..*) { + QT += bluetooth + DEFINES += BLUETOOTH_LE +} + # Enable coverage option coverage { QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage -O0 diff --git a/guh.pro b/guh.pro index 901a037f..5c2f0de6 100644 --- a/guh.pro +++ b/guh.pro @@ -12,6 +12,13 @@ SUBDIRS += libguh server plugins message("Building guh tests disabled") } +# Bluetooth LE support +contains(DEFINES, BLUETOOTH_LE) { + message("Bluetooth LE available (Qt $${QT_VERSION}).") +} else { + message("Bluetooth LE not available (Qt $${QT_VERSION}).") +} + server.depends = libguh plugins plugins.depends = libguh tests.depends = libguh diff --git a/libguh/bluetooth/bluetoothscanner.cpp b/libguh/bluetooth/bluetoothscanner.cpp new file mode 100644 index 00000000..a1ef27d3 --- /dev/null +++ b/libguh/bluetooth/bluetoothscanner.cpp @@ -0,0 +1,117 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This file is part of guh. * + * * + * Guh 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, version 2 of the License. * + * * + * Guh 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 guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "bluetoothscanner.h" + +BluetoothScanner::BluetoothScanner(QObject *parent) : + QObject(parent) +{ + m_timer = new QTimer(this); + m_timer->setSingleShot(true); + m_timer->setInterval(10000); + connect(m_timer, &QTimer::timeout, this, &BluetoothScanner::discoveryTimeout); +} + +bool BluetoothScanner::isAvailable() +{ + //Using default Bluetooth adapter + QBluetoothLocalDevice localDevice; + + // Check if Bluetooth is available on this device + if (!localDevice.isValid()) { + qWarning() << "ERROR: no bluetooth device found."; + m_available = false; + return false; + } + + // Turn Bluetooth on + localDevice.powerOn(); + + // Make it visible to others + localDevice.setHostMode(QBluetoothLocalDevice::HostDiscoverable); + + // Get connected devices + QList remotes = localDevice.allDevices(); + if (remotes.isEmpty()) { + qWarning() << "ERROR: no bluetooth host info found."; + m_available = false; + return false; + } + + QBluetoothHostInfo hostInfo = remotes.first(); + + // Create a discovery agent and connect to its signals + m_discoveryAgent = new QBluetoothDeviceDiscoveryAgent(hostInfo.address(), this); + + connect(m_discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &BluetoothScanner::deviceDiscovered); + connect(m_discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)), this, SLOT(onError(QBluetoothDeviceDiscoveryAgent::Error))); + + qDebug() << "--> Bluetooth discovery created successfully."; + m_available = true; + return true; +} + +bool BluetoothScanner::isRunning() +{ + return m_discoveryAgent->isActive(); +} + +bool BluetoothScanner::discover(const PluginId &pluginId) +{ + if (m_available && !m_discoveryAgent->isActive()) { + m_pluginId = pluginId; + m_deviceInfos.clear(); + m_discoveryAgent->start(); + m_timer->start(); + qDebug() << "Bluetooth discovery started..."; + return true; + } + return false; +} + +void BluetoothScanner::deviceDiscovered(const QBluetoothDeviceInfo &device) +{ + // check if this is LE device + bool bluetoothLE = false; + if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) { + bluetoothLE = true; + } + qDebug() << "Bluetooth device discovered:" << device.name() << device.address() << "LE:" << bluetoothLE; + + m_deviceInfos.append(device); +} + +void BluetoothScanner::onError(QBluetoothDeviceDiscoveryAgent::Error error) +{ + Q_UNUSED(error); + m_available = false; + if (m_timer->isActive()) { + m_timer->stop(); + } + if (isRunning()) { + m_discoveryAgent->stop(); + } + qWarning() << "ERROR: Bluetooth discovery:" << m_discoveryAgent->errorString(); +} + +void BluetoothScanner::discoveryTimeout() +{ + qDebug() << "Bluetooth discovery finished."; + m_discoveryAgent->stop(); + emit bluetoothDiscoveryFinished(m_pluginId, m_deviceInfos); +} diff --git a/libguh/bluetooth/bluetoothscanner.h b/libguh/bluetooth/bluetoothscanner.h new file mode 100644 index 00000000..1f081004 --- /dev/null +++ b/libguh/bluetooth/bluetoothscanner.h @@ -0,0 +1,59 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This file is part of guh. * + * * + * Guh 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, version 2 of the License. * + * * + * Guh 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 guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef BLUETOOTHLE_H +#define BLUETOOTHLE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "typeutils.h" + +class BluetoothScanner : public QObject +{ + Q_OBJECT +public: + explicit BluetoothScanner(QObject *parent = 0); + bool isAvailable(); + bool isRunning(); + bool discover(const PluginId &pluginId); + +private: + QBluetoothDeviceDiscoveryAgent *m_discoveryAgent; + QList m_deviceInfos; + QTimer *m_timer; + bool m_available; + PluginId m_pluginId; + +signals: + void bluetoothDiscoveryFinished(const PluginId &pluginId, const QList &deviceInfos); + +private slots: + void deviceDiscovered(const QBluetoothDeviceInfo &device); + void onError(QBluetoothDeviceDiscoveryAgent::Error error); + void discoveryTimeout(); +}; + +#endif // BLUETOOTHLE_H diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp index 125aed50..043be2e7 100644 --- a/libguh/devicemanager.cpp +++ b/libguh/devicemanager.cpp @@ -213,6 +213,22 @@ DeviceManager::DeviceManager(QObject *parent) : m_networkManager = new NetworkManager(this); connect(m_networkManager, &NetworkManager::replyReady, this, &DeviceManager::replyReady); + + // UPnP discovery + m_upnpDiscovery = new UpnpDiscovery(this); + connect(m_upnpDiscovery, &UpnpDiscovery::discoveryFinished, this, &DeviceManager::upnpDiscoveryFinished); + connect(m_upnpDiscovery, &UpnpDiscovery::upnpNotify, this, &DeviceManager::upnpNotifyReceived); + + // Bluetooth LE + #ifdef BLUETOOTH_LE + m_bluetoothScanner = new BluetoothScanner(this); + if (!m_bluetoothScanner->isAvailable()) { + delete m_bluetoothScanner; + m_bluetoothScanner = 0; + } else { + connect(m_bluetoothScanner, &BluetoothScanner::bluetoothDiscoveryFinished, this, &DeviceManager::bluetoothDiscoveryFinished); + } + #endif } /*! Destructor of the DeviceManager. Each loaded \l{DevicePlugin} will be deleted. */ @@ -1168,6 +1184,17 @@ void DeviceManager::upnpNotifyReceived(const QByteArray ¬ifyData) } } +#ifdef BLUETOOTH_LE +void DeviceManager::bluetoothDiscoveryFinished(const PluginId &pluginId, const QList &deviceInfos) +{ + foreach (DevicePlugin *devicePlugin, m_devicePlugins) { + if (devicePlugin->requiredHardware().testFlag(HardwareResourceBluetoothLE) && devicePlugin->pluginId() == pluginId) { + devicePlugin->bluetoothDiscoveryFinished(deviceInfos); + } + } +} +#endif + void DeviceManager::timerEvent() { foreach (DevicePlugin *plugin, m_pluginTimerUsers) { diff --git a/libguh/devicemanager.h b/libguh/devicemanager.h index ec405e3d..3dbe44f1 100644 --- a/libguh/devicemanager.h +++ b/libguh/devicemanager.h @@ -32,6 +32,10 @@ #include "network/networkmanager.h" +#ifdef BLUETOOTH_LE +#include "bluetooth/bluetoothscanner.h" +#endif + #include #include #include @@ -51,7 +55,9 @@ public: HardwareResourceRadio433 = 0x01, HardwareResourceRadio868 = 0x02, HardwareResourceTimer = 0x04, - HardwareResourceNetworkManager = 0x08 + HardwareResourceNetworkManager = 0x08, + HardwareResourceUpnpDisovery = 0x16, + HardwareResourceBluetoothLE = 0x32 }; Q_DECLARE_FLAGS(HardwareResources, HardwareResource) @@ -146,6 +152,14 @@ private slots: void radio433SignalReceived(QList rawData); void replyReady(const PluginId &pluginId, QNetworkReply *reply); + + void upnpDiscoveryFinished(const QList &deviceDescriptorList, const PluginId &pluginId); + void upnpNotifyReceived(const QByteArray ¬ifyData); + + #ifdef BLUETOOTH_LE + void bluetoothDiscoveryFinished(const PluginId &pluginId, const QList &deviceInfos); + #endif + void timerEvent(); private: @@ -172,6 +186,10 @@ private: QList m_pluginTimerUsers; NetworkManager *m_networkManager; + #ifdef BLUETOOTH_LE + BluetoothScanner *m_bluetoothScanner; + #endif + QHash > m_pairingsJustAdd; QHash > m_pairingsDiscovery; diff --git a/libguh/libguh.pro b/libguh/libguh.pro index 44a12cc7..46c42db2 100644 --- a/libguh/libguh.pro +++ b/libguh/libguh.pro @@ -8,6 +8,11 @@ QT += network target.path = /usr/lib INSTALLS += target +contains(DEFINES, BLUETOOTH_LE) { + SOURCES += bluetooth/bluetoothscanner.cpp + HEADERS += bluetooth/bluetoothscanner.h +} + SOURCES += plugin/device.cpp \ plugin/deviceclass.cpp \ plugin/deviceplugin.cpp \ diff --git a/libguh/plugin/deviceplugin.cpp b/libguh/plugin/deviceplugin.cpp index d5843bf5..dd030733 100644 --- a/libguh/plugin/deviceplugin.cpp +++ b/libguh/plugin/deviceplugin.cpp @@ -649,6 +649,30 @@ QNetworkReply *DevicePlugin::networkManagerPut(const QNetworkRequest &request, c return nullptr; } +/*! + Starts a SSDP search for a certain \a searchTarget (ST). Certain UPnP devices need a special ST (i.e. "udap:rootservice" + for LG Smart Tv's), otherwise they will not respond on the SSDP search. Each HTTP request to this device needs sometimes + also a special \a userAgent, which will be written into the HTTP header. + + \sa DevicePlugin::requiredHardware(), DevicePlugin::upnpDiscoveryFinished() + */ +void DevicePlugin::upnpDiscover(QString searchTarget, QString userAgent) +{ + if(requiredHardware().testFlag(DeviceManager::HardwareResourceUpnpDisovery)){ + deviceManager()->m_upnpDiscovery->discoverDevices(searchTarget, userAgent, pluginId()); + } +} + +#ifdef BLUETOOTH_LE +bool DevicePlugin::discoverBluetooth() +{ + if(requiredHardware().testFlag(DeviceManager::HardwareResourceBluetoothLE)){ + return deviceManager()->m_bluetoothScanner->discover(pluginId()); + } + return false; +} +#endif + QStringList DevicePlugin::verifyFields(const QStringList &fields, const QJsonObject &value) const { QStringList ret; diff --git a/libguh/plugin/deviceplugin.h b/libguh/plugin/deviceplugin.h index 2535a16b..624d0e30 100644 --- a/libguh/plugin/deviceplugin.h +++ b/libguh/plugin/deviceplugin.h @@ -31,6 +31,10 @@ #include "types/vendor.h" #include "types/param.h" +#ifdef BLUETOOTH_LE +#include +#endif + #include #include @@ -71,6 +75,10 @@ public: virtual void networkManagerReplyReady(QNetworkReply *reply) {Q_UNUSED(reply)} + #ifdef BLUETOOTH_LE + virtual void bluetoothDiscoveryFinished(const QList &deviceInfos) {Q_UNUSED(deviceInfos)} + #endif + // Configuration virtual QList configurationDescription() const; DeviceManager::DeviceError setConfiguration(const ParamList &configuration); @@ -101,6 +109,11 @@ protected: // Radio 433 bool transmitData(int delay, QList rawData); + // Bluetooth LE discovery + #ifdef BLUETOOTH_LE + bool discoverBluetooth(); + #endif + // Network manager QNetworkReply *networkManagerGet(const QNetworkRequest &request); QNetworkReply *networkManagerPost(const QNetworkRequest &request, const QByteArray &data);