From fcdf3e1e0b19a6708811cbea72900842434ad4b7 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 22 Jun 2014 00:04:30 +0200 Subject: [PATCH] more work on the Hue plugin --- libguh/devicemanager.cpp | 17 +- libguh/plugin/deviceplugin.h | 1 + .../philipshue/devicepluginphilipshue.cpp | 177 +++++--- .../philipshue/devicepluginphilipshue.h | 24 +- .../philipshue/huebridgeconnection.cpp | 114 ++++- .../philipshue/huebridgeconnection.h | 32 +- plugins/deviceplugins/philipshue/light.cpp | 422 ++++++++++++++++++ plugins/deviceplugins/philipshue/light.h | 129 ++++++ .../deviceplugins/philipshue/philipshue.pro | 7 +- server/jsonrpc/actionhandler.cpp | 12 + server/jsonrpc/devicehandler.cpp | 13 +- 11 files changed, 863 insertions(+), 85 deletions(-) create mode 100644 plugins/deviceplugins/philipshue/light.cpp create mode 100644 plugins/deviceplugins/philipshue/light.h diff --git a/libguh/devicemanager.cpp b/libguh/devicemanager.cpp index d333d306..81dacc55 100644 --- a/libguh/devicemanager.cpp +++ b/libguh/devicemanager.cpp @@ -456,6 +456,7 @@ DeviceClass DeviceManager::findDeviceClass(const DeviceClassId &deviceClassId) c its \l{DevicePlugin}. Then will dispatch the execution to the \l{DevicePlugin}.*/ QPair DeviceManager::executeAction(const Action &action) { + qDebug() << "should execute action"; foreach (Device *device, m_configuredDevices) { if (action.deviceId() == device->id()) { // found device @@ -464,6 +465,7 @@ QPair DeviceManager::executeAction(const Ac DeviceClass deviceClass = findDeviceClass(device->deviceClassId()); bool found = false; foreach (const ActionType &actionType, deviceClass.actionTypes()) { + qDebug() << "checking" << actionType.id() << action.actionTypeId(); if (actionType.id() == action.actionTypeId()) { QPair paramCheck = verifyParams(actionType.parameters(), action.params()); if (paramCheck.first != DeviceErrorNoError) { @@ -736,7 +738,12 @@ void DeviceManager::slotPairingFinished(const QUuid &pairingTransactionId, Devic return; } + // Ok... pairing went fine... Let consumers know about it and inform them about the ongoing setup with a deviceId. DeviceId id = DeviceId::createDeviceId(); + emit pairingFinished(pairingTransactionId, DeviceErrorNoError, QString(), id); + + QList newDevices; + QString setupErrorMessage; Device *device = new Device(plugin->pluginId(), id, deviceClassId, this); device->setName(deviceClass.name()); device->setParams(params); @@ -745,19 +752,22 @@ void DeviceManager::slotPairingFinished(const QUuid &pairingTransactionId, Devic switch (setupStatus.first) { case DeviceSetupStatusFailure: qWarning() << "Device setup failed. Not adding device to system."; + setupErrorMessage = setupStatus.second; + emit deviceSetupFinished(device, DeviceError::DeviceErrorSetupFailed, QString("Device setup failed: %1").arg(errorMessage)); delete device; - emit pairingFinished(pairingTransactionId, DeviceErrorSetupFailed, QString("Device setup failed: %1").arg(setupStatus.second)); - return; + break; case DeviceSetupStatusAsync: + return; case DeviceSetupStatusSuccess: qDebug() << "Device setup complete."; + newDevices.append(id); break; } m_configuredDevices.append(device); storeConfiguredDevices(); - emit pairingFinished(pairingTransactionId, DeviceErrorNoError, QString(), id); + emit deviceSetupFinished(device, DeviceError::DeviceErrorNoError, QString()); } void DeviceManager::slotDeviceStateValueChanged(const QUuid &stateTypeId, const QVariant &value) @@ -842,6 +852,7 @@ QPair DeviceManager::setupDevice(Devic QPair DeviceManager::verifyParams(const QList paramTypes, const QList ¶ms, bool requireAll) { foreach (const Param ¶m, params) { + qDebug() << "verifying param" << param.name() << paramTypes; QPair result = verifyParam(paramTypes, param); if (result.first != DeviceErrorNoError) { return result; diff --git a/libguh/plugin/deviceplugin.h b/libguh/plugin/deviceplugin.h index 062a027d..ad8f697b 100644 --- a/libguh/plugin/deviceplugin.h +++ b/libguh/plugin/deviceplugin.h @@ -81,6 +81,7 @@ signals: 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); + void autoDevicesAppeared(const DeviceClassId &deviceClassId, const QList &deviceDescriptors); protected: DeviceManager *deviceManager() const; diff --git a/plugins/deviceplugins/philipshue/devicepluginphilipshue.cpp b/plugins/deviceplugins/philipshue/devicepluginphilipshue.cpp index fe7bdb71..04d6c9d5 100644 --- a/plugins/deviceplugins/philipshue/devicepluginphilipshue.cpp +++ b/plugins/deviceplugins/philipshue/devicepluginphilipshue.cpp @@ -25,10 +25,6 @@ #include #include -#include -#include -#include -#include #include VendorId hueVendorId = VendorId("0ae1e001-2aa6-47ed-b8c0-334c3728a68f"); @@ -37,14 +33,19 @@ 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"); +ActionTypeId hueSetColorActionTypeId = ActionTypeId("29cc299a-818b-47b2-817f-c5a6361545e4"); + +StateTypeId huePowerStateTypeId = StateTypeId("6ac64eee-f356-4ae4-bc85-8c1244d12b02"); +ActionTypeId hueSetPowerActionTypeId = ActionTypeId("7782d91e-d73a-4321-8828-da768e2f6827"); DevicePluginPhilipsHue::DevicePluginPhilipsHue(): m_discovery(new Discovery(this)) { connect(m_discovery, &Discovery::discoveryDone, this, &DevicePluginPhilipsHue::discoveryDone); - m_nam = new QNetworkAccessManager(this); + m_bridge = new HueBridgeConnection(this); + connect(m_bridge, &HueBridgeConnection::createUserFinished, this, &DevicePluginPhilipsHue::createUserFinished); + connect(m_bridge, &HueBridgeConnection::getFinished, this, &DevicePluginPhilipsHue::getFinished); } QList DevicePluginPhilipsHue::supportedVendors() const @@ -71,6 +72,8 @@ QList DevicePluginPhilipsHue::supportedDevices() const paramTypes.append(ipParam); ParamType usernameParam("username", QVariant::String); paramTypes.append(usernameParam); + ParamType numberParam("number", QVariant::Int, -1); + paramTypes.append(numberParam); deviceClassHue.setParamTypes(paramTypes); QList hueStates; @@ -81,20 +84,32 @@ QList DevicePluginPhilipsHue::supportedDevices() const colorState.setDefaultValue(QColor(Qt::black)); hueStates.append(colorState); + StateType powerState(huePowerStateTypeId); + powerState.setName("power"); + powerState.setType(QVariant::Bool); + powerState.setDefaultValue(false); + hueStates.append(powerState); + deviceClassHue.setStateTypes(hueStates); QList hueActons; - ActionType setColorAction(setHueColorActionTypeId); + ActionType setColorAction(hueSetColorActionTypeId); setColorAction.setName("Set color"); - QList actionParamsSetColor; ParamType actionParamSetColor("color", QVariant::Color); actionParamsSetColor.append(actionParamSetColor); setColorAction.setParameters(actionParamsSetColor); - hueActons.append(setColorAction); + ActionType setPowerAction(hueSetPowerActionTypeId); + setPowerAction.setName("Power"); + QList actionParamsSetPower; + ParamType actionParamSetPower("power", QVariant::Bool); + actionParamsSetPower.append(actionParamSetPower); + setPowerAction.setParameters(actionParamsSetPower); + hueActons.append(setPowerAction); + deviceClassHue.setActions(hueActons); ret.append(deviceClassHue); @@ -151,7 +166,30 @@ DeviceManager::DeviceError DevicePluginPhilipsHue::discoverDevices(const DeviceC QPair DevicePluginPhilipsHue::setupDevice(Device *device) { qDebug() << "setupDevice" << device->params(); - return reportDeviceSetup(); + + Light *light = nullptr; + + // Lets see if this a a newly added device... In which case its hue id number is not set, well, -1... + if (device->paramValue("number").toInt() == -1) { + if (m_unconfiguredLights.count() > 0) { + light = m_unconfiguredLights.takeFirst(); + device->setParamValue("number", light->id()); + } else { + // this shouldn't ever happen + return reportDeviceSetup(DeviceManager::DeviceSetupStatusFailure, "Device not configured yet and no discovered devices around."); + } + } else { + // In this case it most likely comes from the config. Just read all values from there... + light = new Light(QHostAddress(device->paramValue("ip").toString()), device->paramValue("username").toString(), device->paramValue("number").toInt()); + } + + connect(light, &Light::stateChanged, this, &DevicePluginPhilipsHue::lightStateChanged); + light->refresh(); + + m_lights.insert(light, device); + m_asyncSetups.insert(light, device); + + return reportDeviceSetup(DeviceManager::DeviceSetupStatusAsync); } QPair DevicePluginPhilipsHue::confirmPairing(const QUuid &pairingTransactionId, const DeviceClassId &deviceClassId, const QList ¶ms) @@ -175,36 +213,30 @@ QPair DevicePluginPhilipsHue::confirm return reportDeviceSetup(DeviceManager::DeviceSetupStatusFailure, "Missing parameter: username"); } - QVariantMap createUserParams; - createUserParams.insert("devicetype", "guh"); - createUserParams.insert("username", usernameParam.value().toString()); - - 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); - - m_pairings.insert(reply, pairingTransactionId); + int id = m_bridge->createUser(QHostAddress(ipParam.value().toString()), usernameParam.value().toString()); + PairingInfo pi; + pi.pairingTransactionId = pairingTransactionId; + pi.ipParam = ipParam; + pi.usernameParam = usernameParam; + m_pairings.insert(id, pi); 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(); + qDebug() << "Should execute action in hue plugin"; -// device->setStateValue(colorStateTypeId, newColor); - // return report(); + Light *light = m_lights.key(device); + if (!light) { + return report(DeviceManager::DeviceErrorDeviceNotFound, device->id().toString()); + } + + if (action.actionTypeId() == hueSetColorActionTypeId) { + light->setColor(action.param("color").value().value()); + } else if (action.actionTypeId() == hueSetPowerActionTypeId) { + light->setOn(action.param("power").value().toBool()); + } + return report(); } void DevicePluginPhilipsHue::discoveryDone(const QList &bridges) @@ -218,6 +250,8 @@ void DevicePluginPhilipsHue::discoveryDone(const QList &bridges) params.append(param); Param userParam("username", "guh-" + QUuid::createUuid().toString().remove(QRegExp("[\\{\\}]*")).remove(QRegExp("\\-[0-9a-f\\-]*"))); params.append(userParam); + Param numberParam("number", -1); + params.append(numberParam); descriptor.setParams(params); deviceDescriptors.append(descriptor); } @@ -225,28 +259,69 @@ void DevicePluginPhilipsHue::discoveryDone(const QList &bridges) emit devicesDiscovered(hueDeviceClassId, deviceDescriptors); } -void DevicePluginPhilipsHue::createUserFinished() +void DevicePluginPhilipsHue::createUserFinished(int id, const QVariantMap &response) { - QNetworkReply *reply = static_cast(sender()); - QByteArray data = reply->readAll(); - - QUuid pairingTransactionId = m_pairings.take(reply); - - 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."); - return; - } - - QVariantMap response = jsonDoc.toVariant().toList().first().toMap(); + qDebug() << "createuser response" << response; + PairingInfo pairingInfo = m_pairings.take(id); 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()); + emit pairingFinished(pairingInfo.pairingTransactionId, DeviceManager::DeviceSetupStatusFailure, "Pairing failed:" + response.value("error").toMap().value("description").toString()); return; } - emit pairingFinished(pairingTransactionId, DeviceManager::DeviceSetupStatusSuccess, QString()); - qDebug() << "response" << response << data; + // Paired successfully, check how many lightbulbs there are + + int getLightsId = m_bridge->get(QHostAddress(pairingInfo.ipParam.value().toString()), pairingInfo.usernameParam.value().toString(), "lights", this, "getLightsFinished"); + m_pairings.insert(getLightsId, pairingInfo); + +} + +void DevicePluginPhilipsHue::getLightsFinished(int id, const QVariantMap ¶ms) +{ + qDebug() << "getlightsfinished" << params; + PairingInfo pairingInfo = m_pairings.take(id); + + if (params.count() == 0) { + qWarning() << "No light bulbs found on this hue bridge... Cannot proceed with pairing."; + emit pairingFinished(pairingInfo.pairingTransactionId, DeviceManager::DeviceSetupStatusFailure, "No light bulbs found on this Hue bridge."); + return; + } + + // Store a list of all known Lights + foreach (const QString &lightId, params.keys()) { + Light *light = new Light(QHostAddress(pairingInfo.ipParam.value().toString()), pairingInfo.usernameParam.value().toString(), lightId.toInt(), this); + m_unconfiguredLights.insert(lightId.toInt(), light); + } + + emit pairingFinished(pairingInfo.pairingTransactionId, DeviceManager::DeviceSetupStatusSuccess, QString()); + +// // If we have more than one device on that bridge, tell DeviceManager that there are more. +// if (params.count() > 1) { +// emit autoDevicesAppeared(); +// } +} + +void DevicePluginPhilipsHue::getFinished(int id, const QVariantMap ¶ms) +{ + qDebug() << "got lights" << params; +} + +void DevicePluginPhilipsHue::lightStateChanged() +{ + Light *light = static_cast(sender()); + + Device *device; + if (m_asyncSetups.contains(light)) { + device = m_asyncSetups.take(light); + device->setName(light->name()); + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess, QString()); + } else { + device = m_lights.value(light); + } + if (!device) { + return; + } + device->setStateValue(hueColorStateTypeId, QVariant::fromValue(light->color())); + device->setStateValue(huePowerStateTypeId, light->on()); } diff --git a/plugins/deviceplugins/philipshue/devicepluginphilipshue.h b/plugins/deviceplugins/philipshue/devicepluginphilipshue.h index 07624f72..06d8ae53 100644 --- a/plugins/deviceplugins/philipshue/devicepluginphilipshue.h +++ b/plugins/deviceplugins/philipshue/devicepluginphilipshue.h @@ -22,8 +22,8 @@ #include "plugin/deviceplugin.h" #include "discovery.h" #include "huebridgeconnection.h" +#include "light.h" -class QNetworkAccessManager; class QNetworkReply; class DevicePluginPhilipsHue: public DevicePlugin @@ -58,14 +58,30 @@ public slots: private slots: void discoveryDone(const QList &bridges); - void createUserFinished(); + void createUserFinished(int id, const QVariantMap ¶ms); + void getLightsFinished(int id, const QVariantMap ¶ms); + void getFinished(int id, const QVariantMap ¶ms); + + void lightStateChanged(); private: QList m_config; Discovery *m_discovery; - QNetworkAccessManager *m_nam; - QHash m_pairings; + class PairingInfo { + public: + QUuid pairingTransactionId; + Param ipParam; + Param usernameParam; + }; + + QHash m_pairings; + HueBridgeConnection *m_bridge; + + QList m_unconfiguredLights; + QHash m_lights; + + QHash m_asyncSetups; }; #endif // DEVICEPLUGINBOBLIGHT_H diff --git a/plugins/deviceplugins/philipshue/huebridgeconnection.cpp b/plugins/deviceplugins/philipshue/huebridgeconnection.cpp index 3f29c70e..377013f9 100644 --- a/plugins/deviceplugins/philipshue/huebridgeconnection.cpp +++ b/plugins/deviceplugins/philipshue/huebridgeconnection.cpp @@ -18,22 +18,118 @@ #include "huebridgeconnection.h" -#include +#include +#include +#include +#include -HueBridgeConnection::HueBridgeConnection(const QHostAddress &address, const QString &username, QObject *parent) : - QObject(parent), - m_address(address), - m_username(username) +HueBridgeConnection::HueBridgeConnection(QObject *parent) : + QObject(parent) { + m_nam = new QNetworkAccessManager(this); } -QString HueBridgeConnection::username() const +int HueBridgeConnection::createUser(const QHostAddress &address, const QString &username) { - return m_username; + QVariantMap createUserParams; + createUserParams.insert("devicetype", "guh"); + createUserParams.insert("username", username); + + QJsonDocument jsonDoc = QJsonDocument::fromVariant(createUserParams); + QByteArray data = jsonDoc.toJson(); + + QNetworkRequest request(QUrl("http://" + address.toString() + "/api")); + QNetworkReply *reply = m_nam->post(request, data); + connect(reply, &QNetworkReply::finished, this, &HueBridgeConnection::slotCreateUserFinished); + + m_createUserMap.insert(reply, m_requestCounter); + return m_requestCounter++; } -QHostAddress HueBridgeConnection::address() const +int HueBridgeConnection::get(const QHostAddress &address, const QString &username, const QString &path, QObject *caller, const QString &methodName) { - return m_address; + QString baseUrl = "http://" + address.toString() + "/api/" + username + "/"; + QUrl url(baseUrl + path); + + QNetworkRequest request; + request.setUrl(url); + QNetworkReply *reply = m_nam->get(request); + connect(reply, &QNetworkReply::finished, this, &HueBridgeConnection::slotGetFinished); + + Caller c; + c.obj = caller; + c.method = methodName; + c.id = m_requestCounter; + + m_requestMap.insert(reply, c); + return m_requestCounter++; } +int HueBridgeConnection::put(const QHostAddress &address, const QString &username, const QString &path, const QVariantMap &data, QObject *caller, const QString &methodName) +{ + QString baseUrl = "http://" + address.toString() + "/api/" + username + "/"; + QUrl url(baseUrl + path); + QNetworkRequest request; + request.setUrl(url); + + QJsonDocument jsonDoc = QJsonDocument::fromVariant(data); + QByteArray jsonData = jsonDoc.toJson(); + qDebug() << "putting" << url << jsonData; + + QNetworkReply *reply = m_nam->put(request, jsonData); + connect(reply, &QNetworkReply::finished, this, &HueBridgeConnection::slotGetFinished); + + Caller c; + c.obj = caller; + c.method = methodName; + c.id = m_requestCounter; + m_requestMap.insert(reply, c); + + return m_requestCounter++; +} + +void HueBridgeConnection::slotCreateUserFinished() +{ + QNetworkReply *reply = static_cast(sender()); + QByteArray data = reply->readAll(); + int id = m_createUserMap.take(reply); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + QVariantMap params; + QVariantMap errorMap; + errorMap.insert("description", "Failed to parse the bridge's response:" + error.errorString()); + params.insert("error", errorMap); + emit createUserFinished(id, params); + return; + } + + QVariantMap response = jsonDoc.toVariant().toList().first().toMap(); + emit createUserFinished(id, response); +} + +void HueBridgeConnection::slotGetFinished() +{ + QNetworkReply *reply = static_cast(sender()); + QByteArray data = reply->readAll(); + Caller c = m_requestMap.take(reply); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + QVariantMap params; + QVariantMap errorMap; + errorMap.insert("description", "Failed to parse the bridge's response:" + error.errorString()); + params.insert("error", errorMap); + emit createUserFinished(c.id, params); + return; + } + + QVariant response = jsonDoc.toVariant(); + emit getFinished(c.id, response.toMap()); + if (c.obj) { + qDebug() << "onvoking result slot at" << c.obj.data() << c.method.toLatin1().data(); + metaObject()->invokeMethod(c.obj.data(), c.method.toLatin1().data(), Q_ARG(int, c.id), Q_ARG(QVariant, response)); + } +} diff --git a/plugins/deviceplugins/philipshue/huebridgeconnection.h b/plugins/deviceplugins/philipshue/huebridgeconnection.h index 962eb72e..ef1c4ca3 100644 --- a/plugins/deviceplugins/philipshue/huebridgeconnection.h +++ b/plugins/deviceplugins/philipshue/huebridgeconnection.h @@ -21,19 +21,41 @@ #include #include +#include +#include + +class Caller +{ +public: + QPointer obj; + QString method; + int id; +}; class HueBridgeConnection : public QObject { Q_OBJECT public: - explicit HueBridgeConnection(const QHostAddress &address, const QString &username = QString(), QObject *parent = 0); + explicit HueBridgeConnection(QObject *parent = 0); - QHostAddress address() const; - QString username() const; + int createUser(const QHostAddress &address, const QString &username); + + int get(const QHostAddress &address, const QString &username, const QString &path, QObject *caller, const QString &methodName); + int put(const QHostAddress &address, const QString &username, const QString &path, const QVariantMap &data, QObject *caller, const QString &methodName); + +private slots: + void slotCreateUserFinished(); + void slotGetFinished(); + +signals: + void createUserFinished(int id, const QVariantMap &map); + void getFinished(int id, const QVariantMap &map); private: - QHostAddress m_address; - QString m_username; + QNetworkAccessManager *m_nam; + int m_requestCounter; + QHash m_createUserMap; + QHash m_requestMap; }; #endif // HUEBRIDGECONNECTION_H diff --git a/plugins/deviceplugins/philipshue/light.cpp b/plugins/deviceplugins/philipshue/light.cpp new file mode 100644 index 00000000..a0eaa108 --- /dev/null +++ b/plugins/deviceplugins/philipshue/light.cpp @@ -0,0 +1,422 @@ +/* + * Copyright 2013 Michael Zanetti + * + * 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: + * Michael Zanetti + */ + +#include "light.h" +#include "huebridgeconnection.h" + +#include +#include +#include + +Light::Light(const QHostAddress &ip, const QString &username, int id, QObject *parent): + QObject(parent), + m_bridge(new HueBridgeConnection(this)), + m_ip(ip), + m_username(username), + m_id(id), + m_on(false), + m_busyStateChangeId(-1), + m_hueDirty(false), + m_satDirty(false), + m_briDirty(false), + m_ctDirty(false), + m_xyDirty(false) +{ +} + +int Light::id() const +{ + return m_id; +} + +QString Light::name() const +{ + return m_name; +} + +void Light::setName(const QString &name) +{ + if (m_name != name) { + QVariantMap params; + params.insert("name", name); + m_bridge->put(m_ip, m_username, "lights/" + QString::number(m_id), params, this, "setDescriptionFinished"); + } +} + +QString Light::modelId() const +{ + return m_modelId; +} + +void Light::setModelId(const QString &modelId) +{ + if (m_modelId != modelId) { + m_modelId = modelId; + } +} + +QString Light::type() const +{ + return m_type; +} + +void Light::setType(const QString &type) +{ + if (m_type != type) { + m_type = type; + } +} + +QString Light::swversion() const +{ + return m_swversion; +} + +void Light::setSwversion(const QString &swversion) +{ + if (m_swversion != swversion) { + m_swversion = swversion; + } +} + +bool Light::on() const +{ + return m_on; +} + +void Light::setOn(bool on) +{ + if (m_on != on) { + QVariantMap params; + params.insert("on", on); + m_bridge->put(m_ip, m_username, "lights/" + QString::number(m_id) + "/state", params, this, "setStateFinished"); + } +} + +quint8 Light::bri() const +{ + return m_bri; +} + +void Light::setBri(quint8 bri) +{ + if (m_bri != bri) { + qDebug() << "setting brightness to" << bri << m_busyStateChangeId; + if (m_busyStateChangeId == -1) { + QVariantMap params; + params.insert("bri", bri); + params.insert("on", true); + m_busyStateChangeId = m_bridge->put(m_ip, m_username, "lights/" + QString::number(m_id) + "/state", params, this, "setStateFinished"); + } else { + m_dirtyBri = bri; + m_briDirty = true; + } + } +} + +quint16 Light::hue() const +{ + return m_hue; +} + +void Light::setHue(quint16 hue) +{ + if (m_hue != hue) { + m_hue = hue; + emit stateChanged(); + } +} + +quint8 Light::sat() const +{ + return m_sat; +} + +void Light::setSat(quint8 sat) +{ + if (m_sat != sat) { + m_sat = sat; + emit stateChanged(); + } +} + +QColor Light::color() const +{ + return QColor::fromHsv(m_hue * 360 / 65535, m_sat, 255); +} + +void Light::setColor(const QColor &color) +{ + // Transform from RGB to Hue/Sat + quint16 hue = color.hue() * 65535 / 360; + quint8 sat = color.saturation(); + + // Transform from RGB to XYZ + QGenericMatrix<3, 3, qreal> rgb2xyzMatrix; + rgb2xyzMatrix(0, 0) = 0.412453; rgb2xyzMatrix(0, 1) = 0.357580; rgb2xyzMatrix(0, 2) = 0.180423; + rgb2xyzMatrix(1, 0) = 0.212671; rgb2xyzMatrix(1, 1) = 0.715160; rgb2xyzMatrix(1, 2) = 0.072169; + rgb2xyzMatrix(2, 0) = 0.019334; rgb2xyzMatrix(2, 1) = 0.119193; rgb2xyzMatrix(2, 2) = 0.950227; + + QGenericMatrix<1, 3, qreal> rgbMatrix; + rgbMatrix(0, 0) = 1.0 * color.red() / 255; + rgbMatrix(1, 0) = 1.0 * color.green() / 255; + rgbMatrix(2, 0) = 1.0 * color.blue() / 255; + + QGenericMatrix<1, 3, qreal> xyzMatrix = rgb2xyzMatrix * rgbMatrix; + + // transform from XYZ to CIELUV u' and v' + qreal u = 4*xyzMatrix(0, 0) / (xyzMatrix(0, 0) + 15*xyzMatrix(1, 0) + 3*xyzMatrix(2, 0)); + qreal v = 9*xyzMatrix(1, 0) / (xyzMatrix(0, 0) + 15*xyzMatrix(1, 0) + 3*xyzMatrix(2, 0)); + + // Transform from CIELUV to (x,y) + qreal x = 27*u / (18*u - 48*v + 36); + qreal y = 12*v / (18*u - 48*v + 36); + + qDebug() << "setting color" << color; + if (m_busyStateChangeId == -1) { + QVariantMap params; + + params.insert("hue", hue); + params.insert("sat", sat); + // FIXME: There is a bug in the API that it doesn't report back the set state of "sat" + // Lets just assume it always succeeds + m_sat = sat; + +// QVariantList xyList; +// xyList << x << y; +// params.insert("xy", xyList); + + + params.insert("on", true); + m_busyStateChangeId = m_bridge->put(m_ip, m_username, "lights/" + QString::number(m_id) + "/state", params, this, "setStateFinished"); + } else { + m_dirtyHue = hue; + m_hueDirty = true; + m_dirtySat = sat; + m_satDirty = true; +// m_xyDirty = true; +// m_dirtyXy = QPointF(x, y); + } +} + +QPointF Light::xy() const +{ + return m_xy; +} + +void Light::setXy(const QPointF &xy) +{ + if (m_xy != xy) { + m_xy = xy; + emit stateChanged(); + } +} + +quint16 Light::ct() const +{ + return m_ct; +} + +void Light::setCt(quint16 ct) +{ + if (m_busyStateChangeId == -1) { + QVariantMap params; + params.insert("ct", ct); + params.insert("on", true); + m_busyStateChangeId = m_bridge->put(m_ip, m_username, "lights/" + QString::number(m_id) + "/state", params, this, "setStateFinished"); + } else { + m_dirtyCt = ct; + m_ctDirty = true; + } +} + +QString Light::alert() const +{ + return m_alert; +} + +void Light::setAlert(const QString &alert) +{ + if (m_alert != alert) { + m_alert = alert; + emit stateChanged(); + } +} + +QString Light::effect() const +{ + return m_effect; +} + +void Light::setEffect(const QString &effect) +{ + if (m_effect != effect) { + QVariantMap params; + params.insert("effect", effect); + if (effect != "none") { + params.insert("on", true); + } + m_bridge->put(m_ip, m_username, "lights/" + QString::number(m_id) + "/state", params, this, "setStateFinished"); + } +} + +Light::ColorMode Light::colorMode() const +{ + return m_colormode; +} + +bool Light::reachable() const +{ + return m_reachable; +} + +void Light::refresh() +{ + m_bridge->get(m_ip, m_username, "lights/" + QString::number(m_id), this, "responseReceived"); +} + +void Light::setReachable(bool reachable) +{ + if (m_reachable != reachable) { + m_reachable = reachable; + emit stateChanged(); + } +} + +void Light::responseReceived(int id, const QVariant &response) +{ + Q_UNUSED(id) + QVariantMap attributes = response.toMap(); + + m_name = attributes.value("name").toString(); + setModelId(attributes.value("modelid").toString()); + setType(attributes.value("type").toString()); + setSwversion(attributes.value("swversion").toString()); + + QVariantMap stateMap = attributes.value("state").toMap(); + m_on = stateMap.value("on").toBool(); + m_bri = stateMap.value("bri").toInt(); + m_hue = stateMap.value("hue").toInt(); + m_sat = stateMap.value("sat").toInt(); + m_xy = stateMap.value("xy").toPointF(); + m_ct = stateMap.value("ct").toInt(); + m_alert = stateMap.value("alert").toString(); + m_effect = stateMap.value("effect").toString(); + QString colorModeString = stateMap.value("colormode").toString(); + if (colorModeString == "hs") { + m_colormode = ColorModeHS; + } else if (colorModeString == "xy") { + m_colormode = ColorModeXY; + } else if (colorModeString == "ct") { + m_colormode = ColorModeCT; + } + m_reachable = stateMap.value("reachable").toBool(); + emit stateChanged(); + +// qDebug() << "got light response" << m_modelId << m_type << m_swversion << m_on << m_bri << m_reachable; +} + +void Light::setDescriptionFinished(int id, const QVariant &response) +{ + Q_UNUSED(id) + QVariantMap result = response.toList().first().toMap(); + + if (result.contains("success")) { + QVariantMap successMap = result.value("success").toMap(); + if (successMap.contains("/lights/" + QString::number(m_id) + "/name")) { + m_name = successMap.value("/lights/" + QString::number(m_id) + "/name").toString(); + emit stateChanged(); + } + } +} + +void Light::setStateFinished(int id, const QVariant &response) +{ + foreach (const QVariant &resultVariant, response.toList()) { + QVariantMap result = resultVariant.toMap(); + if (result.contains("success")) { + QVariantMap successMap = result.value("success").toMap(); + if (successMap.contains("/lights/" + QString::number(m_id) + "/state/on")) { + m_on = successMap.value("/lights/" + QString::number(m_id) + "/state/on").toBool(); + } + if (successMap.contains("/lights/" + QString::number(m_id) + "/state/hue")) { + m_hue = successMap.value("/lights/" + QString::number(m_id) + "/state/hue").toInt(); + m_colormode = ColorModeHS; + } + if (successMap.contains("/lights/" + QString::number(m_id) + "/state/bri")) { + m_bri = successMap.value("/lights/" + QString::number(m_id) + "/state/bri").toInt(); + } + if (successMap.contains("/lights/" + QString::number(m_id) + "/state/sat")) { + m_sat = successMap.value("/lights/" + QString::number(m_id) + "/state/sat").toInt(); + m_colormode = ColorModeHS; + } + if (successMap.contains("/lights/" + QString::number(m_id) + "/state/xy")) { + m_xy = successMap.value("/lights/" + QString::number(m_id) + "/state/xy").toPoint(); + m_colormode = ColorModeXY; + } + if (successMap.contains("/lights/" + QString::number(m_id) + "/state/ct")) { + m_ct = successMap.value("/lights/" + QString::number(m_id) + "/state/ct").toInt(); + m_colormode = ColorModeCT; + } + if (successMap.contains("/lights/" + QString::number(m_id) + "/state/effect")) { + m_effect = successMap.value("/lights/" + QString::number(m_id) + "/state/effect").toString(); + } + } + } + emit stateChanged(); + + if (m_busyStateChangeId == id) { + m_busyStateChangeId = -1; + if (m_hueDirty || m_satDirty || m_briDirty) { + QVariantMap params; + if (m_hueDirty) { + params.insert("hue", m_dirtyHue); + m_hueDirty = false; + } + if (m_satDirty) { + params.insert("sat", m_dirtySat); + m_satDirty = false; + } + if (m_briDirty) { + params.insert("bri", m_dirtyBri); + m_briDirty = false; + } + + // FIXME: There is a bug in the API that it doesn't report back the set state of "sat" + // Lets just assume it always succeeds + m_sat = m_dirtySat; + + m_busyStateChangeId = m_bridge->put(QHostAddress(m_ip), m_username, "lights/" + QString::number(m_id) + "/state", params, this, "setStateFinished"); + } else if(m_ctDirty) { + QVariantMap params; + params.insert("ct", m_dirtyCt); + m_ctDirty = false; + + m_busyStateChangeId = m_bridge->put(m_ip, m_username, "lights/" + QString::number(m_id) + "/state", params, this, "setStateFinished"); + } else if (m_xyDirty) { + QVariantMap params; + QVariantList xyList; + xyList << m_dirtyXy.x() << m_dirtyXy.y(); + params.insert("xy", xyList); + m_xyDirty = false; + + m_busyStateChangeId = m_bridge->put(m_ip, m_username, "lights/" + QString::number(m_id) + "/state", params, this, "setStateFinished"); + } + } +} diff --git a/plugins/deviceplugins/philipshue/light.h b/plugins/deviceplugins/philipshue/light.h new file mode 100644 index 00000000..1a1999bf --- /dev/null +++ b/plugins/deviceplugins/philipshue/light.h @@ -0,0 +1,129 @@ +/* + * Copyright 2013 Michael Zanetti + * + * 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: + * Michael Zanetti + */ + +#ifndef LIGHT_H +#define LIGHT_H + +#include +#include +#include +#include + +class HueBridgeConnection; + +class Light: public QObject +{ + Q_OBJECT +public: + enum ColorMode { + ColorModeHS, + ColorModeXY, + ColorModeCT + }; + + Light(const QHostAddress &ip, const QString &username, int id, QObject *parent = 0); + + int id() const; + + QString name() const; + void setName(const QString &name); + + QString modelId() const; + void setModelId(const QString &modelId); + + QString type() const; + void setType(const QString &type); + + QString swversion() const; + void setSwversion(const QString &swversion); + + // LightInterface implementation + bool on() const; + quint8 bri() const; + quint16 hue() const; + quint8 sat() const; + QColor color() const; + QPointF xy() const; + quint16 ct() const; + QString alert() const; + QString effect() const; + ColorMode colorMode() const; + bool reachable() const; + + +public slots: + void refresh(); + + void setOn(bool on); + void setBri(quint8 bri); + void setHue(quint16 hue); + void setSat(quint8 sat); + void setColor(const QColor &color); + void setXy(const QPointF &xy); + void setCt(quint16 ct); + void setAlert(const QString &alert); + void setEffect(const QString &effect); + +signals: + void stateChanged(); + +private slots: + void responseReceived(int id, const QVariant &response); + void setDescriptionFinished(int id, const QVariant &response); + void setStateFinished(int id, const QVariant &response); + +private: + void setReachable(bool reachable); + + HueBridgeConnection *m_bridge; + QHostAddress m_ip; + QString m_username; + + int m_id; + QString m_name; + + QString m_modelId; + QString m_type; + QString m_swversion; + + bool m_on; + quint8 m_bri; + quint16 m_hue; + quint8 m_sat; + QPointF m_xy; + quint16 m_ct; + QString m_alert; + QString m_effect; + ColorMode m_colormode; + bool m_reachable; + + int m_busyStateChangeId; + bool m_hueDirty; + quint16 m_dirtyHue; + bool m_satDirty; + quint8 m_dirtySat; + bool m_briDirty; + quint8 m_dirtyBri; + bool m_ctDirty; + quint16 m_dirtyCt; + bool m_xyDirty; + QPointF m_dirtyXy; +}; + +#endif diff --git a/plugins/deviceplugins/philipshue/philipshue.pro b/plugins/deviceplugins/philipshue/philipshue.pro index a62e7dce..36f9719c 100644 --- a/plugins/deviceplugins/philipshue/philipshue.pro +++ b/plugins/deviceplugins/philipshue/philipshue.pro @@ -7,12 +7,15 @@ QT += network SOURCES += \ devicepluginphilipshue.cpp \ discovery.cpp \ - huebridgeconnection.cpp + huebridgeconnection.cpp \ + light.cpp HEADERS += \ devicepluginphilipshue.h \ discovery.h \ - huebridgeconnection.h + huebridgeconnection.h \ + light.h \ + lightinterface.h diff --git a/server/jsonrpc/actionhandler.cpp b/server/jsonrpc/actionhandler.cpp index 8b18b43f..8a145fb1 100644 --- a/server/jsonrpc/actionhandler.cpp +++ b/server/jsonrpc/actionhandler.cpp @@ -114,5 +114,17 @@ QVariantMap ActionHandler::statusToReply(DeviceManager::DeviceError status, cons QVariantMap returns; returns.insert("success", status == DeviceManager::DeviceErrorNoError); returns.insert("errorMessage", errorMessage); + + switch (status) { + case DeviceManager::DeviceErrorNoError: + break; + case DeviceManager::DeviceErrorDeviceNotFound: + returns.insert("errorMessage", QString("Device not found: %1").arg(errorMessage)); + break; + case DeviceManager::DeviceErrorSetupFailed: + returns.insert("errorMessage", QString("Device setup failed: %1").arg(errorMessage)); + break; + } + return returns; } diff --git a/server/jsonrpc/devicehandler.cpp b/server/jsonrpc/devicehandler.cpp index 5f1d466a..c3f05b72 100644 --- a/server/jsonrpc/devicehandler.cpp +++ b/server/jsonrpc/devicehandler.cpp @@ -620,15 +620,6 @@ void DeviceHandler::pairingFinished(const QUuid &pairingTransactionId, DeviceMan 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(); + + m_asynDeviceAdditions.insert(deviceId, reply); }