From 6ea0e0c4a9230084eb3552b0d41f71e4b2059936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 31 Mar 2015 10:31:46 +0200 Subject: [PATCH] first working version of avea example plugin for bluetoot le --- debian/guh-plugins.install | 1 + doc/bluetooth.qdoc | 7 + doc/guh.qdoc | 10 +- doc/plugins.qdoc | 2 + libguh/bluetooth/bluetoothlowenergydevice.cpp | 90 +-- libguh/bluetooth/bluetoothlowenergydevice.h | 21 +- libguh/bluetooth/bluetoothscanner.cpp | 16 +- libguh/hardware/radio433/radio433.cpp | 6 +- plugins/deviceplugins/deviceplugins.pro | 2 +- plugins/deviceplugins/elgato/aveabulb.cpp | 146 +++++ plugins/deviceplugins/elgato/aveabulb.h | 51 ++ .../elgato/devicepluginelgato.cpp | 526 ++++++++++++++++++ .../deviceplugins/elgato/devicepluginelgato.h | 53 ++ .../elgato/devicepluginelgato.json | 56 ++ plugins/deviceplugins/elgato/elgato.pro | 16 + server/jsonrpc/jsonhandler.cpp | 2 +- 16 files changed, 886 insertions(+), 119 deletions(-) create mode 100644 doc/bluetooth.qdoc create mode 100644 plugins/deviceplugins/elgato/aveabulb.cpp create mode 100644 plugins/deviceplugins/elgato/aveabulb.h create mode 100644 plugins/deviceplugins/elgato/devicepluginelgato.cpp create mode 100644 plugins/deviceplugins/elgato/devicepluginelgato.h create mode 100644 plugins/deviceplugins/elgato/devicepluginelgato.json create mode 100644 plugins/deviceplugins/elgato/elgato.pro diff --git a/debian/guh-plugins.install b/debian/guh-plugins.install index 753c713a..ec9904b1 100644 --- a/debian/guh-plugins.install +++ b/debian/guh-plugins.install @@ -18,4 +18,5 @@ usr/lib/guh/plugins/libguh_devicepluginleynew.so usr/lib/guh/plugins/libguh_deviceplugintune.so usr/lib/guh/plugins/libguh_devicepluginudpcommander.so usr/lib/guh/plugins/libguh_devicepluginkodi.so +usr/lib/guh/plugins/libguh_devicepluginelgato.so diff --git a/doc/bluetooth.qdoc b/doc/bluetooth.qdoc new file mode 100644 index 00000000..211d33aa --- /dev/null +++ b/doc/bluetooth.qdoc @@ -0,0 +1,7 @@ +/*! + \page bluetooth-index.html + \title Bluetooth Plugins + + \annotatedlist bluetooth +*/ + diff --git a/doc/guh.qdoc b/doc/guh.qdoc index a7f49f8e..2233e247 100644 --- a/doc/guh.qdoc +++ b/doc/guh.qdoc @@ -23,14 +23,8 @@ \li \l{RF 433 MHz Plugins} \li \l{Service Plugins} \li \l{Network Plugins} - \endlist - \endlist - - \section1 API's - The guh daemon provides two API's: - \list - \li \l{JSON-RPC API}{JSON-RPC} - \li \l{https://github.com/guh/guh/wiki/REST-Api-documentation}{REST} + \li \l{Bluetooth Plugins} + \endlist \endlist \section1 Write your own plugin diff --git a/doc/plugins.qdoc b/doc/plugins.qdoc index f8336424..cd638334 100644 --- a/doc/plugins.qdoc +++ b/doc/plugins.qdoc @@ -13,6 +13,8 @@ \section2 Network Plugins \annotatedlist network + \section1 Bluetooth Plugins + \annotatedlist bluetooth */ diff --git a/libguh/bluetooth/bluetoothlowenergydevice.cpp b/libguh/bluetooth/bluetoothlowenergydevice.cpp index aa59932c..0cb6f6cf 100644 --- a/libguh/bluetooth/bluetoothlowenergydevice.cpp +++ b/libguh/bluetooth/bluetoothlowenergydevice.cpp @@ -26,9 +26,8 @@ BluetoothLowEnergyDevice::BluetoothLowEnergyDevice(const QBluetoothDeviceInfo &d connect(m_controller, &QLowEnergyController::connected, this, &BluetoothLowEnergyDevice::connected); connect(m_controller, &QLowEnergyController::disconnected, this, &BluetoothLowEnergyDevice::disconnected); - connect(m_controller, &QLowEnergyController::serviceDiscovered, this, &BluetoothLowEnergyDevice::serviceDiscovered); - connect(m_controller, &QLowEnergyController::discoveryFinished, this, &BluetoothLowEnergyDevice::serviceScanFinished); connect(m_controller, SIGNAL(error(QLowEnergyController::Error)), this, SLOT(deviceError(QLowEnergyController::Error))); + connect(m_controller, SIGNAL(discoveryFinished()), this, SIGNAL(servicesDiscoveryFinished())); } QString BluetoothLowEnergyDevice::name() const @@ -41,14 +40,9 @@ QBluetoothAddress BluetoothLowEnergyDevice::address() const return m_deviceInfo.address(); } -QList BluetoothLowEnergyDevice::services() const +QLowEnergyController *BluetoothLowEnergyDevice::controller() const { - return m_services; -} - -QList BluetoothLowEnergyDevice::characteristics() const -{ - return m_characteristics; + return m_controller; } void BluetoothLowEnergyDevice::connectDevice() @@ -66,90 +60,22 @@ bool BluetoothLowEnergyDevice::isConnected() const return m_connected; } -bool BluetoothLowEnergyDevice::isDiscovered() const -{ - return m_discovered; -} - -void BluetoothLowEnergyDevice::discoverService(const QBluetoothUuid &serviceUuid) -{ - Q_UNUSED(serviceUuid); -} - void BluetoothLowEnergyDevice::connected() { m_connected = true; - qDebug() << "connected successfully to" << name() << address().toString(); - emit connectionStatusChanged(true); + qDebug() << "connected successfully to bluetooth LE device:" << name() << address().toString(); + emit connectionStatusChanged(); m_controller->discoverServices(); } void BluetoothLowEnergyDevice::disconnected() { m_connected = false; - qWarning() << "disconnected from" << name() << address().toString(); - emit connectionStatusChanged(false); + qWarning() << "disconnected from bluetooth LE device:" << name() << address().toString(); + emit connectionStatusChanged(); } void BluetoothLowEnergyDevice::deviceError(const QLowEnergyController::Error &error) { - qWarning() << "ERROR: Bluetooth device" << name() << address().toString() << ": " << error << m_controller->errorString(); -} - -void BluetoothLowEnergyDevice::serviceDiscovered(const QBluetoothUuid &serviceUuid) -{ - QLowEnergyService *service = m_controller->createServiceObject(serviceUuid); - - if (!service) { - qWarning() << "Cannot create service for uuid" << serviceUuid; - return; - } - - connect(service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)), this, SLOT(serviceCharacteristicChanged(QLowEnergyCharacteristic,QByteArray))); - connect(service, SIGNAL(error(QLowEnergyService::ServiceError)), this, SLOT(serviceError(QLowEnergyService::ServiceError))); - connect(service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(serviceStateChanged(QLowEnergyService::ServiceState))); - - m_services.append(service); -} - -void BluetoothLowEnergyDevice::serviceStateChanged(const QLowEnergyService::ServiceState &newState) -{ - if (newState != QLowEnergyService::ServiceDiscovered) { - return; - } - - QLowEnergyService *service = qobject_cast(sender()); - foreach (QLowEnergyCharacteristic characteristic, service->characteristics()) { - if (!characteristic.descriptors().isEmpty()) { - foreach (QLowEnergyDescriptor descriptor, characteristic.descriptors()) { - if (descriptor.isValid()) { - m_descriptors.append(descriptor); - } - } - } - m_characteristics.append(characteristic); - } - qDebug() << "details discovered for service " << service->serviceName() << service->serviceUuid().toString(); -} - -void BluetoothLowEnergyDevice::serviceScanFinished() -{ - qDebug() << "service scan finished"; - emit serviceScanFinished(); -} - -void BluetoothLowEnergyDevice::serviceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) -{ - qDebug() << "characteristic" << characteristic.name() << "changed:" << value; -} - -void BluetoothLowEnergyDevice::serviceError(const QLowEnergyService::ServiceError &error) -{ - QLowEnergyService *service = qobject_cast(sender()); - qWarning() << "ERROR: Bluetooth service " << service->serviceName() << service->serviceUuid() << ": " << error; -} - -void BluetoothLowEnergyDevice::confirmedDescriptorWrite(const QLowEnergyDescriptor &descriptor, const QByteArray &value) -{ - qDebug() << "descriptor" << descriptor.name() << "written successfully " << value; + qWarning() << "ERROR: Bluetooth LE device:" << name() << address().toString() << ": " << error << m_controller->errorString(); } diff --git a/libguh/bluetooth/bluetoothlowenergydevice.h b/libguh/bluetooth/bluetoothlowenergydevice.h index 0c0f199c..ab85a24a 100644 --- a/libguh/bluetooth/bluetoothlowenergydevice.h +++ b/libguh/bluetooth/bluetoothlowenergydevice.h @@ -33,8 +33,6 @@ public: QString name() const; QBluetoothAddress address() const; - QList services() const; - QList characteristics() const; void connectDevice(); void disconnectDevice(); @@ -42,35 +40,24 @@ public: bool isConnected() const; bool isDiscovered() const; - void discoverService(const QBluetoothUuid &serviceUuid); +protected: + QLowEnergyController *controller() const; private: QBluetoothDeviceInfo m_deviceInfo; QLowEnergyController *m_controller; - QList m_services; - QList m_characteristics; - QList m_descriptors; bool m_connected; bool m_discovered; signals: - void connectionStatusChanged(const bool &connectionStatus); - void servicesDiscovered(); + void connectionStatusChanged(); + void servicesDiscoveryFinished(); private slots: - // Device void connected(); void disconnected(); void deviceError(const QLowEnergyController::Error &error); - void serviceDiscovered(const QBluetoothUuid &serviceUuid); - void serviceStateChanged(const QLowEnergyService::ServiceState &newState); - void serviceScanFinished(); - - // Service - void serviceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); - void serviceError(const QLowEnergyService::ServiceError &error); - void confirmedDescriptorWrite(const QLowEnergyDescriptor &descriptor, const QByteArray &value); }; #endif // BLUETOOTHLOWENERGYDEVICE_H diff --git a/libguh/bluetooth/bluetoothscanner.cpp b/libguh/bluetooth/bluetoothscanner.cpp index a1ef27d3..2816ed1c 100644 --- a/libguh/bluetooth/bluetoothscanner.cpp +++ b/libguh/bluetooth/bluetoothscanner.cpp @@ -23,7 +23,7 @@ BluetoothScanner::BluetoothScanner(QObject *parent) : { m_timer = new QTimer(this); m_timer->setSingleShot(true); - m_timer->setInterval(10000); + m_timer->setInterval(5000); connect(m_timer, &QTimer::timeout, this, &BluetoothScanner::discoveryTimeout); } @@ -86,7 +86,7 @@ bool BluetoothScanner::discover(const PluginId &pluginId) void BluetoothScanner::deviceDiscovered(const QBluetoothDeviceInfo &device) { - // check if this is LE device + // check if this is a LE device bool bluetoothLE = false; if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) { bluetoothLE = true; @@ -98,14 +98,16 @@ void BluetoothScanner::deviceDiscovered(const QBluetoothDeviceInfo &device) void BluetoothScanner::onError(QBluetoothDeviceDiscoveryAgent::Error error) { - Q_UNUSED(error); + Q_UNUSED(error) + m_available = false; - if (m_timer->isActive()) { + + if (m_timer->isActive()) m_timer->stop(); - } - if (isRunning()) { + + if (isRunning()) m_discoveryAgent->stop(); - } + qWarning() << "ERROR: Bluetooth discovery:" << m_discoveryAgent->errorString(); } diff --git a/libguh/hardware/radio433/radio433.cpp b/libguh/hardware/radio433/radio433.cpp index 3b587a4a..da817ae5 100644 --- a/libguh/hardware/radio433/radio433.cpp +++ b/libguh/hardware/radio433/radio433.cpp @@ -98,16 +98,16 @@ bool Radio433::enable() if (gpioFile.exists()) { // bool receiverAvailable = m_receiver->startReceiver(); // if (!receiverAvailable) { -// //qCWarning(dcHardware) << "ERROR: radio 433 MHz receiver not available on GPIO's"; +// //qWarning() << "ERROR: radio 433 MHz receiver not available on GPIO's"; // } // bool transmitterAvailable = m_transmitter->startTransmitter(); // if (!transmitterAvailable) { -// //qCWarning(dcHardware) << "ERROR: radio 433 MHz transmitter not available on GPIO's"; +// //qWarning() << "ERROR: radio 433 MHz transmitter not available on GPIO's"; // } // if (!receiverAvailable && !transmitterAvailable) { -// qCWarning(dcHardware) << "--> Radio 433 MHz GPIO's not available."; +// qWarning() << "--> Radio 433 MHz GPIO's not available."; // return false; // } } diff --git a/plugins/deviceplugins/deviceplugins.pro b/plugins/deviceplugins/deviceplugins.pro index c8980366..3be90732 100644 --- a/plugins/deviceplugins/deviceplugins.pro +++ b/plugins/deviceplugins/deviceplugins.pro @@ -26,5 +26,5 @@ boblight { } contains(DEFINES, BLUETOOTH_LE) { - # bluetooth plugins + SUBDIRS += elgato } diff --git a/plugins/deviceplugins/elgato/aveabulb.cpp b/plugins/deviceplugins/elgato/aveabulb.cpp new file mode 100644 index 00000000..6d23b54a --- /dev/null +++ b/plugins/deviceplugins/elgato/aveabulb.cpp @@ -0,0 +1,146 @@ +#include "aveabulb.h" + + +AveaBulb::AveaBulb(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType, QObject *parent) : + BluetoothLowEnergyDevice(deviceInfo, addressType, parent), + m_colorService(0) +{ + m_colorSeviceUuid = QBluetoothUuid(QUuid("f815e810-456c-6761-746f-4d756e696368")); + m_colorCharacteristicUuid = QBluetoothUuid(QUuid("f815e811-456c-6761-746f-4d756e696368")); + + connect(this, SIGNAL(connectionStatusChanged()), this,SLOT(onConnectionStatusChanged())); + connect(this, SIGNAL(servicesDiscoveryFinished()), this, SLOT(serviceScanFinished())); +} + +bool AveaBulb::isAvailable() +{ + return m_isAvailable; +} + +void AveaBulb::serviceScanFinished() +{ + if (!controller()->services().contains(m_colorSeviceUuid)) { + qWarning() << "ERROR: Color service not found for device" << name() << address().toString(); + return; + } + + if (m_colorService) { + qWarning() << "ERROR: Attention! bad implementation of service handling!!"; + return; + } + + // create color service and discover it + m_colorService = controller()->createServiceObject(m_colorSeviceUuid, this); + + if (!m_colorService) { + qWarning() << "ERROR: could not create color service for device" << name() << address().toString(); + return; + } + + connect(m_colorService, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(serviceStateChanged(QLowEnergyService::ServiceState))); + connect(m_colorService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)), this, SLOT(serviceCharacteristicChanged(QLowEnergyCharacteristic,QByteArray))); + connect(m_colorService, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)), this, SLOT(confirmedCharacteristicWritten(QLowEnergyCharacteristic,QByteArray))); + connect(m_colorService, SIGNAL(error(QLowEnergyService::ServiceError)), this, SLOT(serviceError(QLowEnergyService::ServiceError))); + + m_colorService->discoverDetails(); +} + +void AveaBulb::onConnectionStatusChanged() +{ + if (!isConnected()) { + // delete the service, needs to be recreatedand rediscovered once the device will be reconnected + delete m_colorService; + m_colorService = 0; + + m_isAvailable = false; + emit availableChanged(); + } +} + +void AveaBulb::serviceStateChanged(const QLowEnergyService::ServiceState &state) +{ + switch (state) { + case QLowEnergyService::ServiceDiscovered: + m_colorCharacteristic = m_colorService->characteristic(m_colorCharacteristicUuid); + + if (!m_colorCharacteristic.isValid()) { + qWarning() << "ERROR: color characteristc not found for device " << name() << address().toString(); + return; + } + m_isAvailable = true; + emit availableChanged(); + break; + default: + break; + } +} + +void AveaBulb::serviceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + qDebug() << "service characteristic changed" << characteristic.name() << value.toHex(); +} + +void AveaBulb::confirmedCharacteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + if (characteristic.handle() == m_colorCharacteristic.handle()) { + if (m_actions.contains(value.toHex())) { + ActionId actionId = m_actions.take(value.toHex()); + emit actionExecutionFinished(actionId, true); + } + } +} + +void AveaBulb::serviceError(const QLowEnergyService::ServiceError &error) +{ + qWarning() << "ERROR: color service of " << name() << address().toString() << ":" << error; +} + +bool AveaBulb::enableNotification() +{ + if (!isAvailable()) + return false; + + qDebug() << "enable notify"; + QByteArray value = "0100"; + m_colorService->writeCharacteristic(m_colorCharacteristic, value, QLowEnergyService::WriteWithResponse); + + return true; +} + +bool AveaBulb::testMethod() +{ + if (!isAvailable()) + return false; + + QByteArray value = "34"; + qDebug() << "test" << value; + m_colorService->writeCharacteristic(m_colorCharacteristic, QByteArray::fromHex(value), QLowEnergyService::WriteWithResponse); + + return true; +} + +bool AveaBulb::actionPowerOff(ActionId actionId) +{ + if (!isAvailable()) + return false; + + QByteArray value = "35f4010a00008000b000a00090"; + qDebug() << "set" << name() << "power OFF"; + m_actions.insert(value, actionId); + m_colorService->writeCharacteristic(m_colorCharacteristic, QByteArray::fromHex(value), QLowEnergyService::WriteWithResponse); + + return true; +} + +bool AveaBulb::setWhite(ActionId actionId) +{ + if (!isAvailable()) + return false; + + QByteArray value = "3532000a00ff0f003000200010"; + qDebug() << "set" << name() << "white"; + m_actions.insert(value, actionId); + m_colorService->writeCharacteristic(m_colorCharacteristic, QByteArray::fromHex(value), QLowEnergyService::WriteWithResponse); + + return true; +} diff --git a/plugins/deviceplugins/elgato/aveabulb.h b/plugins/deviceplugins/elgato/aveabulb.h new file mode 100644 index 00000000..f80da713 --- /dev/null +++ b/plugins/deviceplugins/elgato/aveabulb.h @@ -0,0 +1,51 @@ +#ifndef AVEABULB_H +#define AVEABULB_H + +#include +#include "typeutils.h" +#include "bluetooth/bluetoothlowenergydevice.h" + +class AveaBulb : public BluetoothLowEnergyDevice +{ + Q_OBJECT +public: + explicit AveaBulb(const QBluetoothDeviceInfo &deviceInfo, const QLowEnergyController::RemoteAddressType &addressType, QObject *parent = 0); + + bool isAvailable(); + +signals: + void availableChanged(); + void actionExecutionFinished(const ActionId &actionId, const bool &success); + +private: + QBluetoothUuid m_colorSeviceUuid; + QLowEnergyService *m_colorService; + + QBluetoothUuid m_colorCharacteristicUuid; + QLowEnergyCharacteristic m_colorCharacteristic; + + bool m_isAvailable; + + QHash m_actions; + +private slots: + void serviceScanFinished(); + void onConnectionStatusChanged(); + + // Color service + void serviceStateChanged(const QLowEnergyService::ServiceState &state); + void serviceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + void confirmedCharacteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + void serviceError(const QLowEnergyService::ServiceError &error); + + bool enableNotification(); + bool testMethod(); + +public slots: + // actions + bool actionPowerOff(ActionId actionId); + bool setWhite(ActionId actionId); + +}; + +#endif // AVEABULB_H diff --git a/plugins/deviceplugins/elgato/devicepluginelgato.cpp b/plugins/deviceplugins/elgato/devicepluginelgato.cpp new file mode 100644 index 00000000..d0a137e2 --- /dev/null +++ b/plugins/deviceplugins/elgato/devicepluginelgato.cpp @@ -0,0 +1,526 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * 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 . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \page elgato.html + \title Elgato + + \ingroup plugins + \ingroup bluetooth + + This plugin allows to find and controll the Bluetooth Low Energy bulb from \l{https://www.elgato.com/en/smart/avea}{Elgato Avea}. + + \chapter Device Overviw of \tt{Avea_44A9} + \section1 Services + \code + ---------------------------------------------------------------- + name : "Generic Access" + type : "" + uuid : "{00001800-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x1800" + ---------------------------------------------------------------- + name : "Generic Attribute" + type : "" + uuid : "{00001801-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x1801" + ---------------------------------------------------------------- + name : "Device Information" + type : "" + uuid : "{0000180a-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x180a" + ---------------------------------------------------------------- + name : "Unknown Service" + type : "" + uuid : "{f815e600-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e600-456c-6761-746f-4d756e696368" + ---------------------------------------------------------------- + name : "Unknown Service" + type : "" + uuid : "{f815e500-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e500-456c-6761-746f-4d756e696368" + ---------------------------------------------------------------- + name : "Unknown Service" + type : "" + uuid : "{f815e810-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e810-456c-6761-746f-4d756e696368" + ---------------------------------------------------------------- + name : "Unknown Service" + type : "" + uuid : "{f815e900-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e900-456c-6761-746f-4d756e696368" + ---------------------------------------------------------------- + \endcode + + \section2 Service: "Generic Access" {00001800-0000-1000-8000-00805f9b34fb} details + \code + ---------------------------------------------------------------- + characteristics: + name : "GAP Device Name" + uuid : "{00002a00-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a00" + handle : "0x3" + permission : ("Read") + value : "Avea_44A9" + value (hex) : "417665615f34344139" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "GAP Appearance" + uuid : "{00002a01-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a01" + handle : "0x5" + permission : ("Read") + value : " + value (hex) : "0000" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "GAP Peripheral Privacy Flag" + uuid : "{00002a02-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a02" + handle : "0x7" + permission : ("Read", "Write") + value : " + value (hex) : "00" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "GAP Reconnection Address" + uuid : "{00002a03-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a03" + handle : "0x9" + permission : ("Read", "Write") + value : " + value (hex) : "000000000000" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "GAP Peripheral Preferred Connection Parameters" + uuid : "{00002a04-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a04" + handle : "0xb" + permission : ("Read") + value : "P + value (hex) : "5000a0000000e803" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + \endcode + + \section2 Service: "Generic Attribute" {00001801-0000-1000-8000-00805f9b34fb} details + \code + ---------------------------------------------------------------- + characteristics: + name : "GATT Service Changed" + uuid : "{00002a05-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a05" + handle : "0xe" + permission : ("Indicate") + value : "" + value (hex) : "" + --------------------------------------------------------- + descriptor count: 1 + --------------------------------------------------------- + name : "Client Characteristic Configuration" + uuid : "{00002902-0000-1000-8000-00805f9b34fb}" + handle : 15 + value : " + value (hex) : "0000" + ----------------------------------------------------- + \endcode + + \section2 Service: "Device Information" {0000180a-0000-1000-8000-00805f9b34fb} details + \code + ---------------------------------------------------------------- + characteristics: + name : "System ID" + uuid : "{00002a23-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a23" + handle : "0x12" + permission : ("Read") + value : "D�j����" + value (hex) : "44a96afeff18eb84" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "Model Number String" + uuid : "{00002a24-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a24" + handle : "0x14" + permission : ("Read") + value : "1 + value (hex) : "3100" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "Serial Number String" + uuid : "{00002a25-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a25" + handle : "0x16" + permission : ("Read") + value : "44A96A18EB84" + value (hex) : "343441393641313845423834" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "Firmware Revision String" + uuid : "{00002a26-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a26" + handle : "0x18" + permission : ("Read") + value : "1.0.0.296Af + value (hex) : "312e302e302e323936416600" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "Hardware Revision String" + uuid : "{00002a27-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a27" + handle : "0x1a" + permission : ("Read") + value : "Elgato Avea + value (hex) : "456c6761746f204176656100" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "Software Revision String" + uuid : "{00002a28-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a28" + handle : "0x1c" + permission : ("Read") + value : "1.0 + value (hex) : "312e3000" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "Manufacturer Name String" + uuid : "{00002a29-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a29" + handle : "0x1e" + permission : ("Read") + value : "Elgato Systems GmbH + value (hex) : "456c6761746f2053797374656d7320476d624800" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + name : "PnP ID" + uuid : "{00002a50-0000-1000-8000-00805f9b34fb}" + uuid (hex) : "0x2a50" + handle : "0x20" + permission : ("Read") + value : "� + value (hex) : "02d90f00000001" + --------------------------------------------------------- + descriptor count: 0 + --------------------------------------------------------- + \endcode + + \section2 Service: "Unknown Service" {f815e600-456c-6761-746f-4d756e696368} details + \code + ---------------------------------------------------------------- + characteristics: + name : "Alert" + uuid : "{f815e601-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e601-456c-6761-746f-4d756e696368" + handle : "0x23" + permission : ("Notify") + value : "" + value (hex) : "" + --------------------------------------------------------- + descriptor count: 2 + --------------------------------------------------------- + name : "Client Characteristic Configuration" + uuid : "{00002902-0000-1000-8000-00805f9b34fb}" + handle : 36 + value : " + value (hex) : "0000" + ----------------------------------------------------- + name : "Characteristic User Description" + uuid : "{00002901-0000-1000-8000-00805f9b34fb}" + handle : 37 + value : "Alert" + value (hex) : "416c657274" + ----------------------------------------------------- + \endcode + + \section2 Service: "Unknown Service" {f815e500-456c-6761-746f-4d756e696368} details + \code + ---------------------------------------------------------------- + characteristics: + name : "Seq Upload" + uuid : "{f815e501-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e501-456c-6761-746f-4d756e696368" + handle : "0x28" + permission : ("Write", "Notify", "WriteNoResp") + value : "" + value (hex) : "" + --------------------------------------------------------- + descriptor count: 2 + --------------------------------------------------------- + name : "Client Characteristic Configuration" + uuid : "{00002902-0000-1000-8000-00805f9b34fb}" + handle : 41 + value : " + value (hex) : "0000" + ----------------------------------------------------- + name : "Characteristic User Description" + uuid : "{00002901-0000-1000-8000-00805f9b34fb}" + handle : 42 + value : "Seq Upload" + value (hex) : "5365712055706c6f6164" + ----------------------------------------------------- + \endcode + + \section2 Service: "Unknown Service" {f815e810-456c-6761-746f-4d756e696368} details + + Tis service will be used to set the color (handle \tt{0x2d}). + + \code + ---------------------------------------------------------------- + characteristics: + name : "Debug" + uuid : "{f815e811-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e811-456c-6761-746f-4d756e696368" + handle : "0x2d" + permission : ("Write", "Notify") + value : "" + value (hex) : "" + --------------------------------------------------------- + descriptor count: 2 + --------------------------------------------------------- + name : "Client Characteristic Configuration" + uuid : "{00002902-0000-1000-8000-00805f9b34fb}" + handle : 46 + value : " + value (hex) : "0000" + ----------------------------------------------------- + name : "Characteristic User Description" + uuid : "{00002901-0000-1000-8000-00805f9b34fb}" + handle : 47 + value : "Debug" + value (hex) : "4465627567" + ----------------------------------------------------- + name : "User Name" + uuid : "{f815e812-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e812-456c-6761-746f-4d756e696368" + handle : "0x31" + permission : ("Read", "Write") + value : "" + value (hex) : "" + --------------------------------------------------------- + descriptor count: 1 + --------------------------------------------------------- + name : "Characteristic User Description" + uuid : "{00002901-0000-1000-8000-00805f9b34fb}" + handle : 50 + value : "User Name" + value (hex) : "55736572204e616d65" + ----------------------------------------------------- + \endcode + + \section2 Service: "Unknown Service" {f815e900-456c-6761-746f-4d756e696368} details + \code + ---------------------------------------------------------------- + characteristics: + name : "Img Identify" + uuid : "{f815e901-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e901-456c-6761-746f-4d756e696368" + handle : "0x35" + permission : ("Write", "Notify", "WriteNoResp") + value : "" + value (hex) : "" + --------------------------------------------------------- + descriptor count: 2 + --------------------------------------------------------- + name : "Client Characteristic Configuration" + uuid : "{00002902-0000-1000-8000-00805f9b34fb}" + handle : 54 + value : " + value (hex) : "0000" + ----------------------------------------------------- + name : "Characteristic User Description" + uuid : "{00002901-0000-1000-8000-00805f9b34fb}" + handle : 55 + value : "Img Identify" + value (hex) : "496d67204964656e74696679" + ----------------------------------------------------- + name : "Img Block" + uuid : "{f815e902-456c-6761-746f-4d756e696368}" + uuid (hex) : "f815e902-456c-6761-746f-4d756e696368" + handle : "0x39" + permission : ("Write", "Notify", "WriteNoResp") + value : "" + value (hex) : "" + --------------------------------------------------------- + descriptor count: 2 + --------------------------------------------------------- + name : "Client Characteristic Configuration" + uuid : "{00002902-0000-1000-8000-00805f9b34fb}" + handle : 58 + value : " + value (hex) : "0000" + ----------------------------------------------------- + name : "Characteristic User Description" + uuid : "{00002901-0000-1000-8000-00805f9b34fb}" + handle : 59 + value : "Img Block" + value (hex) : "496d6720426c6f636b" + ----------------------------------------------------- + \endcode + + + \chapter Plugin properties + Following JSON file contains the definition and the description of all available \l{DeviceClass}{DeviceClasses} + and \l{Vendor}{Vendors} of this \l{DevicePlugin}. + + Each \l{DeviceClass} has a list of \l{ParamType}{paramTypes}, \l{ActionType}{actionTypes}, \l{StateType}{stateTypes} + and \l{EventType}{eventTypes}. The \l{DeviceClass::CreateMethod}{createMethods} parameter describes how the \l{Device} + will be created in the system. A device can have more than one \l{DeviceClass::CreateMethod}{CreateMethod}. + The \l{DeviceClass::SetupMethod}{setupMethod} describes the setup method of the \l{Device}. + The detailed implementation of each \l{DeviceClass} can be found in the source code. + + \quotefile plugins/deviceplugins/elgato/devicepluginelgato.json +*/ + +#include "devicepluginelgato.h" + +#include "plugin/device.h" +#include "devicemanager.h" +#include "plugininfo.h" + +DevicePluginElgato::DevicePluginElgato() +{ +} + +DeviceManager::DeviceError DevicePluginElgato::discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) +{ + Q_UNUSED(params) + + if (deviceClassId != aveaDeviceClassId) + return DeviceManager::DeviceErrorDeviceClassNotFound; + + if (!discoverBluetooth()) + return DeviceManager::DeviceErrorHardwareNotAvailable; + + return DeviceManager::DeviceErrorAsync; +} + +DeviceManager::DeviceSetupStatus DevicePluginElgato::setupDevice(Device *device) +{ + if (device->deviceClassId() == aveaDeviceClassId) { + QBluetoothAddress address = QBluetoothAddress(device->paramValue("mac address").toString()); + QString name = device->paramValue("name").toString(); + QBluetoothDeviceInfo deviceInfo = QBluetoothDeviceInfo(address, name, 0); + + AveaBulb *avea = new AveaBulb(deviceInfo, QLowEnergyController::PublicAddress, this); + connect(avea, &AveaBulb::availableChanged, this, &DevicePluginElgato::bulbAvailableChanged); + connect(avea, &AveaBulb::actionExecutionFinished, this, &DevicePluginElgato::actionFinished); + m_bulbs.insert(avea, device); + + avea->connectDevice(); + + return DeviceManager::DeviceSetupStatusSuccess; + } + return DeviceManager::DeviceSetupStatusFailure; +} + +DeviceManager::HardwareResources DevicePluginElgato::requiredHardware() const +{ + return DeviceManager::HardwareResourceBluetoothLE; +} + +DeviceManager::DeviceError DevicePluginElgato::executeAction(Device *device, const Action &action) +{ + // check deviceClassId + if (device->deviceClassId() == aveaDeviceClassId) { + AveaBulb *bulb = m_bulbs.key(device); + + if (!bulb->isAvailable()) + return DeviceManager::DeviceErrorHardwareNotAvailable; + + // check actionTypeId + if (action.actionTypeId() == powerOffActionTypeId) { + bulb->actionPowerOff(action.id()); + return DeviceManager::DeviceErrorAsync; + } else if (action.actionTypeId() == whiteActionTypeId) { + bulb->setWhite(action.id()); + return DeviceManager::DeviceErrorAsync; + } + return DeviceManager::DeviceErrorActionTypeNotFound; + } + return DeviceManager::DeviceErrorDeviceClassNotFound; +} + +void DevicePluginElgato::bluetoothDiscoveryFinished(const QList &deviceInfos) +{ + QList deviceDescriptors; + foreach (QBluetoothDeviceInfo deviceInfo, deviceInfos) { + if (deviceInfo.name().contains("Avea")) { + if (!verifyExistingDevices(deviceInfo)) { + DeviceDescriptor descriptor(aveaDeviceClassId, "Avea", deviceInfo.address().toString()); + ParamList params; + params.append(Param("name", deviceInfo.name())); + params.append(Param("mac address", deviceInfo.address().toString())); + descriptor.setParams(params); + deviceDescriptors.append(descriptor); + } + } + } + + emit devicesDiscovered(aveaDeviceClassId, deviceDescriptors); +} + +void DevicePluginElgato::deviceRemoved(Device *device) +{ + if (!m_bulbs.values().contains(device)) + return; + + AveaBulb *bulb= m_bulbs.key(device); + + m_bulbs.take(bulb); + delete bulb; +} + +bool DevicePluginElgato::verifyExistingDevices(const QBluetoothDeviceInfo &deviceInfo) +{ + foreach (Device *device, myDevices()) { + if (device->paramValue("mac address").toString() == deviceInfo.address().toString()) + return true; + } + + return false; +} + +void DevicePluginElgato::bulbAvailableChanged() +{ + AveaBulb *bulb =static_cast(sender()); + Device *device = m_bulbs.value(bulb); + device->setStateValue(availableStateTypeId, bulb->isAvailable()); +} + +void DevicePluginElgato::actionFinished(const ActionId actionId, const bool &success) +{ + if (success) { + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); + } else { + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorHardwareFailure); + } +} + + + diff --git a/plugins/deviceplugins/elgato/devicepluginelgato.h b/plugins/deviceplugins/elgato/devicepluginelgato.h new file mode 100644 index 00000000..23e1f2eb --- /dev/null +++ b/plugins/deviceplugins/elgato/devicepluginelgato.h @@ -0,0 +1,53 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * 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 DEVICEPLUGINELGATO_H +#define DEVICEPLUGINELGATO_H + +#include "plugin/deviceplugin.h" +#include "bluetooth/bluetoothlowenergydevice.h" +#include "aveabulb.h" + +class DevicePluginElgato : public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginelgato.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginElgato(); + + DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + DeviceManager::HardwareResources requiredHardware() const override; + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + + void bluetoothDiscoveryFinished(const QList &deviceInfos); + void deviceRemoved(Device *device) override; + +private: + QHash m_bulbs; + bool verifyExistingDevices(const QBluetoothDeviceInfo &deviceInfo); + +private slots: + void bulbAvailableChanged(); + void actionFinished(const ActionId actionId, const bool &success); +}; + +#endif // DEVICEPLUGINELGATO_H diff --git a/plugins/deviceplugins/elgato/devicepluginelgato.json b/plugins/deviceplugins/elgato/devicepluginelgato.json new file mode 100644 index 00000000..da1be95d --- /dev/null +++ b/plugins/deviceplugins/elgato/devicepluginelgato.json @@ -0,0 +1,56 @@ +{ + "name": "Elgato", + "id": "c5c03ad4-bfdb-444a-8eca-2c234c46cc27", + "vendors": [ + { + "name": "Elgato", + "id": "90a3091d-1053-4f77-8dc3-92e27bbcebe7", + "deviceClasses": [ + { + "deviceClassId": "164f9602-90ee-4693-bda3-9cafae37603e", + "idName": "avea", + "name": "Avea", + "createMethods": ["discovery"], + "paramTypes": [ + { + "name": "name", + "type": "QString", + "inputType": "TextLine" + }, + { + "name": "mac address", + "type": "QString", + "inputType": "MacAddress" + } + ], + "stateTypes": [ + { + "id": "6d5e792a-c786-40d2-ae35-a48ac6fafcbc", + "idName": "available", + "name": "available", + "type": "bool", + "defaultValue": false + } + ], + "actionTypes": [ + { + "id": "30b6334d-37cd-4b94-a397-3b9b642c762e", + "idName": "reconnect", + "name": "reconnect" + }, + { + "id": "7936bd51-7ea3-4df4-9998-7325de85b677", + "idName": "white", + "name": "white" + }, + { + "id": "b2f39b7b-dd11-4db4-a82b-dd3d4b973bd5", + "idName": "powerOff", + "name": "power off" + } + ] + } + ] + } + ] +} diff --git a/plugins/deviceplugins/elgato/elgato.pro b/plugins/deviceplugins/elgato/elgato.pro new file mode 100644 index 00000000..f56c92e4 --- /dev/null +++ b/plugins/deviceplugins/elgato/elgato.pro @@ -0,0 +1,16 @@ +include(../../plugins.pri) + +TARGET = $$qtLibraryTarget(guh_devicepluginelgato) + +QT+= bluetooth + +SOURCES += \ + devicepluginelgato.cpp \ + aveabulb.cpp + +HEADERS += \ + devicepluginelgato.h \ + aveabulb.h + + + diff --git a/server/jsonrpc/jsonhandler.cpp b/server/jsonrpc/jsonhandler.cpp index 0dc8fe5e..2031c884 100644 --- a/server/jsonrpc/jsonhandler.cpp +++ b/server/jsonrpc/jsonhandler.cpp @@ -227,7 +227,7 @@ void JsonReply::setCommandId(int commandId) void JsonReply::startWait() { - m_timeout.start(5000); + m_timeout.start(10000); } void JsonReply::timeout()