From a6725ec4e9a79649fdab3901f43873a3f8aa4e2c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 19 Jun 2014 22:29:45 +0200 Subject: [PATCH] add initial support for pairing devices (Only CreateMethodDiscovery and SetupMethodPushButton so far) implement an initial Philips Hue plugin to test it add a python script to test stuff --- libguh/devicemanager.cpp | 179 ++++++++++++- libguh/devicemanager.h | 13 +- libguh/plugin/deviceclass.cpp | 10 + libguh/plugin/deviceclass.h | 4 + libguh/plugin/devicedescriptor.h | 2 + libguh/plugin/deviceplugin.cpp | 14 + libguh/plugin/deviceplugin.h | 3 + plugins/deviceplugins/deviceplugins.pro | 1 + .../devicepluginopenweathermap.cpp | 4 +- .../philipshue/devicepluginphilipshue.cpp | 242 ++++++++++++++++++ .../philipshue/devicepluginphilipshue.h | 71 +++++ .../philipshue/devicepluginphilipshue.json | 1 + .../deviceplugins/philipshue/discovery.cpp | 86 +++++++ plugins/deviceplugins/philipshue/discovery.h | 50 ++++ .../philipshue/huebridgeconnection.cpp | 39 +++ .../philipshue/huebridgeconnection.h | 39 +++ .../deviceplugins/philipshue/philipshue.pro | 18 ++ server/jsonrpc/devicehandler.cpp | 133 +++++++++- server/jsonrpc/devicehandler.h | 7 + server/jsonrpc/jsonrpcserver.cpp | 2 +- server/main.cpp | 1 + server/ruleengine.cpp | 5 +- server/server.pro | 1 + tests/auto/api.json | 33 ++- tests/scripts/cmdmgr.py | 225 ++++++++++++++++ 25 files changed, 1169 insertions(+), 14 deletions(-) create mode 100644 plugins/deviceplugins/philipshue/devicepluginphilipshue.cpp create mode 100644 plugins/deviceplugins/philipshue/devicepluginphilipshue.h create mode 100644 plugins/deviceplugins/philipshue/devicepluginphilipshue.json create mode 100644 plugins/deviceplugins/philipshue/discovery.cpp create mode 100644 plugins/deviceplugins/philipshue/discovery.h create mode 100644 plugins/deviceplugins/philipshue/huebridgeconnection.cpp create mode 100644 plugins/deviceplugins/philipshue/huebridgeconnection.h create mode 100644 plugins/deviceplugins/philipshue/philipshue.pro create mode 100755 tests/scripts/cmdmgr.py diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp index b8f86d6a..d333d306 100644 --- a/libguh/devicemanager.cpp +++ b/libguh/devicemanager.cpp @@ -95,6 +95,9 @@ DeviceManager::DeviceManager(QObject *parent) : QObject(parent), m_radio433(0) { + qRegisterMetaType(); + qRegisterMetaType(); + m_pluginTimer.setInterval(15000); connect(&m_pluginTimer, &QTimer::timeout, this, &DeviceManager::timerEvent); @@ -228,7 +231,7 @@ QPair DeviceManager::addConfiguredDevice(co return qMakePair(DeviceErrorCreationMethodNotSupported, "CreateMethodDiscovery"); } - DeviceDescriptor descriptor = m_discoveredDevices.take(deviceClassId).take(deviceDescriptorId); + DeviceDescriptor descriptor = m_discoveredDevices.take(deviceDescriptorId); if (!descriptor.isValid()) { return qMakePair(DeviceErrorDeviceDescriptorNotFound, deviceDescriptorId.toString()); } @@ -236,6 +239,98 @@ QPair DeviceManager::addConfiguredDevice(co return addConfiguredDeviceInternal(deviceClassId, descriptor.params(), deviceId); } +QPair DeviceManager::pairDevice(const DeviceClassId &deviceClassId, const QList ¶ms) +{ + DeviceClass deviceClass = findDeviceClass(deviceClassId); + if (deviceClass.id().isNull()) { + qWarning() << "cannot find a device class with id" << deviceClassId; + return qMakePair(DeviceErrorDeviceClassNotFound, deviceClassId.toString()); + } + + if (deviceClass.setupMethod() == DeviceClass::SetupMethodJustAdd) { + qWarning() << "Cannot setup this device this way. No need to pair this device."; + return qMakePair(DeviceErrorCreationMethodNotSupported, "No need to pair this device."); + } + + QUuid pairingTransactionId = QUuid::createUuid(); + m_pairingsJustAdd.insert(pairingTransactionId, qMakePair >(deviceClassId, params)); + + if (deviceClass.setupMethod() == DeviceClass::SetupMethodDisplayPin) { + // TODO: fetch PIN from device plugin + qWarning() << "SetupMethodDisplayPin not implemented yet"; + return qMakePair(DeviceErrorSetupFailed, "SetupMethodDisplayPin Not implemented yet."); + } + + return qMakePair(DeviceErrorNoError, pairingTransactionId.toString()); +} + +QPair DeviceManager::pairDevice(const DeviceClassId &deviceClassId, const DeviceDescriptorId &deviceDescriptorId) +{ + DeviceClass deviceClass = findDeviceClass(deviceClassId); + if (deviceClass.id().isNull()) { + qWarning() << "cannot find a device class with id" << deviceClassId; + return qMakePair(DeviceErrorDeviceClassNotFound, deviceClassId.toString()); + } + + if (deviceClass.setupMethod() == DeviceClass::SetupMethodJustAdd) { + qWarning() << "Cannot setup this device this way. No need to pair this device."; + return qMakePair(DeviceErrorCreationMethodNotSupported, "No need to pair this device."); + } + + if (!m_discoveredDevices.contains(deviceDescriptorId)) { + qWarning() << "Cannot find a DeviceDescriptor with ID" << deviceClassId.toString(); + return qMakePair(DeviceErrorDeviceDescriptorNotFound, deviceDescriptorId.toString()); + } + + QUuid pairingTransactionId = QUuid::createUuid(); + m_pairingsDiscovery.insert(pairingTransactionId, qMakePair(deviceClassId, deviceDescriptorId)); + + if (deviceClass.setupMethod() == DeviceClass::SetupMethodDisplayPin) { + // TODO: fetch PIN from device plugin + qWarning() << "SetupMethodDisplayPin not implemented yet"; + return qMakePair(DeviceErrorSetupFailed, "SetupMethodDisplayPin Not implemented yet."); + } + + return qMakePair(DeviceErrorNoError, pairingTransactionId.toString()); +} + +QPair DeviceManager::confirmPairing(const QUuid &pairingTransactionId, const QString &secret) +{ + if (m_pairingsJustAdd.contains(pairingTransactionId)) { + qWarning() << "this SetupMethod is not implemented yet"; + m_pairingsJustAdd.remove(pairingTransactionId); + return qMakePair(DeviceErrorSetupFailed, "Not implemented yet"); + } + + if (m_pairingsDiscovery.contains(pairingTransactionId)) { + DeviceDescriptorId deviceDescriptorId = m_pairingsDiscovery.value(pairingTransactionId).second; + DeviceClassId deviceClassId = m_pairingsDiscovery.value(pairingTransactionId).first; + + DeviceDescriptor deviceDescriptor = m_discoveredDevices.value(deviceDescriptorId); + + DevicePlugin *plugin = m_devicePlugins.value(m_supportedDevices.value(deviceClassId).pluginId()); + + if (!plugin) { + qWarning() << "Can't find a plugin for this device class"; + return report(DeviceErrorPluginNotFound, m_supportedDevices.value(deviceClassId).pluginId().toString()); + } + + QPair status = plugin->confirmPairing(pairingTransactionId, deviceClassId, deviceDescriptor.params()); + switch (status.first) { + case DeviceSetupStatusSuccess: + m_pairingsDiscovery.remove(pairingTransactionId); + return report(DeviceErrorNoError); + case DeviceSetupStatusFailure: + m_pairingsDiscovery.remove(pairingTransactionId); + return report(DeviceErrorSetupFailed, status.second); + case DeviceSetupStatusAsync: + return report(DeviceErrorAsync); + } + } + + return report(DeviceErrorPairingTransactionIdNotFound, pairingTransactionId.toString()); +} + QPair DeviceManager::addConfiguredDeviceInternal(const DeviceClassId &deviceClassId, const QList ¶ms, const DeviceId id) { DeviceClass deviceClass = findDeviceClass(deviceClassId); @@ -244,6 +339,11 @@ QPair DeviceManager::addConfiguredDeviceInt return qMakePair(DeviceErrorDeviceClassNotFound, deviceClassId.toString()); } + if (deviceClass.setupMethod() != DeviceClass::SetupMethodJustAdd) { + qWarning() << "Cannot setup this device this way. You need to pair this device."; + return qMakePair(DeviceErrorCreationMethodNotSupported, "You need to pair this device."); + } + QPair result = verifyParams(deviceClass.paramTypes(), params); if (result.first != DeviceErrorNoError) { return result; @@ -437,6 +537,7 @@ void DeviceManager::loadPlugins() connect(pluginIface, &DevicePlugin::devicesDiscovered, this, &DeviceManager::slotDevicesDiscovered); connect(pluginIface, &DevicePlugin::deviceSetupFinished, this, &DeviceManager::slotDeviceSetupFinished); connect(pluginIface, &DevicePlugin::actionExecutionFinished, this, &DeviceManager::actionExecutionFinished); + connect(pluginIface, &DevicePlugin::pairingFinished, this, &DeviceManager::slotPairingFinished); } } } @@ -527,11 +628,9 @@ void DeviceManager::createNewAutoDevices() void DeviceManager::slotDevicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors) { - QHash descriptorHash; foreach (const DeviceDescriptor &descriptor, deviceDescriptors) { - descriptorHash.insert(descriptor.id(), descriptor); + m_discoveredDevices.insert(descriptor.id(), descriptor); } - m_discoveredDevices[deviceClassId] = descriptorHash; emit devicesDiscovered(deviceClassId, deviceDescriptors); } @@ -589,6 +688,78 @@ void DeviceManager::slotDeviceSetupFinished(Device *device, DeviceManager::Devic emit deviceSetupFinished(device, DeviceManager::DeviceErrorNoError, QString()); } +void DeviceManager::slotPairingFinished(const QUuid &pairingTransactionId, DeviceManager::DeviceSetupStatus status, const QString &errorMessage) +{ + if (!m_pairingsJustAdd.contains(pairingTransactionId) && !m_pairingsDiscovery.contains(pairingTransactionId)) { + DevicePlugin *plugin = dynamic_cast(sender()); + if (plugin) { + qWarning() << "Received a pairing finished without waiting for it from plugin:" << plugin->metaObject()->className(); + } else { + qWarning() << "Received a pairing finished without waiting for it."; + } + return; + } + + DeviceClassId deviceClassId; + QList params; + + // Do this before checking status to make sure we clean up our hashes properly + if (m_pairingsJustAdd.contains(pairingTransactionId)) { + QPair > pair = m_pairingsJustAdd.take(pairingTransactionId); + deviceClassId = pair.first; + params = pair.second; + } + + if (m_pairingsDiscovery.contains(pairingTransactionId)) { + QPair pair = m_pairingsDiscovery.take(pairingTransactionId); + + DeviceDescriptorId deviceDescriptorId = pair.second; + DeviceDescriptor descriptor = m_discoveredDevices.take(deviceDescriptorId); + + deviceClassId = pair.first; + params = descriptor.params(); + } + + + qDebug() << "pairingfinsihed!" << errorMessage; + if (status != DeviceSetupStatusSuccess) { + qDebug() << "emitting shit"; + emit pairingFinished(pairingTransactionId, DeviceErrorSetupFailed, errorMessage); + return; + } + + DeviceClass deviceClass = findDeviceClass(deviceClassId); + DevicePlugin *plugin = m_devicePlugins.value(deviceClass.pluginId()); + if (!plugin) { + qWarning() << "Cannot find a plugin for this device class!"; + emit pairingFinished(pairingTransactionId, DeviceErrorPluginNotFound, deviceClass.pluginId().toString()); + return; + } + + DeviceId id = DeviceId::createDeviceId(); + Device *device = new Device(plugin->pluginId(), id, deviceClassId, this); + device->setName(deviceClass.name()); + device->setParams(params); + + QPair setupStatus = setupDevice(device); + switch (setupStatus.first) { + case DeviceSetupStatusFailure: + qWarning() << "Device setup failed. Not adding device to system."; + delete device; + emit pairingFinished(pairingTransactionId, DeviceErrorSetupFailed, QString("Device setup failed: %1").arg(setupStatus.second)); + return; + case DeviceSetupStatusAsync: + case DeviceSetupStatusSuccess: + qDebug() << "Device setup complete."; + break; + } + + m_configuredDevices.append(device); + storeConfiguredDevices(); + + emit pairingFinished(pairingTransactionId, DeviceErrorNoError, QString(), id); +} + void DeviceManager::slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value) { Device *device = qobject_cast(sender()); diff --git a/libguh/devicemanager.h b/libguh/devicemanager.h index fefceb69..ced75d46 100644 --- a/libguh/devicemanager.h +++ b/libguh/devicemanager.h @@ -59,7 +59,8 @@ public: DeviceErrorCreationMethodNotSupported, DeviceErrorActionParameterError, DeviceErrorDeviceDescriptorNotFound, - DeviceErrorAsync + DeviceErrorAsync, + DeviceErrorPairingTransactionIdNotFound, }; enum DeviceSetupStatus { @@ -82,6 +83,9 @@ public: QList configuredDevices() const; QPair addConfiguredDevice(const DeviceClassId &deviceClassId, const QList ¶ms, const DeviceId id = DeviceId::createDeviceId()); QPair addConfiguredDevice(const DeviceClassId &deviceClassId, const DeviceDescriptorId &deviceDescriptorId, const DeviceId &id = DeviceId::createDeviceId()); + QPair pairDevice(const DeviceClassId &deviceClassId, const QList ¶ms); + QPair pairDevice(const DeviceClassId &deviceClassId, const DeviceDescriptorId &deviceDescriptorId); + QPair confirmPairing(const QUuid &pairingTransactionId, const QString &secret = QString()); QPair removeConfiguredDevice(const DeviceId &deviceId); Device* findConfiguredDevice(const DeviceId &id) const; @@ -94,6 +98,7 @@ signals: void deviceStateChanged(Device *device, const QUuid &stateTypeId, const QVariant &value); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList &devices); void deviceSetupFinished(Device *device, DeviceError status, const QString &errorMessage); + void pairingFinished(const QUuid &pairingTransactionId, DeviceError status, const QString &errorMessage, const DeviceId &deviceId = DeviceId()); void actionExecutionFinished(const ActionId, DeviceError status, const QString &errorMessage); public slots: @@ -106,6 +111,7 @@ private slots: void createNewAutoDevices(); void slotDevicesDiscovered(const DeviceClassId &deviceClassId, const QList deviceDescriptors); void slotDeviceSetupFinished(Device *device, DeviceManager::DeviceSetupStatus status, const QString &errorMessage); + void slotPairingFinished(const QUuid &pairingTransactionId, DeviceManager::DeviceSetupStatus status, const QString &errorMessage); // Only connect this to Devices. It will query the sender() void slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value); @@ -128,7 +134,7 @@ private: QHash > m_vendorDeviceMap; QHash m_supportedDevices; QList m_configuredDevices; - QHash > m_discoveredDevices; + QHash m_discoveredDevices; QHash m_devicePlugins; @@ -139,6 +145,9 @@ private: QTimer m_pluginTimer; QList m_pluginTimerUsers; + QHash > > m_pairingsJustAdd; + QHash > m_pairingsDiscovery; + friend class DevicePlugin; }; Q_DECLARE_OPERATORS_FOR_FLAGS(DeviceManager::HardwareResources) diff --git a/libguh/plugin/deviceclass.cpp b/libguh/plugin/deviceclass.cpp index 26e4a5ec..4b65a8d7 100644 --- a/libguh/plugin/deviceclass.cpp +++ b/libguh/plugin/deviceclass.cpp @@ -189,6 +189,16 @@ void DeviceClass::setSetupMethod(DeviceClass::SetupMethod setupMethod) m_setupMethod = setupMethod; } +QString DeviceClass::pairingInfo() const +{ + return m_pairingInfo; +} + +void DeviceClass::setPairingInfo(const QString &pairingInfo) +{ + m_pairingInfo = pairingInfo; +} + /*! Compare this \a deviceClass to another. This is effectively the same as calling a.id() == b.id(). Returns true if the ids match.*/ bool DeviceClass::operator==(const DeviceClass &deviceClass) const { diff --git a/libguh/plugin/deviceclass.h b/libguh/plugin/deviceclass.h index 7ccc8964..d3d565ff 100644 --- a/libguh/plugin/deviceclass.h +++ b/libguh/plugin/deviceclass.h @@ -74,6 +74,9 @@ public: SetupMethod setupMethod() const; void setSetupMethod(SetupMethod setupMethod); + QString pairingInfo() const; + void setPairingInfo(const QString &pairingInfo); + bool operator==(const DeviceClass &device) const; private: @@ -89,6 +92,7 @@ private: QList m_discoveryParamTypes; CreateMethod m_createMethod; SetupMethod m_setupMethod; + QString m_pairingInfo; }; #endif diff --git a/libguh/plugin/devicedescriptor.h b/libguh/plugin/devicedescriptor.h index 40a32f9b..590c6f4a 100644 --- a/libguh/plugin/devicedescriptor.h +++ b/libguh/plugin/devicedescriptor.h @@ -53,4 +53,6 @@ private: QList m_params; }; +Q_DECLARE_METATYPE(DeviceDescriptor) + #endif // DEVICEDESCRIPTION_H diff --git a/libguh/plugin/deviceplugin.cpp b/libguh/plugin/deviceplugin.cpp index 0633c548..9d625e82 100644 --- a/libguh/plugin/deviceplugin.cpp +++ b/libguh/plugin/deviceplugin.cpp @@ -147,6 +147,11 @@ bool DevicePlugin::configureAutoDevice(QList loadedDevices, Device *dev return false; } +/*! Reimplement this if you support a DeviceClass with createMethod CreateMethodDiscovery. + This will be called to discover Devices for the given DeviceClass. This will always + be an async operation. Return DeviceErrorAsync or DeviceErrorNoError if the discovery + has been started successfully. Return an appropriate error otherwise. + Once devices are discovered, emit devicesDiscovered() once. */ DeviceManager::DeviceError DevicePlugin::discoverDevices(const DeviceClassId &deviceClassId, const QVariantMap ¶ms) const { Q_UNUSED(deviceClassId) @@ -177,6 +182,15 @@ void DevicePlugin::deviceRemoved(Device *device) Q_UNUSED(device) } +QPair DevicePlugin::confirmPairing(const QUuid &pairingTransactionId, const DeviceClassId &deviceClassId, const QList ¶ms) +{ + Q_UNUSED(pairingTransactionId) + Q_UNUSED(deviceClassId) + Q_UNUSED(params) + + return reportDeviceSetup(DeviceManager::DeviceSetupStatusFailure, "Plugin does not implement pairing."); +} + QList DevicePlugin::configurationDescription() const { return QList(); diff --git a/libguh/plugin/deviceplugin.h b/libguh/plugin/deviceplugin.h index 448efe25..062a027d 100644 --- a/libguh/plugin/deviceplugin.h +++ b/libguh/plugin/deviceplugin.h @@ -55,6 +55,8 @@ public: virtual QPair setupDevice(Device *device); virtual void deviceRemoved(Device *device); + virtual QPair confirmPairing(const QUuid &pairingTransactionId, const DeviceClassId &deviceClassId, const QList ¶ms); + // Hardware input virtual void radioData(QList rawData) {Q_UNUSED(rawData)} virtual void guhTimer() {} @@ -76,6 +78,7 @@ signals: void emitEvent(const Event &event); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); void deviceSetupFinished(Device *device, DeviceManager::DeviceSetupStatus status, const QString &errorMessage); + void pairingFinished(const QUuid &pairingTransactionId, DeviceManager::DeviceSetupStatus status, const QString &errorMessage); void actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status, const QString &errorMessage); void configValueChanged(const QString ¶mName, const QVariant &value); diff --git a/plugins/deviceplugins/deviceplugins.pro b/plugins/deviceplugins/deviceplugins.pro index bc49ad4f..71c9d2ba 100644 --- a/plugins/deviceplugins/deviceplugins.pro +++ b/plugins/deviceplugins/deviceplugins.pro @@ -9,6 +9,7 @@ SUBDIRS += elro \ lircd \ wakeonlan \ mailnotification \ + philipshue \ boblight { SUBDIRS += boblight diff --git a/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.cpp b/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.cpp index 5c08e16e..37946f0d 100644 --- a/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.cpp +++ b/plugins/deviceplugins/openweathermap/devicepluginopenweathermap.cpp @@ -428,10 +428,10 @@ DeviceManager::DeviceError DevicePluginOpenweathermap::discoverDevices(const Dev qDebug() << "should discover divces for" << deviceClassId << params.value("location").toString(); if(params.value("location").toString() == ""){ m_openweaher->searchAutodetect(); - return DeviceManager::DeviceErrorNoError; + return DeviceManager::DeviceErrorAsync; } m_openweaher->search(params.value("location").toString()); - return DeviceManager::DeviceErrorNoError; + return DeviceManager::DeviceErrorAsync; } QPair DevicePluginOpenweathermap::setupDevice(Device *device) diff --git a/plugins/deviceplugins/philipshue/devicepluginphilipshue.cpp b/plugins/deviceplugins/philipshue/devicepluginphilipshue.cpp new file mode 100644 index 00000000..174b8ce1 --- /dev/null +++ b/plugins/deviceplugins/philipshue/devicepluginphilipshue.cpp @@ -0,0 +1,242 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * 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 "devicepluginphilipshue.h" + +#include "plugin/device.h" +#include "devicemanager.h" +#include "types/param.h" +#include "huebridgeconnection.h" + +#include +#include +#include +#include +#include +#include +#include + +VendorId hueVendorId = VendorId("0ae1e001-2aa6-47ed-b8c0-334c3728a68f"); + +PluginId huePluginUuid = PluginId("5f2e634b-b7f3-48ee-976a-b5ae22aa5c55"); +DeviceClassId hueDeviceClassId = DeviceClassId("d8f4c397-e05e-47c1-8917-8e72d4d0d47c"); + +StateTypeId hueColorStateTypeId = StateTypeId("d25423e7-b924-4b20-80b6-77eecc65d089"); +ActionTypeId setHueColorActionTypeId = ActionTypeId("29cc299a-818b-47b2-817f-c5a6361545e4"); + +DevicePluginPhilipsHue::DevicePluginPhilipsHue(): + m_discovery(new Discovery(this)) +{ + connect(m_discovery, &Discovery::discoveryDone, this, &DevicePluginPhilipsHue::discoveryDone); + + m_nam = new QNetworkAccessManager(this); +} + +QList DevicePluginPhilipsHue::supportedVendors() const +{ + QList ret; + Vendor philips(hueVendorId, "Philips"); + ret.append(philips); + return ret; +} + +QList DevicePluginPhilipsHue::supportedDevices() const +{ + QList ret; + + DeviceClass deviceClassHue(pluginId(), hueVendorId, hueDeviceClassId); + deviceClassHue.setName("Hue"); + deviceClassHue.setCreateMethod(DeviceClass::CreateMethodDiscovery); + + deviceClassHue.setSetupMethod(DeviceClass::SetupMethodPushButton); + deviceClassHue.setPairingInfo("Please press the button on the Hue bridge and then press OK"); + + QList paramTypes; + ParamType ipParam("ip", QVariant::String); + paramTypes.append(ipParam); + deviceClassHue.setParamTypes(paramTypes); + + QList hueStates; + + StateType colorState(hueColorStateTypeId); + colorState.setName("color"); + colorState.setType(QVariant::Color); + colorState.setDefaultValue(QColor(Qt::black)); + hueStates.append(colorState); + + deviceClassHue.setStateTypes(hueStates); + + QList hueActons; + + ActionType setColorAction(setHueColorActionTypeId); + setColorAction.setName("Set color"); + + QList actionParamsSetColor; + ParamType actionParamSetColor("color", QVariant::Color); + actionParamsSetColor.append(actionParamSetColor); + setColorAction.setParameters(actionParamsSetColor); + + hueActons.append(setColorAction); + + deviceClassHue.setActions(hueActons); + + ret.append(deviceClassHue); + + return ret; +} + +DeviceManager::HardwareResources DevicePluginPhilipsHue::requiredHardware() const +{ + return DeviceManager::HardwareResourceNone; +} + +bool DevicePluginPhilipsHue::configureAutoDevice(QList loadedDevices, Device *device) const +{ +// if (!m_bobClient->connected()) { +// return false; +// } +// if (loadedDevices.count() < m_bobClient->lightsCount()) { +// int index = loadedDevices.count(); +// device->setName("Boblight Channel " + QString::number(index)); +// QList params; +// Param param("channel"); +// param.setValue(index); +// params.append(param); +// device->setParams(params); +// device->setStateValue(colorStateTypeId, m_bobClient->currentColor(index)); +// return true; +// } + return false; +} + +QString DevicePluginPhilipsHue::pluginName() const +{ + return "Philips Hue"; +} + +PluginId DevicePluginPhilipsHue::pluginId() const +{ + return huePluginUuid; +} + +QList DevicePluginPhilipsHue::configurationDescription() const +{ + QList params; + return params; +} + +DeviceManager::DeviceError DevicePluginPhilipsHue::discoverDevices(const DeviceClassId &deviceClassId, const QVariantMap ¶ms) const +{ + m_discovery->findBridges(4000); + return DeviceManager::DeviceErrorAsync; +} + +QPair DevicePluginPhilipsHue::confirmPairing(const QUuid &pairingTransactionId, const DeviceClassId &deviceClassId, const QList ¶ms) +{ + Param ipParam; + foreach (const Param ¶m, params) { + if (param.name() == "ip") { + ipParam = param; + } + } + if (!ipParam.isValid()) { + return reportDeviceSetup(DeviceManager::DeviceSetupStatusFailure, "Missing parameter: ip"); + } + + QString username = "guh-" + QUuid::createUuid().toString().remove(QRegExp("[\\{\\}]*")).remove(QRegExp("\\-[0-9a-f\\-]*")); + + QVariantMap createUserParams; + createUserParams.insert("devicetype", "guh"); + createUserParams.insert("username", username); + + QJsonDocument jsonDoc = QJsonDocument::fromVariant(createUserParams); + QByteArray data = jsonDoc.toJson(); + + QNetworkRequest request(QUrl("http://" + ipParam.value().toString() + "/api")); + QNetworkReply *reply = m_nam->post(request, data); + connect(reply, &QNetworkReply::finished, this, &DevicePluginPhilipsHue::createUserFinished); + + HueBridgeConnection *bridge = new HueBridgeConnection(QHostAddress(ipParam.value().toString()), username); + m_pairings.insert(reply, qMakePair(pairingTransactionId, bridge)); + return reportDeviceSetup(DeviceManager::DeviceSetupStatusAsync); +} + +QPair DevicePluginPhilipsHue::executeAction(Device *device, const Action &action) +{ +// if (!m_bobClient->connected()) { + return report(DeviceManager::DeviceErrorSetupFailed, device->id().toString()); +// } +// QColor newColor = action.param("color").value().value(); +// if (!newColor.isValid()) { +// return report(DeviceManager::DeviceErrorActionParameterError, "color"); +// } +// qDebug() << "executing boblight action" << newColor; +// m_bobClient->setColor(device->paramValue("channel").toInt(), newColor); +// m_bobClient->sync(); + +// device->setStateValue(colorStateTypeId, newColor); + // return report(); +} + +void DevicePluginPhilipsHue::discoveryDone(const QList &bridges) +{ + qDebug() << "discovered bridges" << bridges.count(); + QList deviceDescriptors; + foreach (const QHostAddress &bridge, bridges) { + DeviceDescriptor descriptor(hueDeviceClassId, "Philips Hue bridge", bridge.toString()); + QList params; + Param param("ip", bridge.toString()); + params.append(param); + descriptor.setParams(params); + deviceDescriptors.append(descriptor); + } + + emit devicesDiscovered(hueDeviceClassId, deviceDescriptors); +} + +void DevicePluginPhilipsHue::createUserFinished() +{ + QNetworkReply *reply = static_cast(sender()); + QByteArray data = reply->readAll(); + + QPair pair = m_pairings.take(reply); + QUuid pairingTransactionId = pair.first; + HueBridgeConnection *bridge = pair.second; + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + emit pairingFinished(pairingTransactionId, DeviceManager::DeviceSetupStatusFailure, "Pairing failed. Failed to parse response from Hue Bridge."); + delete bridge; + return; + } + + QVariantMap response = jsonDoc.toVariant().toList().first().toMap(); + + if (response.contains("error")) { + qDebug() << "Failed to pair Hue bridge:" << response.value("error").toMap().value("description"); + emit pairingFinished(pairingTransactionId, DeviceManager::DeviceSetupStatusFailure, "Pairing failed:" + response.value("error").toMap().value("description").toString()); + delete bridge; + return; + } + + emit pairingFinished(pairingTransactionId, DeviceManager::DeviceSetupStatusSuccess, QString()); + + m_bridges.append(bridge); + qDebug() << "response" << response << data; +} diff --git a/plugins/deviceplugins/philipshue/devicepluginphilipshue.h b/plugins/deviceplugins/philipshue/devicepluginphilipshue.h new file mode 100644 index 00000000..bac9d0a0 --- /dev/null +++ b/plugins/deviceplugins/philipshue/devicepluginphilipshue.h @@ -0,0 +1,71 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * 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 DEVICEPLUGINPHILIPSHUE_H +#define DEVICEPLUGINPHILIPSHUE_H + +#include "plugin/deviceplugin.h" +#include "discovery.h" +#include "huebridgeconnection.h" + +class QNetworkAccessManager; +class QNetworkReply; + +class DevicePluginPhilipsHue: public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginphilipshue.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginPhilipsHue(); + + QList supportedVendors() const override; + QList supportedDevices() const override; + DeviceManager::HardwareResources requiredHardware() const override; + + bool configureAutoDevice(QList loadedDevices, Device *device) const override; + + QString pluginName() const override; + PluginId pluginId() const override; + + QList configurationDescription() const override; + DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const QVariantMap ¶ms) const override; + + QPair confirmPairing(const QUuid &pairingTransactionId, const DeviceClassId &deviceClassId, const QList ¶ms) override; + +public slots: + QPair executeAction(Device *device, const Action &action); + +private slots: + void discoveryDone(const QList &bridges); + + void createUserFinished(); + +private: + QList m_config; + Discovery *m_discovery; + QNetworkAccessManager *m_nam; + + QHash > m_pairings; + + QList m_bridges; +}; + +#endif // DEVICEPLUGINBOBLIGHT_H diff --git a/plugins/deviceplugins/philipshue/devicepluginphilipshue.json b/plugins/deviceplugins/philipshue/devicepluginphilipshue.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/plugins/deviceplugins/philipshue/devicepluginphilipshue.json @@ -0,0 +1 @@ +{} diff --git a/plugins/deviceplugins/philipshue/discovery.cpp b/plugins/deviceplugins/philipshue/discovery.cpp new file mode 100644 index 00000000..b249fe36 --- /dev/null +++ b/plugins/deviceplugins/philipshue/discovery.cpp @@ -0,0 +1,86 @@ +/* + * Copyright 2013 Christian Muehlhaeuser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Authors: + * Christian Muehlhaeuser + */ + +#include "discovery.h" + +#include +#include + +Discovery::Discovery(QObject *parent) : + QUdpSocket(parent), + m_timeout(new QTimer(this)) +{ + quint16 port = 1900; + unsigned int tries = 0; + const unsigned int maxtries = 10; + + while (!bind(port++)) { + if (++tries == maxtries) { + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection); + return; + } + } + + connect(this, SIGNAL(readyRead()), this, SLOT(onReadyRead())); + + m_timeout->setSingleShot(true); + connect(m_timeout, SIGNAL(timeout()), this, SLOT(onTimeout())); +} + +bool Discovery::findBridges(int timeout) +{ + m_timeout->stop(); + m_reportedBridges.clear(); + + QString b("M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: %1\r\n" + "ST: libhue:idl\r\n"); + b.arg(timeout / 1000); + +// qDebug() << "writing datagram" << b; + m_timeout->start(timeout); + if (writeDatagram(b.toUtf8(), QHostAddress("239.255.255.250"), 1900) < 0) { + return false; + } + return true; +} + +void Discovery::onTimeout() +{ + emit discoveryDone(m_reportedBridges); +} + +void Discovery::onReadyRead() +{ + while (hasPendingDatagrams()) { + QByteArray datagram; + datagram.resize(pendingDatagramSize()); + QHostAddress sender; + quint16 senderPort; + + readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); + +// qDebug() << "got datagram" << datagram; + if (!m_reportedBridges.contains(sender)) { + m_reportedBridges << sender; + } + } +} diff --git a/plugins/deviceplugins/philipshue/discovery.h b/plugins/deviceplugins/philipshue/discovery.h new file mode 100644 index 00000000..4e1ac30e --- /dev/null +++ b/plugins/deviceplugins/philipshue/discovery.h @@ -0,0 +1,50 @@ +/* + * Copyright 2013 Christian Muehlhaeuser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; version 2. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Authors: + * Christian Muehlhaeuser + * Michael Zanetti + */ + +#ifndef DISCOVERY_H +#define DISCOVERY_H + +#include +#include + +class QTimer; + +class Discovery: public QUdpSocket +{ + Q_OBJECT + +public: + Discovery(QObject *parent); + bool findBridges(int timeout); + +signals: + void error(); + void discoveryDone(QList bridges); + +private slots: + void onTimeout(); + void onReadyRead(); + +private: + QList m_reportedBridges; + QTimer *m_timeout; +}; + +#endif diff --git a/plugins/deviceplugins/philipshue/huebridgeconnection.cpp b/plugins/deviceplugins/philipshue/huebridgeconnection.cpp new file mode 100644 index 00000000..3f29c70e --- /dev/null +++ b/plugins/deviceplugins/philipshue/huebridgeconnection.cpp @@ -0,0 +1,39 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * 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 "huebridgeconnection.h" + +#include + +HueBridgeConnection::HueBridgeConnection(const QHostAddress &address, const QString &username, QObject *parent) : + QObject(parent), + m_address(address), + m_username(username) +{ +} + +QString HueBridgeConnection::username() const +{ + return m_username; +} + +QHostAddress HueBridgeConnection::address() const +{ + return m_address; +} + diff --git a/plugins/deviceplugins/philipshue/huebridgeconnection.h b/plugins/deviceplugins/philipshue/huebridgeconnection.h new file mode 100644 index 00000000..962eb72e --- /dev/null +++ b/plugins/deviceplugins/philipshue/huebridgeconnection.h @@ -0,0 +1,39 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * 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 HUEBRIDGECONNECTION_H +#define HUEBRIDGECONNECTION_H + +#include +#include + +class HueBridgeConnection : public QObject +{ + Q_OBJECT +public: + explicit HueBridgeConnection(const QHostAddress &address, const QString &username = QString(), QObject *parent = 0); + + QHostAddress address() const; + QString username() const; + +private: + QHostAddress m_address; + QString m_username; +}; + +#endif // HUEBRIDGECONNECTION_H diff --git a/plugins/deviceplugins/philipshue/philipshue.pro b/plugins/deviceplugins/philipshue/philipshue.pro new file mode 100644 index 00000000..a62e7dce --- /dev/null +++ b/plugins/deviceplugins/philipshue/philipshue.pro @@ -0,0 +1,18 @@ +include(../../plugins.pri) + +TARGET = $$qtLibraryTarget(guh_devicepluginphilipshue) + +QT += network + +SOURCES += \ + devicepluginphilipshue.cpp \ + discovery.cpp \ + huebridgeconnection.cpp + +HEADERS += \ + devicepluginphilipshue.h \ + discovery.h \ + huebridgeconnection.h + + + diff --git a/server/jsonrpc/devicehandler.cpp b/server/jsonrpc/devicehandler.cpp index d1a0d8ee..5f1d466a 100644 --- a/server/jsonrpc/devicehandler.cpp +++ b/server/jsonrpc/devicehandler.cpp @@ -77,7 +77,12 @@ DeviceHandler::DeviceHandler(QObject *parent) : setReturns("SetPluginConfiguration", returns); params.clear(); returns.clear(); - setDescription("AddConfiguredDevice", "Add a configured device. Use deviceDescriptorId or deviceParams, depending on the createMethod of the device class."); + setDescription("AddConfiguredDevice", "Add a configured device with a setupMethod of SetupMethodJustAdd. " + "For devices with a setupMethod different than SetupMethodJustAdd, use PairDevice. " + "Use deviceDescriptorId or deviceParams, depending on the createMethod of the device class. " + "CreateMethodJustAdd takes the parameters you want to have with that device. " + "CreateMethodDiscovery requires the use of a deviceDescriptorId." + ); params.insert("deviceClassId", "uuid"); params.insert("o:deviceDescriptorId", "uuid"); QVariantList deviceParams; @@ -89,6 +94,34 @@ DeviceHandler::DeviceHandler(QObject *parent) : returns.insert("o:deviceId", "uuid"); setReturns("AddConfiguredDevice", returns); + returns.clear(); // Reused params from above! + setDescription("PairDevice", "Pair a device. " + "Use this for DeviceClasses with a setupMethod different than SetupMethodJustAdd." + "Use deviceDescriptorId or deviceParams, depending on the createMethod of the device class. " + "CreateMethodJustAdd takes the parameters you want to have with that device. " + "CreateMethodDiscovery requires the use of a deviceDescriptorId. " + "If success is true, the return values will contain a pairingTransactionId, a displayMessage and " + "the setupMethod. Depending on the setupMethod you should either proceed with AddConfiguredDevice " + " or PairDevice." + ); + setParams("PairDevice", params); + returns.insert("success", "bool"); + returns.insert("errorMessage", "string"); + returns.insert("o:pairingTransactionId", "uuid"); + returns.insert("o:displayMessage", "string"); + returns.insert("o:setupMethod", JsonTypes::setupMethodTypesRef()); + setReturns("PairDevice", returns); + + params.clear(); returns.clear(); + setDescription("ConfirmPairing", "Confirm an ongoing pairing. In case of SetupMethodEnterPin also provide the pin in the params."); + params.insert("pairingTransactionId", "uuid"); + params.insert("o:secret", "string"); + setParams("ConfirmPairing", params); + returns.insert("success", "bool"); + returns.insert("errorMessage", "string"); + returns.insert("o:deviceId", "uuid"); + setReturns("ConfirmPairing", returns); + params.clear(); returns.clear(); setDescription("GetConfiguredDevices", "Returns a list of configured devices."); setParams("GetConfiguredDevices", params); @@ -165,8 +198,9 @@ DeviceHandler::DeviceHandler(QObject *parent) : setParams("StateChanged", params); connect(GuhCore::instance()->deviceManager(), &DeviceManager::deviceStateChanged, this, &DeviceHandler::deviceStateChanged); - connect(GuhCore::instance()->deviceManager(), &DeviceManager::devicesDiscovered, this, &DeviceHandler::devicesDiscovered); + connect(GuhCore::instance()->deviceManager(), &DeviceManager::devicesDiscovered, this, &DeviceHandler::devicesDiscovered, Qt::QueuedConnection); connect(GuhCore::instance()->deviceManager(), &DeviceManager::deviceSetupFinished, this, &DeviceHandler::deviceSetupFinished); + connect(GuhCore::instance()->deviceManager(), &DeviceManager::pairingFinished, this, &DeviceHandler::pairingFinished); } QString DeviceHandler::name() const @@ -211,6 +245,7 @@ JsonReply *DeviceHandler::GetDiscoveredDevices(const QVariantMap ¶ms) const DeviceManager::DeviceError status = GuhCore::instance()->deviceManager()->discoverDevices(deviceClassId, params.value("discoveryParams").toMap()); switch (status) { + case DeviceManager::DeviceErrorAsync: case DeviceManager::DeviceErrorNoError: { JsonReply *reply = createAsyncReply("GetDiscoveredDevices"); m_discoverRequests.insert(deviceClassId, reply); @@ -352,6 +387,79 @@ JsonReply* DeviceHandler::AddConfiguredDevice(const QVariantMap ¶ms) return createReply(returns); } +JsonReply *DeviceHandler::PairDevice(const QVariantMap ¶ms) +{ + DeviceClassId deviceClassId(params.value("deviceClassId").toString()); + DeviceClass deviceClass = GuhCore::instance()->deviceManager()->findDeviceClass(deviceClassId); + + QPair status; + if (params.contains("deviceDescriptorId")) { + DeviceDescriptorId deviceDescriptorId(params.value("deviceDescriptorId").toString()); + status = GuhCore::instance()->deviceManager()->pairDevice(deviceClassId, deviceDescriptorId); + } else { + QList deviceParams; + foreach (const QString ¶mName, params.value("deviceParams").toMap().keys()) { + Param param(paramName); + param.setValue(params.value("deviceParams").toMap().value(paramName)); + deviceParams.append(param); + } + status = GuhCore::instance()->deviceManager()->pairDevice(deviceClassId, deviceParams); + } + + QVariantMap returns; + switch (status.first) { + case DeviceManager::DeviceErrorNoError: + returns.insert("success", true); + returns.insert("errorMessage", ""); + returns.insert("displayMessage", deviceClass.pairingInfo()); + returns.insert("pairingTransactionId", status.second); + returns.insert("setupMethod", JsonTypes::setupMethodTypes().at(deviceClass.setupMethod())); + break; + case DeviceManager::DeviceErrorDeviceClassNotFound: + returns.insert("errorMessage", QString("Error pairing device. Device class not found: %1").arg(status.second)); + returns.insert("success", false); + break; + case DeviceManager::DeviceErrorDeviceDescriptorNotFound: + returns.insert("errorMessage", QString("Error pairing device. Device descriptor not found: %1").arg(status.second)); + returns.insert("success", false); + break; + case DeviceManager::DeviceErrorCreationMethodNotSupported: + returns.insert("errorMessage", QString("Error pairing device. This device can't be created this way: %1").arg(status.second)); + returns.insert("success", false); + break; + case DeviceManager::DeviceErrorPairingTransactionIdNotFound: + returns.insert("errorMessage", QString("Error pairing device. PairingTransactionId not found: %1").arg(status.second)); + returns.insert("success", false); + break; + } + return createReply(returns); +} + +JsonReply *DeviceHandler::ConfirmPairing(const QVariantMap ¶ms) +{ + QUuid pairingTransactionId = params.value("pairingTransactionId").toUuid(); + QString secret = params.value("secret").toString(); + QPair status = GuhCore::instance()->deviceManager()->confirmPairing(pairingTransactionId, secret); + + JsonReply *reply = 0; + QVariantMap returns; + switch (status.first) { + case DeviceManager::DeviceErrorAsync: + reply = createAsyncReply("ConfirmPairing"); + m_asyncPairingRequests.insert(pairingTransactionId, reply); + return reply; + case DeviceManager::DeviceErrorNoError: + returns.insert("success", true); + returns.insert("errorMessage", QString()); + case DeviceManager::DeviceErrorSetupFailed: + default: + returns.insert("success", false); + returns.insert("errorMessage", status.second); + } + reply = createReply(returns); + return reply; +} + JsonReply* DeviceHandler::GetConfiguredDevices(const QVariantMap ¶ms) const { Q_UNUSED(params) @@ -503,3 +611,24 @@ void DeviceHandler::deviceSetupFinished(Device *device, DeviceManager::DeviceErr reply->setData(returns); reply->finished(); } + +void DeviceHandler::pairingFinished(const QUuid &pairingTransactionId, DeviceManager::DeviceError status, const QString &errorMessage, const DeviceId &deviceId) +{ + qDebug() << "handler: pairing finished"; + JsonReply *reply = m_asyncPairingRequests.take(pairingTransactionId); + if (!reply) { + qDebug() << "not for me"; + return; + } + QVariantMap returns; + if (status == DeviceManager::DeviceErrorNoError) { + returns.insert("success", true); + returns.insert("errorMessage", QString()); + returns.insert("deviceId", deviceId.toString()); + } else { + returns.insert("success", false); + returns.insert("errorMessage", errorMessage); + } + reply->setData(returns); + reply->finished(); +} diff --git a/server/jsonrpc/devicehandler.h b/server/jsonrpc/devicehandler.h index d43bd187..978f4e91 100644 --- a/server/jsonrpc/devicehandler.h +++ b/server/jsonrpc/devicehandler.h @@ -44,6 +44,10 @@ public: Q_INVOKABLE JsonReply* AddConfiguredDevice(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply* PairDevice(const QVariantMap ¶ms); + + Q_INVOKABLE JsonReply* ConfirmPairing(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply* GetConfiguredDevices(const QVariantMap ¶ms) const; Q_INVOKABLE JsonReply* RemoveConfiguredDevice(const QVariantMap ¶ms); @@ -66,10 +70,13 @@ private slots: void deviceSetupFinished(Device *device, DeviceManager::DeviceError status); + void pairingFinished(const QUuid &pairingTransactionId, DeviceManager::DeviceError status, const QString &errorMessage, const DeviceId &deviceId); + private: // A cache for async replies mutable QHash m_discoverRequests; mutable QHash m_asynDeviceAdditions; + mutable QHash m_asyncPairingRequests; }; #endif // DEVICEHANDLER_H diff --git a/server/jsonrpc/jsonrpcserver.cpp b/server/jsonrpc/jsonrpcserver.cpp index 8a3978d8..399d4492 100644 --- a/server/jsonrpc/jsonrpcserver.cpp +++ b/server/jsonrpc/jsonrpcserver.cpp @@ -42,7 +42,7 @@ #include #include -#define JSON_PROTOCOL_VERSION 1 +#define JSON_PROTOCOL_VERSION 2 JsonRPCServer::JsonRPCServer(QObject *parent): JsonHandler(parent), diff --git a/server/main.cpp b/server/main.cpp index bca4affd..38cd4b99 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -31,6 +31,7 @@ Q_IMPORT_PLUGIN(DevicePluginOpenweathermap) Q_IMPORT_PLUGIN(DevicePluginLircd) Q_IMPORT_PLUGIN(DevicePluginWakeOnLan) Q_IMPORT_PLUGIN(DevicePluginMailNotification) +Q_IMPORT_PLUGIN(DevicePluginPhilipsHue) #if USE_BOBLIGHT Q_IMPORT_PLUGIN(DevicePluginBoblight) diff --git a/server/ruleengine.cpp b/server/ruleengine.cpp index 9e69094d..72acf67d 100644 --- a/server/ruleengine.cpp +++ b/server/ruleengine.cpp @@ -144,7 +144,10 @@ RuleEngine::RuleEngine(QObject *parent) : list of all \l{Action}{Actions} that should be executed. */ QList RuleEngine::evaluateEvent(const Event &event) { - qDebug() << "got event:" << event; + Device *device = GuhCore::instance()->deviceManager()->findConfiguredDevice(event.deviceId()); + + qDebug() << "got event:" << event << device->name(); + QList actions; for (int i = 0; i < m_rules.count(); ++i) { qDebug() << "evaluating rule" << i << m_rules.at(i).eventDescriptors(); diff --git a/server/server.pro b/server/server.pro index f2c75a30..f17a03d0 100644 --- a/server/server.pro +++ b/server/server.pro @@ -29,6 +29,7 @@ LIBS += -L../plugins/deviceplugins/openweathermap -lguh_devicepluginopenweatherm LIBS += -L../plugins/deviceplugins/lircd -lguh_devicepluginlircd LIBS += -L../plugins/deviceplugins/mailnotification -lguh_devicepluginmailnotification LIBS += -L../plugins/deviceplugins/wakeonlan -lguh_devicepluginwakeonlan +LIBS += -L../plugins/deviceplugins/philipshue -lguh_devicepluginphilipshue boblight { xcompile { diff --git a/tests/auto/api.json b/tests/auto/api.json index a44a084a..e40257ab 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -1 +2 { "methods": { "Actions.ExecuteAction": { @@ -33,7 +33,7 @@ } }, "Devices.AddConfiguredDevice": { - "description": "Add a configured device. Use deviceDescriptorId or deviceParams, depending on the createMethod of the device class.", + "description": "Add a configured device with a setupMethod of SetupMethodJustAdd. For devices with a setupMethod different than SetupMethodJustAdd, use PairDevice. Use deviceDescriptorId or deviceParams, depending on the createMethod of the device class. CreateMethodJustAdd takes the parameters you want to have with that device. CreateMethodDiscovery requires the use of a deviceDescriptorId.", "params": { "deviceClassId": "uuid", "o:deviceDescriptorId": "uuid", @@ -47,6 +47,18 @@ "success": "bool" } }, + "Devices.ConfirmPairing": { + "description": "Confirm an ongoing pairing. In case of SetupMethodEnterPin also provide the pin in the params.", + "params": { + "o:secret": "string", + "pairingTransactionId": "uuid" + }, + "returns": { + "errorMessage": "string", + "o:deviceId": "uuid", + "success": "bool" + } + }, "Devices.GetActionTypes": { "description": "Get action types for a specified deviceClassId.", "params": { @@ -162,6 +174,23 @@ ] } }, + "Devices.PairDevice": { + "description": "Pair a device. Use this for DeviceClasses with a setupMethod different than SetupMethodJustAdd.Use deviceDescriptorId or deviceParams, depending on the createMethod of the device class. CreateMethodJustAdd takes the parameters you want to have with that device. CreateMethodDiscovery requires the use of a deviceDescriptorId. If success is true, the return values will contain a pairingTransactionId, a displayMessage and the setupMethod. Depending on the setupMethod you should either proceed with AddConfiguredDevice or PairDevice.", + "params": { + "deviceClassId": "uuid", + "o:deviceDescriptorId": "uuid", + "o:deviceParams": [ + "$ref:Param" + ] + }, + "returns": { + "errorMessage": "string", + "o:displayMessage": "string", + "o:pairingTransactionId": "uuid", + "o:setupMethod": "$ref:SetupMethodType", + "success": "bool" + } + }, "Devices.RemoveConfiguredDevice": { "description": "Remove a device from the system.", "params": { diff --git a/tests/scripts/cmdmgr.py b/tests/scripts/cmdmgr.py new file mode 100755 index 00000000..60388ca0 --- /dev/null +++ b/tests/scripts/cmdmgr.py @@ -0,0 +1,225 @@ +#!/usr/bin/python + +import telnetlib +import json + +HOST='localhost' +PORT=1234 +commandId=0 + +methods = {'List supported Vendors': 'list_vendors', + 'List supported Devices': 'list_deviceClasses', + 'List configured Devices': 'list_configured_devices', + 'Add Device': 'add_device', + 'Remove a device': 'remove_device', + 'List supported Devices by vendor': 'list_deviceClasses_by_vendor' } + + +def get_selection(title, options): + print "\n\n", title + for i in range(0,len(options)): + print "%i: %s" % (i, options[i]) + selection = raw_input("Enter selection: ") + return int(selection) + +def send_command(method, params = None): + global commandId + if params == None or len(params) == 0: + command = '{"id": %i, "method":"%s"}\n' % (commandId, method) + else: + paramString = "" + for i in range(0, len(params)): + if paramString != "": + paramString = paramString + ", " + paramKey = params.keys()[i] + paramString = paramString + ('"%s":"%s"' % (paramKey, params[paramKey].encode('utf-8'))) + command = '{"id": %i, "method":"%s", "params":{%s}}\n' % (commandId, method, paramString) + + commandId = commandId + 1 + tn.write(command) + response = json.loads(tn.read_until("\n}\n")) + if response['status'] != "success": + print "JSON error happened: %s" % response + return response + +def get_vendors(): + return send_command("Devices.GetSupportedVendors") + +def list_vendors(): + response = get_vendors(); + print "=== Vendors ===" + for vendor in response['params']['vendors']: + print "%40s %s" % (vendor['name'], vendor['id']) + print "=== Vendors ===" + +def select_vendor(): + vendors = get_vendors()['params']['vendors'] + vendorList = [] + vendorIdList = [] + for i in range(0,len(vendors)): + vendorList.append(vendors[i]['name']) + vendorIdList.append(vendors[i]['id']) + selection = get_selection("Please select vendor", vendorList) + return vendorIdList[selection] + +def get_deviceClasses(vendorId = None): + params = {}; + if vendorId != None: + params['vendorId'] = vendorId + return send_command("Devices.GetSupportedDevices", params)['params']['deviceClasses'] + +def list_deviceClasses(vendorId = None): + response = get_deviceClasses(vendorId) + print "=== Devices ===" + for deviceClass in response: + print "%40s %s" % (deviceClass['name'], deviceClass['id']) + print "=== Devices ===" + +def select_deviceClass(): + vendorId = select_vendor() + deviceClasses = get_deviceClasses(vendorId) + if len(deviceClasses) == 0: + print "No supported devices for this vendor" + return "" + deviceClassList = [] + deviceClassIdList = [] + for i in range(0,len(deviceClasses)): + deviceClassList.append(deviceClasses[i]['name']) + deviceClassIdList.append(deviceClasses[i]['id']) + selection = get_selection("Please select device class", deviceClassList) + return deviceClassIdList[selection] + +def list_deviceClasses_by_vendor(): + vendorId = select_vendor() + list_deviceClasses(vendorId) + +def get_configured_devices(): + return send_command("Devices.GetConfiguredDevices")['params']['devices'] + +def list_configured_devices(): + deviceList = get_configured_devices() + print "=== Configured Devices ===" + for device in deviceList: + print "Name: %40s, ID: %s, DeviceClassID: %s" % (device['name'], device['id'], device['deviceClassId']) + print "=== Configured Devices ===" + + +def discover_device(deviceClassId = None): + if deviceClassId == None: + deviceClassId = select_deviceClass() + params = {} + params['deviceClassId'] = deviceClassId + print "\ndiscovering..." + response = send_command("Devices.GetDiscoveredDevices", params) + deviceDescriptorList = []; + deviceDescriptorIdList = []; + for deviceDescriptor in response['params']['deviceDescriptors']: + deviceDescriptorList.append("%s (%s)" % (deviceDescriptor['title'], deviceDescriptor['description'])) + deviceDescriptorIdList.append(deviceDescriptor['id']) + selection = get_selection("Please select a device descriptor", deviceDescriptorList) + return deviceDescriptorIdList[selection] + +def get_deviceClass(deviceClassId): + deviceClasses = get_deviceClasses() + for deviceClass in deviceClasses: +# print "got deviceclass", deviceClass + if deviceClass['id'] == deviceClassId: + return deviceClass + return None + +def add_configured_device(deviceClassId): + params = {} + params['deviceClassId'] = deviceClassId + response = send_command("Devices.AddConfiguredDevice", params) + if response['params']['success'] != "true": + print "Error executing method: %s" % response['params']['errorMessage'] + return + print "Added device: %s" % response['params']['deviceId'] + +def add_discovered_device(deviceClassId, deviceDescriptorId): + params = {} + params['deviceClassId'] = deviceClassId + params['deviceDescriptorId'] = deviceDescriptorId + + deviceClass = get_deviceClass(deviceClassId) + if deviceClass['setupMethod'] == "SetupMethodJustAdd": + response = send_command("Devices.AddConfiguredDevice", params) + if not response['params']['success']: + print "Adding device failed: %s" % response['params']['errorMessage'] + else: + print "Device added successfully. Device ID: %s" % response['params']['deviceId'] + else: + params = {} + params['deviceClassId'] = deviceClassId + params['deviceDescriptorId'] = deviceDescriptorId + response = send_command("Devices.PairDevice", params) + print "pairdevice response:", response + if not response['params']['success']: + print "Pairing failed: %s", response['params']['errorMessage'] + return + else: + print "\nPairing device %s\n\n%s" % (deviceClass['name'], response['params']['displayMessage']) + if response['params']['setupMethod'] == "SetupMethodPushButton": + raw_input("Press enter to confirm") + + params = {} + params['pairingTransactionId'] = response['params']['pairingTransactionId'] + response = send_command("Devices.ConfirmPairing", params) + if response['params']['success']: + success = True + print "Device paired successfully" + else: + print "Error pairing device: %s" % response['params']['errorMessage'] + + +def add_device(): + deviceClassId = select_deviceClass() + if deviceClassId == "": + print "Empty deviceClass. Can't continue" + return + deviceClass = get_deviceClass(deviceClassId) + print "createmethod is", deviceClass['createMethod'] + if deviceClass['createMethod'] == "CreateMethodUser": + add_configured_device(deviceClassId) + elif deviceClass['createMethod'] == "CreateMethodDiscovery": + deviceDescriptorId = discover_device(deviceClassId) + add_discovered_device(deviceClassId, deviceDescriptorId) + elif deviceClass['createMethod'] == "CreateMethodAuto": + print "Can't create this device manually. It'll be created automatically when hardware is discovered." + +def select_device(): + devices = get_configured_devices() + deviceList = [] + deviceIdList = [] + for i in range(len(devices)): + deviceList.append(devices[i]['name']) + deviceIdList.append(devices[i]['id']) + selection = get_selection("Please select a device", deviceList) + return deviceIdList[selection] + +def remove_device(): + deviceId = select_device() + print "should remove device", deviceId + params = {} + params['deviceId'] = deviceId + response = send_command("Devices.RemoveConfiguredDevice", params) + if response['params']['success']: + print "Successfully deleted device" + else: + print "Error deleting device %s" % deviceId + + +tn = telnetlib.Telnet(HOST, PORT) +packet = tn.read_until("\n}\n") + +packet = json.loads(packet) +print "connected to", packet["server"], "\nserver version:", packet["version"], "\nprotocol version:", packet["protocol version"], "\n" + +while True: + selection = get_selection("What do you want to do?", methods.keys()) + selectionKey = methods.keys() + methodName = methods[methods.keys()[selection]] + methodToCall = globals()[methods[methods.keys()[selection]]] + methodToCall() + +