From 59ce0a640e97ce5876b34dd464ccf0c82c3729c4 Mon Sep 17 00:00:00 2001 From: ntim Date: Fri, 31 Mar 2017 10:17:14 +0200 Subject: [PATCH] Added brightnessFactor configuration option to LedDevicePhilipsHue (#427) * Added missing json document compilation to states retrieval. * Major rework and implemented feature to increase brightness by a configurable factor. * Made network manager property of the hue bridge class. * Fixed some errors regarding model id handling. * Removed early return. * Update of philips hue schema for new parameter --- libsrc/leddevice/LedDevicePhilipsHue.cpp | 544 +++++++++--------- libsrc/leddevice/LedDevicePhilipsHue.h | 260 +++++---- .../leddevice/schemas/schema-philipshue.json | 6 + 3 files changed, 444 insertions(+), 366 deletions(-) diff --git a/libsrc/leddevice/LedDevicePhilipsHue.cpp b/libsrc/leddevice/LedDevicePhilipsHue.cpp index 077d8d45..ce4e1d68 100755 --- a/libsrc/leddevice/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/LedDevicePhilipsHue.cpp @@ -6,118 +6,29 @@ #include #include -#include -#include -#include +const CiColor CiColor::BLACK = +{ 0, 0, 0 }; -bool operator ==(CiColor p1, CiColor p2) { +bool operator ==(CiColor p1, CiColor p2) +{ return (p1.x == p2.x) && (p1.y == p2.y) && (p1.bri == p2.bri); } -bool operator !=(CiColor p1, CiColor p2) { +bool operator !=(CiColor p1, CiColor p2) +{ return !(p1 == p2); } -PhilipsHueLight::PhilipsHueLight(unsigned int id, QString originalState, QString modelId) - : id(id) - , originalState(originalState) -{ - // Hue system model ids (http://www.developers.meethue.com/documentation/supported-lights). - // Light strips, color iris, ... - const std::set GAMUT_A_MODEL_IDS = { "LLC001", "LLC005", "LLC006", "LLC007", "LLC010", "LLC011", "LLC012", - "LLC013", "LLC014", "LST001" }; - // Hue bulbs, spots, ... - const std::set GAMUT_B_MODEL_IDS = { "LCT001", "LCT002", "LCT003", "LCT007", "LLM001" }; - // Hue Lightstrip plus, go ... - const std::set GAMUT_C_MODEL_IDS = { "LLC020", "LST002" }; - // Find id in the sets and set the appropiate color space. - if (GAMUT_A_MODEL_IDS.find(modelId) != GAMUT_A_MODEL_IDS.end()) - { - colorSpace.red = {0.703f, 0.296f}; - colorSpace.green = {0.2151f, 0.7106f}; - colorSpace.blue = {0.138f, 0.08f}; - } - else if (GAMUT_B_MODEL_IDS.find(modelId) != GAMUT_B_MODEL_IDS.end()) - { - colorSpace.red = {0.675f, 0.322f}; - colorSpace.green = {0.4091f, 0.518f}; - colorSpace.blue = {0.167f, 0.04f}; - } - else if (GAMUT_C_MODEL_IDS.find(modelId) != GAMUT_B_MODEL_IDS.end()) - { - colorSpace.red = {0.675f, 0.322f}; - colorSpace.green = {0.2151f, 0.7106f}; - colorSpace.blue = {0.167f, 0.04f}; - } - else - { - colorSpace.red = {1.0f, 0.0f}; - colorSpace.green = {0.0f, 1.0f}; - colorSpace.blue = {0.0f, 0.0f}; - } - // Initialize black color. - black = rgbToCiColor(0.0f, 0.0f, 0.0f); - // Initialize color with black - color = {black.x, black.y, black.bri}; -} - -float PhilipsHueLight::crossProduct(CiColor p1, CiColor p2) -{ - return p1.x * p2.y - p1.y * p2.x; -} - -bool PhilipsHueLight::isPointInLampsReach(CiColor p) -{ - CiColor v1 = { colorSpace.green.x - colorSpace.red.x, colorSpace.green.y - colorSpace.red.y }; - CiColor v2 = { colorSpace.blue.x - colorSpace.red.x, colorSpace.blue.y - colorSpace.red.y }; - CiColor q = { p.x - colorSpace.red.x, p.y - colorSpace.red.y }; - float s = crossProduct(q, v2) / crossProduct(v1, v2); - float t = crossProduct(v1, q) / crossProduct(v1, v2); - if ((s >= 0.0f) && (t >= 0.0f) && (s + t <= 1.0f)) - { - return true; - } - return false; -} - -CiColor PhilipsHueLight::getClosestPointToPoint(CiColor a, CiColor b, CiColor p) -{ - CiColor AP = { p.x - a.x, p.y - a.y }; - CiColor AB = { b.x - a.x, b.y - a.y }; - float ab2 = AB.x * AB.x + AB.y * AB.y; - float ap_ab = AP.x * AB.x + AP.y * AB.y; - float t = ap_ab / ab2; - if (t < 0.0f) - { - t = 0.0f; - } - else if (t > 1.0f) - { - t = 1.0f; - } - return {a.x + AB.x * t, a.y + AB.y * t}; -} - -float PhilipsHueLight::getDistanceBetweenTwoPoints(CiColor p1, CiColor p2) -{ - // Horizontal difference. - float dx = p1.x - p2.x; - // Vertical difference. - float dy = p1.y - p2.y; - // Absolute value. - return sqrt(dx * dx + dy * dy); -} - -CiColor PhilipsHueLight::rgbToCiColor(float red, float green, float blue) +CiColor CiColor::rgbToCiColor(float red, float green, float blue, CiColorTriangle colorSpace) { // Apply gamma correction. float r = (red > 0.04045f) ? powf((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f); float g = (green > 0.04045f) ? powf((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); float b = (blue > 0.04045f) ? powf((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); // Convert to XYZ space. - float X = r * 0.649926f + g * 0.103455f + b * 0.197109f; - float Y = r * 0.234327f + g * 0.743075f + b * 0.022598f; - float Z = r * 0.0000000f + g * 0.053077f + b * 1.035763f; + float X = r * 0.664511f + g * 0.154324f + b * 0.162028f; + float Y = r * 0.283881f + g * 0.668433f + b * 0.047685f; + float Z = r * 0.000088f + g * 0.072310f + b * 0.986039f; // Convert to x,y space. float cx = X / (X + Y + Z); float cy = Y / (X + Y + Z); @@ -130,9 +41,10 @@ CiColor PhilipsHueLight::rgbToCiColor(float red, float green, float blue) cy = 0.0f; } // Brightness is simply Y in the XYZ space. - CiColor xy = { cx, cy, Y }; + CiColor xy = + { cx, cy, Y }; // Check if the given XY value is within the color reach of our lamps. - if (!isPointInLampsReach(xy)) + if (!isPointInLampsReach(xy, colorSpace)) { // It seems the color is out of reach let's find the closes color we can produce with our lamp and send this XY value out. CiColor pAB = getClosestPointToPoint(colorSpace.red, colorSpace.green, xy); @@ -161,12 +73,243 @@ CiColor PhilipsHueLight::rgbToCiColor(float red, float green, float blue) return xy; } -LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject &deviceConfig) - : LedDevice() +float CiColor::crossProduct(CiColor p1, CiColor p2) { + return p1.x * p2.y - p1.y * p2.x; +} + +bool CiColor::isPointInLampsReach(CiColor p, CiColorTriangle colorSpace) +{ + CiColor v1 = + { colorSpace.green.x - colorSpace.red.x, colorSpace.green.y - colorSpace.red.y }; + CiColor v2 = + { colorSpace.blue.x - colorSpace.red.x, colorSpace.blue.y - colorSpace.red.y }; + CiColor q = + { p.x - colorSpace.red.x, p.y - colorSpace.red.y }; + float s = crossProduct(q, v2) / crossProduct(v1, v2); + float t = crossProduct(v1, q) / crossProduct(v1, v2); + if ((s >= 0.0f) && (t >= 0.0f) && (s + t <= 1.0f)) + { + return true; + } + return false; +} + +CiColor CiColor::getClosestPointToPoint(CiColor a, CiColor b, CiColor p) +{ + CiColor AP = + { p.x - a.x, p.y - a.y }; + CiColor AB = + { b.x - a.x, b.y - a.y }; + float ab2 = AB.x * AB.x + AB.y * AB.y; + float ap_ab = AP.x * AB.x + AP.y * AB.y; + float t = ap_ab / ab2; + if (t < 0.0f) + { + t = 0.0f; + } + else if (t > 1.0f) + { + t = 1.0f; + } + return + { a.x + AB.x * t, a.y + AB.y * t}; +} + +float CiColor::getDistanceBetweenTwoPoints(CiColor p1, CiColor p2) +{ + // Horizontal difference. + float dx = p1.x - p2.x; + // Vertical difference. + float dy = p1.y - p2.y; + // Absolute value. + return sqrt(dx * dx + dy * dy); +} + +QByteArray PhilipsHueBridge::get(QString route) +{ + QString url = QString("http://%1/api/%2/%3").arg(host).arg(username).arg(route); + Debug(log, "Get %s", url.toStdString().c_str()); + // Perfrom request + QNetworkRequest request(url); + QNetworkReply* reply = manager->get(request); + // Connect requestFinished signal to quit slot of the loop. + QEventLoop loop; + loop.connect(reply, SIGNAL(finished()), SLOT(quit())); + // Go into the loop until the request is finished. + loop.exec(); + // Read all data of the response. + QByteArray response = reply->readAll(); + // Free space. + reply->deleteLater(); + // Return response; + return response; +} + +void PhilipsHueBridge::post(QString route, QString content) +{ + QString url = QString("http://%1/api/%2/%3").arg(host).arg(username).arg(route); + Debug(log, "Post %s: %s", url.toStdString().c_str(), content.toStdString().c_str()); + // Perfrom request + QNetworkRequest request(url); + QNetworkReply* reply = manager->put(request, content.toLatin1()); + // Connect finished signal to quit slot of the loop. + QEventLoop loop; + loop.connect(reply, SIGNAL(finished()), SLOT(quit())); + // Go into the loop until the request is finished. + loop.exec(); + // Free space. + reply->deleteLater(); +} + +const std::set PhilipsHueLight::GAMUT_A_MODEL_IDS = +{ "LLC001", "LLC005", "LLC006", "LLC007", "LLC010", "LLC011", "LLC012", "LLC013", "LLC014", "LST001" }; +const std::set PhilipsHueLight::GAMUT_B_MODEL_IDS = +{ "LCT001", "LCT002", "LCT003", "LCT007", "LLM001" }; +const std::set PhilipsHueLight::GAMUT_C_MODEL_IDS = +{ "LLC020", "LST002" }; + +PhilipsHueLight::PhilipsHueLight(Logger* log, PhilipsHueBridge& bridge, unsigned int id) : + log(log), bridge(bridge), id(id) +{ + + // Get model id and original state. + QByteArray response = bridge.get(QString("lights/%1").arg(id)); + // Use JSON parser to parse response. + QJsonParseError error; + QJsonDocument reader = QJsonDocument::fromJson(response, &error); + ; + // Parse response. + if (error.error != QJsonParseError::NoError) + { + Error(log, "Got invalid response from light %d", id); + } + // Get state object values which are subject to change. + QJsonObject json = reader.object(); + if (!json["state"].toObject().contains("on")) + { + Error(log, "Got no state object from light %d", id); + } + if (!json["state"].toObject().contains("on")) + { + Error(log, "Got invalid state object from light %d", id); + } + QJsonObject state; + state["on"] = json["state"].toObject()["on"]; + on = false; + if (json["state"].toObject()["on"].toBool() == true) + { + state["xy"] = json["state"].toObject()["xy"]; + state["bri"] = json["state"].toObject()["bri"]; + on = true; + + color = + { (float) state["xy"].toArray()[0].toDouble(),(float) state["xy"].toArray()[1].toDouble(), (float) state["bri"].toDouble() / 255.0f}; + transitionTime = json["state"].toObject()["transitiontime"].toInt(); + } + // Determine the model id. + modelId = json["modelid"].toString().trimmed().replace("\"", ""); + // Determine the original state. + originalState = QJsonDocument(state).toJson(QJsonDocument::JsonFormat::Compact).trimmed(); + // Find id in the sets and set the appropriate color space. + if (GAMUT_A_MODEL_IDS.find(modelId) != GAMUT_A_MODEL_IDS.end()) + { + Debug(log, "Recognized model id %s as gamut A", modelId.toStdString().c_str()); + colorSpace.red = + { 0.703f, 0.296f}; + colorSpace.green = + { 0.2151f, 0.7106f}; + colorSpace.blue = + { 0.138f, 0.08f}; + } + else if (GAMUT_B_MODEL_IDS.find(modelId) != GAMUT_B_MODEL_IDS.end()) + { + Debug(log, "Recognized model id %s as gamut B", modelId.toStdString().c_str()); + colorSpace.red = + { 0.675f, 0.322f}; + colorSpace.green = + { 0.4091f, 0.518f}; + colorSpace.blue = + { 0.167f, 0.04f}; + } + else if (GAMUT_C_MODEL_IDS.find(modelId) != GAMUT_C_MODEL_IDS.end()) + { + Debug(log, "Recognized model id %s as gamut C", modelId.toStdString().c_str()); + colorSpace.red = + { 0.675f, 0.322f}; + colorSpace.green = + { 0.2151f, 0.7106f}; + colorSpace.blue = + { 0.167f, 0.04f}; + } + else + { + Warning(log, "Did not recognize model id %s", modelId.toStdString().c_str()); + colorSpace.red = + { 1.0f, 0.0f}; + colorSpace.green = + { 0.0f, 1.0f}; + colorSpace.blue = + { 0.0f, 0.0f}; + } +} + +PhilipsHueLight::~PhilipsHueLight() +{ + // Restore the original state. + set(originalState); +} + +void PhilipsHueLight::set(QString state) +{ + bridge.post(QString("lights/%1/state").arg(id), state); +} + +void PhilipsHueLight::setOn(bool on) +{ + if (this->on != on) + { + QString arg = on ? "true" : "false"; + set(QString("{ \"on\": %1 }").arg(arg)); + } + this->on = on; +} + +void PhilipsHueLight::setTransitionTime(unsigned int transitionTime) +{ + if (this->transitionTime != transitionTime) + { + set(QString("{ \"transitiontime\": %1 }").arg(transitionTime)); + } + this->transitionTime = transitionTime; +} + +void PhilipsHueLight::setColor(CiColor color, float brightnessFactor) +{ + if (this->color != color) + { + const int bri = qRound(qMin(254.0f, brightnessFactor * qMax(1.0f, color.bri * 254.0f))); + set(QString("{ \"xy\": [%1, %2], \"bri\": %3 }").arg(color.x, 0, 'f', 4).arg(color.y, 0, 'f', 4).arg(bri)); + } + this->color = color; +} + +CiColor PhilipsHueLight::getColor() const +{ + return color; +} + +CiColorTriangle PhilipsHueLight::getColorSpace() const +{ + return colorSpace; +} + +LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject &deviceConfig) : + LedDevice() +{ + manager = new QNetworkAccessManager(); _deviceReady = init(deviceConfig); - manager = new QNetworkAccessManager(); timer.setInterval(3000); timer.setSingleShot(true); connect(&timer, SIGNAL(timeout()), this, SLOT(restoreStates())); @@ -174,20 +317,22 @@ LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject &deviceConfig) LedDevicePhilipsHue::~LedDevicePhilipsHue() { - delete manager; + // Switch off. + switchOff(); } bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) { LedDevice::init(deviceConfig); - host = deviceConfig["output"].toString().toStdString().c_str(); - username = deviceConfig["username"].toString("newdeveloper").toStdString().c_str(); + bridge = + { _log, manager, deviceConfig["output"].toString(), deviceConfig["username"].toString("newdeveloper")}; switchOffOnBlack = deviceConfig["switchOffOnBlack"].toBool(true); - transitiontime = deviceConfig["transitiontime"].toInt(1); + brightnessFactor = (float) deviceConfig["transitiontime"].toDouble(1.0); + transitionTime = deviceConfig["transitiontime"].toInt(1); lightIds.clear(); QJsonArray lArray = deviceConfig["lightIds"].toArray(); - for(int i = 0; i < lArray.size(); i++) + for (int i = 0; i < lArray.size(); i++) { lightIds.push_back(lArray[i].toInt()); } @@ -205,11 +350,10 @@ int LedDevicePhilipsHue::write(const std::vector & ledValues) // Save light states if not done before. if (!areStatesSaved()) { - saveStates((unsigned int) _ledCount); - switchOn((unsigned int) _ledCount); + saveStates((unsigned int) ledValues.size()); } // If there are less states saved than colors given, then maybe something went wrong before. - if (lights.size() != (unsigned)_ledCount) + if (lights.size() != ledValues.size()) { restoreStates(); return 0; @@ -219,42 +363,29 @@ int LedDevicePhilipsHue::write(const std::vector & ledValues) for (const ColorRgb& color : ledValues) { // Get lamp. - PhilipsHueLight& lamp = lights.at(idx); + PhilipsHueLight& light = lights.at(idx); // Scale colors from [0, 255] to [0, 1] and convert to xy space. - CiColor xy = lamp.rgbToCiColor(color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f); + CiColor xy = CiColor::rgbToCiColor(color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f, + light.getColorSpace()); // Write color if color has been changed. - if (xy != lamp.color) + if (switchOffOnBlack && light.getColor() != CiColor::BLACK && xy == CiColor::BLACK) { - // From a color to black. - if (switchOffOnBlack && lamp.color != lamp.black && xy == lamp.black) - { - put(getStateRoute(lamp.id), QString("{\"on\": false}")); - } - // From black to a color - else if (switchOffOnBlack && lamp.color == lamp.black && xy != lamp.black) - { - // Send adjust color and brightness command in JSON format. - // We have to set the transition time each time. - // Send also command to switch the lamp on. - put(getStateRoute(lamp.id), - QString("{\"on\": true, \"xy\": [%1, %2], \"bri\": %3, \"transitiontime\": %4}").arg(xy.x).arg( - xy.y).arg(qRound(xy.bri * 255.0f)).arg(transitiontime)); - } - // Normal color change. - else - { - // Send adjust color and brightness command in JSON format. - // We have to set the transition time each time. - put(getStateRoute(lamp.id), - QString("{\"xy\": [%1, %2], \"bri\": %3, \"transitiontime\": %4}").arg(xy.x).arg(xy.y).arg( - qRound(xy.bri * 255.0f)).arg(transitiontime)); - } + light.setOn(false); } - // Remember last color. - lamp.color = xy; + else if (switchOffOnBlack && light.getColor() == CiColor::BLACK && xy != CiColor::BLACK) + { + light.setOn(true); + } + else + { + light.setOn(true); + } + light.setTransitionTime(transitionTime); + light.setColor(xy, brightnessFactor); // Next light id. idx++; } + // Reset timer. timer.start(); return 0; } @@ -271,155 +402,52 @@ int LedDevicePhilipsHue::switchOff() return 0; } -void LedDevicePhilipsHue::put(QString route, QString content) -{ - QString url = getUrl(route); - // Perfrom request - QNetworkRequest request(url); - QNetworkReply* reply = manager->put(request, content.toLatin1()); - // Connect finished signal to quit slot of the loop. - QEventLoop loop; - loop.connect(reply, SIGNAL(finished()), SLOT(quit())); - // Go into the loop until the request is finished. - loop.exec(); - // Free space. - reply->deleteLater(); -} - -QByteArray LedDevicePhilipsHue::get(QString route) -{ - QString url = getUrl(route); - // Perfrom request - QNetworkRequest request(url); - QNetworkReply* reply = manager->get(request); - // Connect requestFinished signal to quit slot of the loop. - QEventLoop loop; - loop.connect(reply, SIGNAL(finished()), SLOT(quit())); - // Go into the loop until the request is finished. - loop.exec(); - // Read all data of the response. - QByteArray response = reply->readAll(); - // Free space. - reply->deleteLater(); - // Return response - return response; -} - -QString LedDevicePhilipsHue::getStateRoute(unsigned int lightId) -{ - return QString("lights/%1/state").arg(lightId); -} - -QString LedDevicePhilipsHue::getRoute(unsigned int lightId) -{ - return QString("lights/%1").arg(lightId); -} - -QString LedDevicePhilipsHue::getUrl(QString route) -{ - return QString("http://%1/api/%2/%3").arg(host).arg(username).arg(route); -} - void LedDevicePhilipsHue::saveStates(unsigned int nLights) { - QJsonParseError error; - QJsonDocument reader; - QJsonObject json; - QByteArray response; - + // Clear saved lamps. lights.clear(); - + // + if (nLights == 0) { + return; + } // Read light ids if none have been supplied by the user. if (lightIds.size() != nLights) { lightIds.clear(); - // - response = get("lights"); + // Retrieve lights from bridge. + QByteArray response = bridge.get("lights"); // Use QJsonDocument to parse reponse. - reader = QJsonDocument::fromJson(response, &error); + QJsonParseError error; + QJsonDocument reader = QJsonDocument::fromJson(response, &error); if (error.error != QJsonParseError::NoError) { - throw std::runtime_error(("No lights found at " + getUrl("lights")).toStdString()); + Error(_log, "No lights found."); } - - json = reader.object(); - // Loop over all children. + QJsonObject json = reader.object(); for (QJsonObject::iterator it = json.begin(); it != json.end() && lightIds.size() < nLights; it++) { int lightId = atoi(it.key().toStdString().c_str()); lightIds.push_back(lightId); - Debug(_log, "nLights=%d: found light with id %d.", nLights, lightId); + Debug(_log, "nLights=%d: found light with id %d.", nLights, lightId); } // Check if we found enough lights. if (lightIds.size() != nLights) { - throw std::runtime_error(("Not enough lights found at " + getUrl("lights")).toStdString()); + Error(_log, "Not enough lights found"); } } // Iterate lights. for (unsigned int i = 0; i < nLights; i++) { - // Read the response. - response = get(getRoute(lightIds.at(i))); - // Parse JSON. - reader = QJsonDocument::fromJson(response, &error); - - if (error.error != QJsonParseError::NoError) - { - // Error occured, break loop. - Error(_log, "saveStates(nLights=%d): got invalid response from light %s. (error:%s, offset:%d)", - nLights, getUrl(getRoute(lightIds.at(i))).toStdString().c_str(), error.errorString().toLocal8Bit().constData(), error.offset ); - - break; - } - - json = reader.object(); - - // Get state object values which are subject to change. - QJsonObject state; - if (!json.contains("state")) - { - Error(_log, "saveStates(nLights=%d): got no state for light from %s", nLights, getUrl(getRoute(lightIds.at(i))).toStdString().c_str()); - break; - } - if (!json["state"].toObject().contains("on")) - { - Error(_log, "saveStates(nLights=%d,): got no valid state from light %s", nLights, getUrl(getRoute(lightIds.at(i))).toStdString().c_str()); - break; - } - state["on"] = json["state"].toObject()["on"]; - if (json["state"].toObject()["on"].toBool() == true) - { - state["xy"] = json["state"].toObject()["xy"]; - state["bri"] = json["state"].toObject()["bri"]; - } - // Determine the model id. - QJsonDocument mId(json["modelid"].toObject()); - QString modelId = mId.toJson().trimmed().replace("\"", ""); - QJsonDocument st(state); - QString originalState = mId.toJson().trimmed(); - // Save state object. - lights.push_back(PhilipsHueLight(lightIds.at(i), originalState, modelId)); + lights.push_back(PhilipsHueLight(_log, bridge, lightIds.at(i))); } -} -void LedDevicePhilipsHue::switchOn(unsigned int nLights) -{ - for (PhilipsHueLight light : lights) - { - put(getStateRoute(light.id), "{\"on\": true}"); - } } void LedDevicePhilipsHue::restoreStates() { - for (PhilipsHueLight light : lights) - { - put(getStateRoute(light.id), light.originalState); - } - // Clear saved light states. lights.clear(); } diff --git a/libsrc/leddevice/LedDevicePhilipsHue.h b/libsrc/leddevice/LedDevicePhilipsHue.h index 4d574fd1..f62d8f17 100644 --- a/libsrc/leddevice/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/LedDevicePhilipsHue.h @@ -1,5 +1,8 @@ #pragma once +// STL includes +#include + // Qt includes #include #include @@ -7,16 +10,72 @@ // Leddevice includes #include +// Forward declaration +struct CiColorTriangle; + /** * A color point in the color space of the hue system. */ -struct CiColor { +struct CiColor +{ /// X component. float x; /// Y component. float y; /// The brightness. float bri; + /// Black color constant. + static const CiColor BLACK; + + /// + /// Converts an RGB color to the Hue xy color space and brightness. + /// https://github.com/PhilipsHue/PhilipsHueSDK-iOS-OSX/blob/master/ApplicationDesignNotes/RGB%20to%20xy%20Color%20conversion.md + /// + /// @param red the red component in [0, 1] + /// + /// @param green the green component in [0, 1] + /// + /// @param blue the blue component in [0, 1] + /// + /// @return color point + /// + static CiColor rgbToCiColor(float red, float green, float blue, CiColorTriangle colorSpace); + + /// + /// @param p the color point to check + /// + /// @return true if the color point is covered by the lamp color space + /// + static bool isPointInLampsReach(CiColor p, CiColorTriangle colorSpace); + + /// + /// @param p1 point one + /// + /// @param p2 point tow + /// + /// @return the cross product between p1 and p2 + /// + static float crossProduct(CiColor p1, CiColor p2); + + /// + /// @param a reference point one + /// + /// @param b reference point two + /// + /// @param p the point to which the closest point is to be found + /// + /// @return the closest color point of p to a and b + /// + static CiColor getClosestPointToPoint(CiColor a, CiColor b, CiColor p); + + /// + /// @param p1 point one + /// + /// @param p2 point tow + /// + /// @return the distance between the two points + /// + static float getDistanceBetweenTwoPoints(CiColor p1, CiColor p2); }; bool operator==(CiColor p1, CiColor p2); @@ -25,74 +84,111 @@ bool operator!=(CiColor p1, CiColor p2); /** * Color triangle to define an available color space for the hue lamps. */ -struct CiColorTriangle { +struct CiColorTriangle +{ CiColor red, green, blue; }; +class PhilipsHueBridge +{ +private: + Logger* log; + /// QNetworkAccessManager object for sending requests. + QNetworkAccessManager* manager; + /// Ip address of the bridge + QString host; + /// User name for the API ("newdeveloper") + QString username; + +public: + PhilipsHueBridge(Logger* log, QNetworkAccessManager* manager, QString host, QString username) : + log(log), manager(manager), host(host), username(username) + { + } + PhilipsHueBridge() + { + log = NULL; + manager = NULL; + } + + /// + /// @param route the route of the GET request. + /// + /// @return the response of the GET request. + /// + QByteArray get(QString route); + + /// + /// @param route the route of the POST request. + /// + /// @param content the content of the POST request. + /// + void post(QString route, QString content); +}; + /** * Simple class to hold the id, the latest color, the color space and the original state. */ -class PhilipsHueLight { -public: +class PhilipsHueLight +{ +private: + Logger * log; + PhilipsHueBridge& bridge; unsigned int id; - CiColor black; + bool on; + unsigned int transitionTime; CiColor color; + /// The model id of the hue lamp which is used to determine the color space. + QString modelId; CiColorTriangle colorSpace; + /// The json string of the original state. QString originalState; + /// + /// @param state the state as json object to set + /// + void set(QString state); + +public: + // Hue system model ids (http://www.developers.meethue.com/documentation/supported-lights). + // Light strips, color iris, ... + static const std::set GAMUT_A_MODEL_IDS; + // Hue bulbs, spots, ... + static const std::set GAMUT_B_MODEL_IDS; + // Hue Lightstrip plus, go ... + static const std::set GAMUT_C_MODEL_IDS; + /// /// Constructs the light. /// + /// @param log the logger + /// @param bridge the bridge /// @param id the light id /// - /// @param originalState the json string of the original state - /// - /// @param modelId the model id of the hue lamp which is used to determine the color space - /// - PhilipsHueLight(unsigned int id, QString originalState, QString modelId); + PhilipsHueLight(Logger* log, PhilipsHueBridge& bridge, unsigned int id); + ~PhilipsHueLight(); /// - /// Converts an RGB color to the Hue xy color space and brightness. - /// https://github.com/PhilipsHue/PhilipsHueSDK-iOS-OSX/blob/master/ApplicationDesignNotes/RGB%20to%20xy%20Color%20conversion.md + /// @param on /// - /// @param red the red component in [0, 1] - /// @param green the green component in [0, 1] - /// @param blue the blue component in [0, 1] - /// - /// @return color point - /// - CiColor rgbToCiColor(float red, float green, float blue); + void setOn(bool on); /// - /// @param p the color point to check - /// @return true if the color point is covered by the lamp color space + /// @param transitionTime the transition time between colors in multiples of 100 ms /// - bool isPointInLampsReach(CiColor p); + void setTransitionTime(unsigned int transitionTime); /// - /// @param p1 point one - /// @param p2 point tow + /// @param color the color to set + /// @param brightnessFactor the factor to apply to the CiColor#bri value /// - /// @return the cross product between p1 and p2 - /// - float crossProduct(CiColor p1, CiColor p2); + void setColor(CiColor color, float brightnessFactor = 1.0f); + CiColor getColor() const; /// - /// @param a reference point one - /// @param b reference point two - /// @param p the point to which the closest point is to be found - /// - /// @return the closest color point of p to a and b - /// - CiColor getClosestPointToPoint(CiColor a, CiColor b, CiColor p); + /// @return the color space of the light determined by the model id reported by the bridge. + CiColorTriangle getColorSpace() const; - /// - /// @param p1 point one - /// @param p2 point tow - /// - /// @return the distance between the two points - /// - float getDistanceBetweenTwoPoints(CiColor p1, CiColor p2); }; /** @@ -105,7 +201,8 @@ public: * * @author ntim (github), bimsarck (github) */ -class LedDevicePhilipsHue: public LedDevice { +class LedDevicePhilipsHue: public LedDevice +{ Q_OBJECT @@ -122,16 +219,9 @@ public: /// virtual ~LedDevicePhilipsHue(); - /// - /// Sets configuration - /// - /// @param deviceConfig the json device config - /// @return true if success - bool init(const QJsonObject &deviceConfig); - /// constructs leddevice static LedDevice* construct(const QJsonObject &deviceConfig); - + /// Restores the original state of the leds. virtual int switchOff(); @@ -139,72 +229,33 @@ private slots: /// Restores the status of all lights. void restoreStates(); -private: +protected: /// - /// Sends the given led-color values via put request to the hue system + /// Writes the RGB-Color values to the leds. /// - /// @param ledValues The color-value per led + /// @param[in] ledValues The RGB-color per led /// /// @return Zero on success else negative /// virtual int write(const std::vector & ledValues); + bool init(const QJsonObject &deviceConfig); - /// Array to save the lamps. - std::vector lights; - /// Ip address of the bridge - QString host; - /// User name for the API ("newdeveloper") - QString username; - /// QNetworkAccessManager object for sending requests. +private: QNetworkAccessManager* manager; + PhilipsHueBridge bridge; /// Use timer to reset lights when we got into "GRABBINGMODE_OFF". QTimer timer; /// bool switchOffOnBlack; + /// The brightness factor to multiply on color change. + float brightnessFactor; /// Transition time in multiples of 100 ms. - /// The default of the Hue lights will be 400 ms, but we want to have it snapier - int transitiontime; + /// The default of the Hue lights is 400 ms, but we may want it snapier. + int transitionTime; /// Array of the light ids. std::vector lightIds; - - /// - /// Sends a HTTP GET request (blocking). - /// - /// @param route the URI of the request - /// - /// @return response of the request - /// - QByteArray get(QString route); - - /// - /// Sends a HTTP PUT request (non-blocking). - /// - /// @param route the URI of the request - /// - /// @param content content of the request - /// - void put(QString route, QString content); - - /// - /// @param lightId the id of the hue light (starting from 1) - /// - /// @return the URI of the light state for PUT requests. - /// - QString getStateRoute(unsigned int lightId); - - /// - /// @param lightId the id of the hue light (starting from 1) - /// - /// @return the URI of the light for GET requests. - /// - QString getRoute(unsigned int lightId); - - /// - /// @param route - /// - /// @return the full URL of the request. - /// - QString getUrl(QString route); + /// Array to save the lamps. + std::vector lights; /// /// Queries the status of all lights and saves it. @@ -213,13 +264,6 @@ private: /// void saveStates(unsigned int nLights); - /// - /// Switches the leds on. - /// - /// @param nLights the number of lights - /// - void switchOn(unsigned int nLights); - /// /// @return true if light states have been saved. /// diff --git a/libsrc/leddevice/schemas/schema-philipshue.json b/libsrc/leddevice/schemas/schema-philipshue.json index 519bc393..0e1dd1e0 100644 --- a/libsrc/leddevice/schemas/schema-philipshue.json +++ b/libsrc/leddevice/schemas/schema-philipshue.json @@ -34,6 +34,12 @@ "title":"edt_dev_spec_switchOffOnBlack_title", "default" : true, "propertyOrder" : 5 + }, + "brightnessFactor": { + "type": "number", + "title":"edt_dev_spec_brightnessFactor_title", + "default" : 1.0, + "propertyOrder" : 6 } }, "additionalProperties": true