From 88c523518a92d61a4562135fffacf55f28045fa0 Mon Sep 17 00:00:00 2001 From: ntim Date: Sun, 27 Apr 2014 12:59:44 +0200 Subject: [PATCH 1/3] Initial commit of support for the Philips Hue system. Former-commit-id: 5b7d802c326151ee96a5b950badb01e94adfe7f3 --- libsrc/leddevice/CMakeLists.txt | 2 + libsrc/leddevice/LedDeviceFactory.cpp | 6 ++ libsrc/leddevice/LedDevicePhilipsHue.cpp | 81 +++++++++++++++++++++ libsrc/leddevice/LedDevicePhilipsHue.h | 90 ++++++++++++++++++++++++ 4 files changed, 179 insertions(+) mode change 100644 => 100755 libsrc/leddevice/CMakeLists.txt mode change 100644 => 100755 libsrc/leddevice/LedDeviceFactory.cpp create mode 100755 libsrc/leddevice/LedDevicePhilipsHue.cpp create mode 100755 libsrc/leddevice/LedDevicePhilipsHue.h diff --git a/libsrc/leddevice/CMakeLists.txt b/libsrc/leddevice/CMakeLists.txt old mode 100644 new mode 100755 index 443e845d..098ac38c --- a/libsrc/leddevice/CMakeLists.txt +++ b/libsrc/leddevice/CMakeLists.txt @@ -29,6 +29,7 @@ SET(Leddevice_HEADERS ${CURRENT_SOURCE_DIR}/LedDeviceSedu.h ${CURRENT_SOURCE_DIR}/LedDeviceTest.h ${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.h + ${CURRENT_SOURCE_DIR}/LedDevicePhilipsHue.h ) SET(Leddevice_SOURCES @@ -44,6 +45,7 @@ SET(Leddevice_SOURCES ${CURRENT_SOURCE_DIR}/LedDeviceSedu.cpp ${CURRENT_SOURCE_DIR}/LedDeviceTest.cpp ${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.cpp + ${CURRENT_SOURCE_DIR}/LedDevicePhilipsHue.cpp ) if(ENABLE_SPIDEV) diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp old mode 100644 new mode 100755 index b5bc6f5e..71aeda06 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -28,6 +28,7 @@ #include "LedDeviceSedu.h" #include "LedDeviceTest.h" #include "LedDeviceHyperionUsbasp.h" +#include "LedDevicePhilipsHue.h" LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) { @@ -159,6 +160,11 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) deviceHyperionUsbasp->open(); device = deviceHyperionUsbasp; } + else if (type == "philipshue") + { + const std::string output = deviceConfig["output"].asString(); + device = new LedDevicePhilipsHue(output); + } else if (type == "test") { const std::string output = deviceConfig["output"].asString(); diff --git a/libsrc/leddevice/LedDevicePhilipsHue.cpp b/libsrc/leddevice/LedDevicePhilipsHue.cpp new file mode 100755 index 00000000..01334403 --- /dev/null +++ b/libsrc/leddevice/LedDevicePhilipsHue.cpp @@ -0,0 +1,81 @@ +// Local-Hyperion includes +#include "LedDevicePhilipsHue.h" + +#include + +#include +#include +#include +#include + +LedDevicePhilipsHue::LedDevicePhilipsHue(const std::string& output) : + host(output.c_str()), username("newdeveloper") { + http = new QHttp(host, 80); +} + +LedDevicePhilipsHue::~LedDevicePhilipsHue() { + delete http; +} + +int LedDevicePhilipsHue::write(const std::vector & ledValues) { + // Due to rate limiting (max. 30 request per seconds), discard new values if + // the previous request have not been completed yet. + if (http->hasPendingRequests()) { + return -1; + } + unsigned int lightId = 1; + for (const ColorRgb& color : ledValues) { + float x, y, b; + // Scale colors from [0, 255] to [0, 1] and convert to xy space. + rgbToXYBrightness(color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f, x, y, b); + // Send adjust color command in JSON format. + put(getRoute(lightId), QString("{\"xy\": [%1, %2]}").arg(x).arg(y)); + // Send brightness color command in JSON format. + put(getRoute(lightId), QString("{\"bri\": %1}").arg(qRound(b * 255.0f))); + // Next light id. + lightId++; + } + return 0; +} + +int LedDevicePhilipsHue::switchOff() { + return 0; +} + +void LedDevicePhilipsHue::put(QString route, QString content) { + QString url = QString("/api/%1/%2").arg(username).arg(route); + QHttpRequestHeader header("PUT", url); + header.setValue("Host", host); + header.setValue("Accept-Encoding", "identity"); + header.setValue("Content-Length", QString("%1").arg(content.size())); + http->setHost(host); + http->request(header, content.toAscii()); + // std::cout << "LedDevicePhilipsHue::put(): " << header.toString().toUtf8().constData() << std::endl; + // std::cout << "LedDevicePhilipsHue::put(): " << content.toUtf8().constData() << std::endl; +} + +QString LedDevicePhilipsHue::getRoute(unsigned int lightId) { + return QString("lights/%1/state").arg(lightId); +} + +void LedDevicePhilipsHue::rgbToXYBrightness(float red, float green, float blue, float& x, float& y, float& brightness) { + // Apply gamma correction. + red = (red > 0.04045f) ? qPow((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f); + green = (green > 0.04045f) ? qPow ((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); + blue = (blue > 0.04045f) ? qPow ((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); + // Convert to XYZ space. + float X = red * 0.649926f + green * 0.103455f + blue * 0.197109f; + float Y = red * 0.234327f + green * 0.743075f + blue * 0.022598f; + float Z = red * 0.0000000f + green * 0.053077f + blue * 1.035763f; + // Convert to x,y space. + x = X / (X + Y + Z); + y = Y / (X + Y + Z); + if (isnan(x)) { + x = 0.0f; + } + if (isnan(y)) { + y = 0.0f; + } + // Brightness is simply Y in the XYZ space. + brightness = Y; +} diff --git a/libsrc/leddevice/LedDevicePhilipsHue.h b/libsrc/leddevice/LedDevicePhilipsHue.h new file mode 100755 index 00000000..e5710f40 --- /dev/null +++ b/libsrc/leddevice/LedDevicePhilipsHue.h @@ -0,0 +1,90 @@ +#pragma once + +// STL includes +#include + +// Qt includes +#include +#include + +// Leddevice includes +#include + +/** + * Implementation for the Philips Hue system. + * + * To use set the device to "philipshue". + * Uses the official Philips Hue API (http://developers.meethue.com). + * Framegrabber should be limited to 30 Hz / numer of lights to avoid rate limitation by the hue bridge. + * Create a new API user name "newdeveloper" on the bridge (http://developers.meethue.com/gettingstarted.html) + */ +class LedDevicePhilipsHue : public LedDevice +{ +public: + /// + /// Constructs the device. + /// + /// @param output the ip address of the bridge + /// + LedDevicePhilipsHue(const std::string& output); + + /// + /// Destructor of this device + /// + virtual ~LedDevicePhilipsHue(); + + /// + /// Sends the given led-color values via put request to the hue system + /// + /// @param ledValues The color-value per led + /// + /// @return Zero on success else negative + /// + virtual int write(const std::vector & ledValues); + + /// Switch the leds off + virtual int switchOff(); + +private: + /// Ip address of the bridge + QString host; + /// User name for the API ("newdeveloper") + QString username; + QHttp* http; + + /// + /// Sends a HTTP PUT request + /// + /// @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 + /// + QString getRoute(unsigned int lightId); + + /// + /// 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] + /// + /// @param x converted x component + /// + /// @param y converted y component + /// + /// @param brightness converted brightness component + /// + void rgbToXYBrightness(float red, float green, float blue, + float& x, float& y, float& brightness); + +}; From ebb22cdc87799612bea0bdd2bf111250f0d03ce0 Mon Sep 17 00:00:00 2001 From: ntim Date: Sun, 27 Apr 2014 18:42:26 +0200 Subject: [PATCH 2/3] Removed rate limiting from code. Setting framegrabbing frequency of 10 Hz / number of lights is sufficient. Former-commit-id: 0686a8d18c780038eb017fdf26b5faf4b8053f3a --- libsrc/leddevice/LedDevicePhilipsHue.cpp | 19 ++++++------------- libsrc/leddevice/LedDevicePhilipsHue.h | 2 +- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/libsrc/leddevice/LedDevicePhilipsHue.cpp b/libsrc/leddevice/LedDevicePhilipsHue.cpp index 01334403..11e99a21 100755 --- a/libsrc/leddevice/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/LedDevicePhilipsHue.cpp @@ -18,11 +18,6 @@ LedDevicePhilipsHue::~LedDevicePhilipsHue() { } int LedDevicePhilipsHue::write(const std::vector & ledValues) { - // Due to rate limiting (max. 30 request per seconds), discard new values if - // the previous request have not been completed yet. - if (http->hasPendingRequests()) { - return -1; - } unsigned int lightId = 1; for (const ColorRgb& color : ledValues) { float x, y, b; @@ -50,8 +45,6 @@ void LedDevicePhilipsHue::put(QString route, QString content) { header.setValue("Content-Length", QString("%1").arg(content.size())); http->setHost(host); http->request(header, content.toAscii()); - // std::cout << "LedDevicePhilipsHue::put(): " << header.toString().toUtf8().constData() << std::endl; - // std::cout << "LedDevicePhilipsHue::put(): " << content.toUtf8().constData() << std::endl; } QString LedDevicePhilipsHue::getRoute(unsigned int lightId) { @@ -71,11 +64,11 @@ void LedDevicePhilipsHue::rgbToXYBrightness(float red, float green, float blue, x = X / (X + Y + Z); y = Y / (X + Y + Z); if (isnan(x)) { - x = 0.0f; - } - if (isnan(y)) { - y = 0.0f; - } - // Brightness is simply Y in the XYZ space. + x = 0.0f; + } + if (isnan(y)) { + y = 0.0f; + } + // Brightness is simply Y in the XYZ space. brightness = Y; } diff --git a/libsrc/leddevice/LedDevicePhilipsHue.h b/libsrc/leddevice/LedDevicePhilipsHue.h index e5710f40..a9155899 100755 --- a/libsrc/leddevice/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/LedDevicePhilipsHue.h @@ -15,7 +15,7 @@ * * To use set the device to "philipshue". * Uses the official Philips Hue API (http://developers.meethue.com). - * Framegrabber should be limited to 30 Hz / numer of lights to avoid rate limitation by the hue bridge. + * Framegrabber must be limited to 10 Hz / numer of lights to avoid rate limitation by the hue bridge. * Create a new API user name "newdeveloper" on the bridge (http://developers.meethue.com/gettingstarted.html) */ class LedDevicePhilipsHue : public LedDevice From ab5e17e1057324955260060a01e13a86246ba68d Mon Sep 17 00:00:00 2001 From: Tim Niggemann Date: Mon, 28 Apr 2014 14:32:37 +0200 Subject: [PATCH 3/3] State of all lights is saved and restored on switchOff(). Former-commit-id: 1ee26e8c01d90456424c1b5ea3f113dfd0ff6525 --- libsrc/leddevice/LedDevicePhilipsHue.cpp | 83 +++++++++++++++++++++--- libsrc/leddevice/LedDevicePhilipsHue.h | 46 +++++++++++-- 2 files changed, 113 insertions(+), 16 deletions(-) diff --git a/libsrc/leddevice/LedDevicePhilipsHue.cpp b/libsrc/leddevice/LedDevicePhilipsHue.cpp index 11e99a21..690a7b94 100755 --- a/libsrc/leddevice/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/LedDevicePhilipsHue.cpp @@ -1,16 +1,18 @@ // Local-Hyperion includes #include "LedDevicePhilipsHue.h" -#include +// jsoncpp includes +#include +// qt includes #include #include #include -#include +#include LedDevicePhilipsHue::LedDevicePhilipsHue(const std::string& output) : - host(output.c_str()), username("newdeveloper") { - http = new QHttp(host, 80); + host(output.c_str()), username("newdeveloper") { + http = new QHttp(host); } LedDevicePhilipsHue::~LedDevicePhilipsHue() { @@ -18,15 +20,20 @@ LedDevicePhilipsHue::~LedDevicePhilipsHue() { } int LedDevicePhilipsHue::write(const std::vector & ledValues) { + // Save light states if not done before. + if (!statesSaved()) { + saveStates(ledValues.size()); + } + // Iterate through colors and set light states. unsigned int lightId = 1; for (const ColorRgb& color : ledValues) { float x, y, b; // Scale colors from [0, 255] to [0, 1] and convert to xy space. rgbToXYBrightness(color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f, x, y, b); // Send adjust color command in JSON format. - put(getRoute(lightId), QString("{\"xy\": [%1, %2]}").arg(x).arg(y)); + put(getStateRoute(lightId), QString("{\"xy\": [%1, %2]}").arg(x).arg(y)); // Send brightness color command in JSON format. - put(getRoute(lightId), QString("{\"bri\": %1}").arg(qRound(b * 255.0f))); + put(getStateRoute(lightId), QString("{\"bri\": %1}").arg(qRound(b * 255.0f))); // Next light id. lightId++; } @@ -34,6 +41,11 @@ int LedDevicePhilipsHue::write(const std::vector & ledValues) { } int LedDevicePhilipsHue::switchOff() { + // If light states have been saved before, ... + if (statesSaved()) { + // ... restore them. + restoreStates(); + } return 0; } @@ -47,15 +59,68 @@ void LedDevicePhilipsHue::put(QString route, QString content) { http->request(header, content.toAscii()); } -QString LedDevicePhilipsHue::getRoute(unsigned int lightId) { +QByteArray LedDevicePhilipsHue::get(QString route) { + QString url = QString("/api/%1/%2").arg(username).arg(route); + // Event loop to block until request finished. + QEventLoop loop; + // Connect requestFinished signal to quit slot of the loop. + loop.connect(http, SIGNAL(requestFinished(int, bool)), SLOT(quit())); + // Perfrom request + http->get(url); + // Go into the loop until the request is finished. + loop.exec(); + // Read all data of the response. + return http->readAll(); +} + +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); +} + +void LedDevicePhilipsHue::saveStates(unsigned int nLights) { + // Clear saved light states. + states.clear(); + // Use json parser to parse reponse. + Json::Reader reader; + Json::FastWriter writer; + // Iterate lights. + for (unsigned int i = 0; i < nLights; i++) { + // Read the response. + QByteArray response = get(getRoute(i + 1)); + // Parse JSON. + Json::Value state; + if (!reader.parse(QString(response).toStdString(), state)) { + // Error occured, break loop. + break; + } + // Save state object. + states.push_back(QString(writer.write(state["state"]).c_str())); + } +} + +void LedDevicePhilipsHue::restoreStates() { + unsigned int lightId = 1; + for (QString state : states) { + put(getStateRoute(lightId), state); + lightId++; + } + // Clear saved light states. + states.clear(); +} + +bool LedDevicePhilipsHue::statesSaved() { + return !states.empty(); +} + void LedDevicePhilipsHue::rgbToXYBrightness(float red, float green, float blue, float& x, float& y, float& brightness) { // Apply gamma correction. red = (red > 0.04045f) ? qPow((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f); - green = (green > 0.04045f) ? qPow ((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); - blue = (blue > 0.04045f) ? qPow ((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); + green = (green > 0.04045f) ? qPow((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); + blue = (blue > 0.04045f) ? qPow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); // Convert to XYZ space. float X = red * 0.649926f + green * 0.103455f + blue * 0.197109f; float Y = red * 0.234327f + green * 0.743075f + blue * 0.022598f; diff --git a/libsrc/leddevice/LedDevicePhilipsHue.h b/libsrc/leddevice/LedDevicePhilipsHue.h index a9155899..0bb71182 100755 --- a/libsrc/leddevice/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/LedDevicePhilipsHue.h @@ -9,7 +9,7 @@ // Leddevice includes #include - + /** * Implementation for the Philips Hue system. * @@ -18,8 +18,7 @@ * Framegrabber must be limited to 10 Hz / numer of lights to avoid rate limitation by the hue bridge. * Create a new API user name "newdeveloper" on the bridge (http://developers.meethue.com/gettingstarted.html) */ -class LedDevicePhilipsHue : public LedDevice -{ +class LedDevicePhilipsHue: public LedDevice { public: /// /// Constructs the device. @@ -46,14 +45,26 @@ public: virtual int switchOff(); private: + /// Array to save the light states. + std::vector states; /// Ip address of the bridge QString host; /// User name for the API ("newdeveloper") QString username; + /// Qhttp object for sending requests. QHttp* http; /// - /// Sends a HTTP PUT request + /// 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 /// @@ -64,10 +75,32 @@ private: /// /// @param lightId the id of the hue light (starting from 1) /// - /// @return the URI of the light + /// @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); + /// + /// Queries the status of all lights and saves it. + /// + /// @param nLights the number of lights + /// + void saveStates(unsigned int nLights); + + /// Restores the status of all lights. + void restoreStates(); + + /// + /// @return true if light states have been saved. + /// + bool statesSaved(); + /// /// 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 @@ -84,7 +117,6 @@ private: /// /// @param brightness converted brightness component /// - void rgbToXYBrightness(float red, float green, float blue, - float& x, float& y, float& brightness); + void rgbToXYBrightness(float red, float green, float blue, float& x, float& y, float& brightness); };