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..690a7b94 --- /dev/null +++ b/libsrc/leddevice/LedDevicePhilipsHue.cpp @@ -0,0 +1,139 @@ +// Local-Hyperion includes +#include "LedDevicePhilipsHue.h" + +// jsoncpp includes +#include + +// qt includes +#include +#include +#include +#include + +LedDevicePhilipsHue::LedDevicePhilipsHue(const std::string& output) : + host(output.c_str()), username("newdeveloper") { + http = new QHttp(host); +} + +LedDevicePhilipsHue::~LedDevicePhilipsHue() { + delete http; +} + +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(getStateRoute(lightId), QString("{\"xy\": [%1, %2]}").arg(x).arg(y)); + // Send brightness color command in JSON format. + put(getStateRoute(lightId), QString("{\"bri\": %1}").arg(qRound(b * 255.0f))); + // Next light id. + lightId++; + } + return 0; +} + +int LedDevicePhilipsHue::switchOff() { + // If light states have been saved before, ... + if (statesSaved()) { + // ... restore them. + restoreStates(); + } + 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()); +} + +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); + // 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..0bb71182 --- /dev/null +++ b/libsrc/leddevice/LedDevicePhilipsHue.h @@ -0,0 +1,122 @@ +#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 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 { +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: + /// 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 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); + + /// + /// 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 + /// + /// @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); + +};