diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json
index 479123a8..644049ff 100644
--- a/assets/webconfig/i18n/de.json
+++ b/assets/webconfig/i18n/de.json
@@ -426,6 +426,7 @@
"edt_dev_spec_transistionTime_title": "Übergangszeit",
"edt_dev_spec_switchOffOnBlack_title": "Aus bei schwarz",
"edt_dev_spec_brightnessFactor_title": "Helligkeitsfaktor",
+ "edt_dev_spec_restoreOriginalState_title" : "Lampen Originalzustand wiederhestellen",
"edt_dev_spec_ledType_title": "LED typ",
"edt_dev_spec_uid_title": "UID",
"edt_dev_spec_intervall_title": "Intervall",
diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json
index 603d8710..07d9aaea 100644
--- a/assets/webconfig/i18n/en.json
+++ b/assets/webconfig/i18n/en.json
@@ -425,6 +425,7 @@
"edt_dev_spec_transistionTime_title" : "Transition time",
"edt_dev_spec_switchOffOnBlack_title" : "Switch off on black",
"edt_dev_spec_brightnessFactor_title" : "Brightness factor",
+ "edt_dev_spec_restoreOriginalState_title" : "Restore lights' original state",
"edt_dev_spec_ledType_title" : "LED Type",
"edt_dev_spec_uid_title" : "UID",
"edt_dev_spec_intervall_title" : "Interval",
diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js
index fb7ab6d2..de33979e 100644
--- a/assets/webconfig/js/wizard.js
+++ b/assets/webconfig/js/wizard.js
@@ -567,23 +567,35 @@ function checkHueBridge(cb,hueUser){
timeout: 2000
})
.done( function( data, textStatus, jqXHR ) {
- if(Array.isArray(data) && data[0].error && data[0].error.type == 4)
- cb(true);
- else if(Array.isArray(data) && data[0].error)
- cb(false);
+ if( Array.isArray(data) && data[0].error)
+ {
+ if ( data[0].error.type == 3 || data[0].error.type == 4)
+ {
+ cb(true, usr);
+ }
+ else
+ {
+ cb(false);
+ }
+ }
else
- cb(true);
+ {
+ cb(true, usr);
+ }
+
})
.fail( function( jqXHR, textStatus ) {
cb(false);
});
}
-function checkUserResult(reply){
+function checkUserResult(reply, usr){
+
if(reply)
{
$('#wiz_hue_usrstate').html("");
$('#wiz_hue_create_user').toggle(false);
+ $('#user').val(usr);
get_hue_lights();
}
else
@@ -640,17 +652,22 @@ function checkBridgeResult(reply){
function identHueId(id, off)
{
- var on = true;
if(off !== true)
+ {
setTimeout(identHueId,1500,id,true);
+ var put_data = '{"on":true,"bri":254,"hue":47000,"sat":254}';
+ }
else
- on = false;
+ {
+ var put_data = '{"on":false}';
+ }
$.ajax({
url: 'http://'+$('#ip').val()+'/api/'+$('#user').val()+'/lights/'+id+'/state',
type: 'PUT',
timeout: 2000,
- data: ' {"on":'+on+', "sat":254, "bri":254,"hue":47000}'
+
+ data: put_data
})
}
@@ -686,7 +703,9 @@ function beginWizardHue()
//check if ip is empty/reachable/search for bridge
if(conf_editor.getEditor("root.specificOptions.output").getValue() == "")
+ {
getHueIPs();
+ }
else
{
var ip = conf_editor.getEditor("root.specificOptions.output").getValue();
@@ -719,6 +738,7 @@ function beginWizardHue()
}
}
+ var ledCount= Object.keys(lightIDs).length;
window.serverConfig.leds = hueLedConfig;
@@ -736,6 +756,7 @@ function beginWizardHue()
d.lightIds = finalLightIds;
d.username = $('#user').val();
d.type = "philipshue";
+ d.hardwareLedCount = ledCount;
d.transitiontime = 1;
d.switchOffOnBlack = true;
@@ -814,6 +835,7 @@ function get_hue_lights(){
for(var lightid in r)
{
+
$('.lidsb').append(createTableRow([lightid+' ('+r[lightid].name+')', '','']));
}
diff --git a/include/leddevice/LedDevice.h b/include/leddevice/LedDevice.h
index a587d5b0..29e5e83a 100644
--- a/include/leddevice/LedDevice.h
+++ b/include/leddevice/LedDevice.h
@@ -59,7 +59,9 @@ public:
unsigned int getLedCount() const { return _ledCount; }
bool enabled() const { return _enabled; }
+
int getLatchTime() const { return _latchTime_ms; }
+ void setLatchTime( int latchTime_ms );
///
/// Check, if device is ready to be used
diff --git a/libsrc/leddevice/LedDevice.cpp b/libsrc/leddevice/LedDevice.cpp
index 0f1e7410..30191903 100644
--- a/libsrc/leddevice/LedDevice.cpp
+++ b/libsrc/leddevice/LedDevice.cpp
@@ -231,6 +231,12 @@ void LedDevice::setLedCount(unsigned int ledCount)
_ledRGBWCount = _ledCount * sizeof(ColorRgbw);
}
+void LedDevice::setLatchTime( int latchTime_ms )
+{
+ _latchTime_ms = latchTime_ms;
+ Debug(_log, "LatchTime updated to %dms", this->getLatchTime());
+}
+
int LedDevice::rewriteLeds()
{
int retval = -1;
diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp
index 1aad493c..83f162f8 100644
--- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp
+++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp
@@ -37,13 +37,13 @@ static const char STATE_ONOFF_VALUE[] = "value";
static const char STATE_VALUE_TRUE[] = "true";
static const char STATE_VALUE_FALSE[] = "false";
-//Device Data elements
+// Device Data elements
static const char DEV_DATA_NAME[] = "name";
static const char DEV_DATA_MODEL[] = "model";
static const char DEV_DATA_MANUFACTURER[] = "manufacturer";
static const char DEV_DATA_FIRMWAREVERSION[] = "firmwareVersion";
-//Nanoleaf Stream Control elements
+// Nanoleaf Stream Control elements
//static const char STREAM_CONTROL_IP[] = "streamControlIpAddr";
static const char STREAM_CONTROL_PORT[] = "streamControlPort";
//static const char STREAM_CONTROL_PROTOCOL[] = "streamControlProtocol";
@@ -59,7 +59,7 @@ static const char API_STATE[] ="state";
static const char API_PANELLAYOUT[] = "panelLayout";
static const char API_EFFECT[] = "effects";
-//Nanoleaf ssdp services
+// Nanoleaf ssdp services
static const char SSDP_CANVAS[] = "nanoleaf:nl29";
static const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light";
const int SSDP_TIMEOUT = 5000; // timout in ms
@@ -132,7 +132,7 @@ bool LedDeviceNanoleaf::init(const QJsonObject &deviceConfig)
if ( _hostname.isEmpty() )
{
//Discover Nanoleaf device
- if ( !discoverNanoleafDevice() )
+ if ( !discoverDevice() )
{
this->setInError("No target IP defined nor Nanoleaf device was discovered");
return false;
@@ -256,24 +256,17 @@ int LedDeviceNanoleaf::open()
if ( init(_devConfig) )
{
- if ( !initNetwork() )
+ if ( initLeds() )
{
- this->setInError( "UDP Network error!" );
- }
- else
- {
- if ( initLeds() )
- {
- _deviceReady = true;
- setEnable(true);
- retval = 0;
- }
+ _deviceReady = true;
+ setEnable(true);
+ retval = 0;
}
}
return retval;
}
-bool LedDeviceNanoleaf::discoverNanoleafDevice()
+bool LedDeviceNanoleaf::discoverDevice()
{
bool isDeviceFound (false);
diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h
index 4fc28734..5ef704f5 100644
--- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h
+++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h
@@ -103,7 +103,7 @@ private:
///
/// @return True, if Nanoleaf device was found
///
- bool discoverNanoleafDevice();
+ bool discoverDevice();
///
/// Change Nanoleaf device to External Control (UDP) mode
diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp
index 83b672cf..8a5d3064 100644
--- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp
+++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp
@@ -1,91 +1,171 @@
// Local-Hyperion includes
#include "LedDevicePhilipsHue.h"
-// qt includes
+// ssdp discover
+#include
+
+// Qt includes
#include
+#include
#include
-bool operator ==(CiColor p1, CiColor p2)
+//
+static const bool verbose = false;
+static const bool verbose3 = false;
+
+// Configuration settings
+static const char CONFIG_ADDRESS[] = "output";
+//static const char CONFIG_PORT[] = "port";
+static const char CONFIG_USERNAME[] ="username";
+static const char CONFIG_BRIGHTNESSFACTOR [] = "brightnessFactor";
+static const char CONFIG_TRANSITIONTIME [] = "transitiontime";
+static const char CONFIG_ON_OFF_BLACK [] = "switchOffOnBlack";
+static const char CONFIG_RESTORE_STATE [] = "restoreOriginalState";
+static const char CONFIG_LIGHTIDS [] = "lightIds";
+
+// Device Data elements
+static const char DEV_DATA_BRIDGEID[] = "bridgeid";
+static const char DEV_DATA_MODEL[] = "modelid";
+static const char DEV_DATA_NAME[] = "name";
+//static const char DEV_DATA_MANUFACTURER[] = "manufacturer";
+static const char DEV_DATA_FIRMWAREVERSION[] = "swversion";
+static const char DEV_DATA_APIVERSION[] = "apiversion";
+
+// Philips Hue OpenAPI URLs
+static const char API_DEFAULT_PORT[] = "80";
+static const char API_URL_FORMAT[] = "http://%1:%2/api/%3/%4";
+static const char API_ROOT[] = "";
+static const char API_STATE[] ="state";
+static const char API_CONFIG[] = "config";
+static const char API_LIGHTS[] = "lights";
+
+// List of resources
+static const char API_XY_COORDINATES[] = "xy";
+static const char API_BRIGHTNESS[] = "bri";
+static const char API_TRANSITIONTIME[] = "transitiontime";
+static const char API_MODEID[] = "modelid";
+
+// List of State Information
+static const char API_STATE_ON[] = "on";
+static const char API_STATE_VALUE_TRUE[] = "true";
+static const char API_STATE_VALUE_FALSE[] = "false";
+
+// List of Error Information
+static const char API_ERROR[] = "error";
+static const char API_ERROR_ADDRESS[] = "address";
+static const char API_ERROR_DESCRIPTION[] = "description";
+static const char API_ERROR_TYPE[]="type";
+
+// Phlips Hue ssdp services
+static const char SSDP_ID[] = "urn:schemas-upnp-org:device:Basic:1";
+const int SSDP_TIMEOUT = 5000; // timout in ms
+
+
+bool operator ==(const CiColor& p1, const CiColor& p2)
{
- return (p1.x == p2.x) && (p1.y == p2.y) && (p1.bri == p2.bri);
+ return ((p1.x == p2.x) && (p1.y == p2.y) && (p1.bri == p2.bri));
}
-bool operator !=(CiColor p1, CiColor p2)
+bool operator != (const CiColor& p1, const CiColor& p2)
{
return !(p1 == p2);
}
-CiColor CiColor::rgbToCiColor(float red, float green, float blue, CiColorTriangle colorSpace)
+CiColor CiColor::rgbToCiColor(double red, double green, double 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.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);
+ double cx;
+ double cy;
+ double bri;
+
+ if(red + green + blue > 0)
+ {
+
+ // Apply gamma correction.
+ double r = (red > 0.04045) ? pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
+ double g = (green > 0.04045) ? pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
+ double b = (blue > 0.04045) ? pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
+
+ // Convert to XYZ space.
+ double X = r * 0.664511 + g * 0.154324 + b * 0.162028;
+ double Y = r * 0.283881 + g * 0.668433 + b * 0.047685;
+ double Z = r * 0.000088 + g * 0.072310 + b * 0.986039;
+
+ cx = X / (X + Y + Z);
+ cy = Y / (X + Y + Z);
+
+ // RGB to HSV/B Conversion before gamma correction V/B for brightness, not Y from XYZ Space.
+ // bri = std::max(std::max(red, green), blue);
+ // RGB to HSV/B Conversion after gamma correction V/B for brightness, not Y from XYZ Space.
+ bri = std::max(r, std::max(g, b));
+ }
+ else
+ {
+ cx = 0.0;
+ cy = 0.0;
+ bri = 0.0;
+ }
+
if (std::isnan(cx))
{
- cx = 0.0f;
+ cx = 0.0;
}
if (std::isnan(cy))
{
- cy = 0.0f;
+ cy = 0.0;
}
- // RGB to HSV/B Conversion after gamma correction use V for brightness, not Y from XYZ Space.
- float bri = fmax(fmax(r, g), b);
- CiColor xy =
- { cx, cy, bri };
- // Check if the given XY value is within the color reach of our lamps.
- if (!isPointInLampsReach(xy, colorSpace))
+ if (std::isnan(bri))
{
- // 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);
- CiColor pAC = getClosestPointToPoint(colorSpace.blue, colorSpace.red, xy);
- CiColor pBC = getClosestPointToPoint(colorSpace.green, colorSpace.blue, xy);
- // Get the distances per point and see which point is closer to our Point.
- float dAB = getDistanceBetweenTwoPoints(xy, pAB);
- float dAC = getDistanceBetweenTwoPoints(xy, pAC);
- float dBC = getDistanceBetweenTwoPoints(xy, pBC);
- float lowest = dAB;
- CiColor closestPoint = pAB;
- if (dAC < lowest)
+ bri = 0.0;
+ }
+
+ CiColor xy = { cx, cy, bri };
+
+ if(red + green + blue > 0)
+ {
+ // Check if the given XY value is within the color reach of our lamps.
+ if (!isPointInLampsReach(xy, colorSpace))
{
- lowest = dAC;
- closestPoint = pAC;
+ // 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);
+ CiColor pAC = getClosestPointToPoint(colorSpace.blue, colorSpace.red, xy);
+ CiColor pBC = getClosestPointToPoint(colorSpace.green, colorSpace.blue, xy);
+ // Get the distances per point and see which point is closer to our Point.
+ double dAB = getDistanceBetweenTwoPoints(xy, pAB);
+ double dAC = getDistanceBetweenTwoPoints(xy, pAC);
+ double dBC = getDistanceBetweenTwoPoints(xy, pBC);
+ double lowest = dAB;
+ CiColor closestPoint = pAB;
+ if (dAC < lowest)
+ {
+ lowest = dAC;
+ closestPoint = pAC;
+ }
+ if (dBC < lowest)
+ {
+ //lowest = dBC;
+ closestPoint = pBC;
+ }
+ // Change the xy value to a value which is within the reach of the lamp.
+ xy.x = closestPoint.x;
+ xy.y = closestPoint.y;
}
- if (dBC < lowest)
- {
- lowest = dBC;
- closestPoint = pBC;
- }
- // Change the xy value to a value which is within the reach of the lamp.
- xy.x = closestPoint.x;
- xy.y = closestPoint.y;
}
return xy;
}
-float CiColor::crossProduct(CiColor p1, CiColor p2)
+double 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))
+ 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 };
+ double s = crossProduct(q, v2) / crossProduct(v1, v2);
+ double t = crossProduct(v1, q) / crossProduct(v1, v2);
+ if ((s >= 0.0) && (t >= 0.0) && (s + t <= 1.0))
{
return true;
}
@@ -94,250 +174,522 @@ bool CiColor::isPointInLampsReach(CiColor p, CiColorTriangle colorSpace)
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)
+ CiColor AP = { p.x - a.x, p.y - a.y };
+ CiColor AB = { b.x - a.x, b.y - a.y };
+ double ab2 = AB.x * AB.x + AB.y * AB.y;
+ double ap_ab = AP.x * AB.x + AP.y * AB.y;
+ double t = ap_ab / ab2;
+ if (t < 0.0)
{
- t = 0.0f;
+ t = 0.0;
}
- else if (t > 1.0f)
+ else if (t > 1.0)
{
- t = 1.0f;
+ t = 1.0;
}
- return
- { a.x + AB.x * t, a.y + AB.y * t};
+ return { a.x + AB.x * t, a.y + AB.y * t };
}
-float CiColor::getDistanceBetweenTwoPoints(CiColor p1, CiColor p2)
+double CiColor::getDistanceBetweenTwoPoints(CiColor p1, CiColor p2)
{
// Horizontal difference.
- float dx = p1.x - p2.x;
+ double dx = p1.x - p2.x;
// Vertical difference.
- float dy = p1.y - p2.y;
+ double dy = p1.y - p2.y;
// Absolute value.
return sqrt(dx * dx + dy * dy);
}
-PhilipsHueBridge::PhilipsHueBridge(Logger* log, QString host, QString username)
- : QObject()
- , _log(log)
- , host(host)
- , username(username)
+LedDevicePhilipsHueBridge::LedDevicePhilipsHueBridge(const QJsonObject &deviceConfig)
+ : LedDevice(deviceConfig)
+ , _networkmanager (nullptr)
+ , _api_major(0)
+ , _api_minor(0)
+ , _api_patch(0)
+ , _isHueEntertainmentReady (false)
{
- // setup reconnection timer
- bTimer.setInterval(5000);
- bTimer.setSingleShot(true);
-
- connect(&bTimer, &QTimer::timeout, this, &PhilipsHueBridge::bConnect);
- connect(&manager, &QNetworkAccessManager::finished, this, &PhilipsHueBridge::resolveReply);
+ _devConfig = deviceConfig;
+ _deviceReady = false;
}
-void PhilipsHueBridge::bConnect(void)
+LedDevicePhilipsHueBridge::~LedDevicePhilipsHueBridge()
{
- if(username.isEmpty() || host.isEmpty())
+ if (_networkmanager != nullptr)
{
- Error(_log,"Username or IP Address is empty!");
+ delete _networkmanager;
+ _networkmanager = nullptr;
+ }
+}
+
+bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig)
+{
+ // Overwrite non supported/required features
+ _devConfig["latchTime"] = 0;
+ if (deviceConfig["rewriteTime"].toInt(0) > 0)
+ {
+ Info (_log, "Device Philips Hue does not require rewrites. Refresh time is ignored.");
+ _devConfig["rewriteTime"] = 0;
+ }
+
+ DebugIf(verbose, _log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData() );
+
+ bool isInitOK = LedDevice::init(deviceConfig);
+
+ Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
+ Debug(_log, "LedCount : %u", this->getLedCount());
+ Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
+ Debug(_log, "RefreshTime : %d", _refresh_timer_interval);
+ Debug(_log, "LatchTime : %d", this->getLatchTime());
+
+ if ( isInitOK )
+ {
+ //Set hostname as per configuration and_defaultHost default port
+ QString address = deviceConfig[ CONFIG_ADDRESS ].toString();
+
+ if (! address.isEmpty() )
+ {
+ QStringList addressparts = address.split(":", QString::SkipEmptyParts);
+
+ _hostname = addressparts[0];
+ if ( addressparts.size() > 1)
+ {
+ _api_port = addressparts[1];
+ }
+ else
+ {
+ _api_port = API_DEFAULT_PORT;
+ }
+ }
+ _username = deviceConfig[ CONFIG_USERNAME ].toString();
+
+ Debug(_log, "Hostname/IP : %s", QSTRING_CSTR( _hostname ));
+ Debug(_log, "Port : %s", QSTRING_CSTR( _api_port ));
+ }
+ return isInitOK;
+}
+
+int LedDevicePhilipsHueBridge::open( )
+{
+ return open (_hostname,_api_port, _username );
+}
+
+int LedDevicePhilipsHueBridge::open( const QString& hostname, const QString& port, const QString& username )
+{
+ _deviceInError = false;
+ bool isInitOK = true;
+
+ //If host not configured then discover device
+ if ( hostname.isEmpty() )
+ {
+ //Discover Nanoleaf device
+ if ( !discoverDevice() )
+ {
+ this->setInError("No target IP defined nor Philips Hue Bridge was discovered");
+ return false;
+ }
}
else
{
- QString url = QString("http://%1/api/%2").arg(host).arg(username);
- Debug(_log, "Connect to bridge %s", QSTRING_CSTR(url));
-
- QNetworkRequest request(url);
- manager.get(request);
+ _hostname = hostname;
+ _api_port = port;
}
-}
-void PhilipsHueBridge::resolveReply(QNetworkReply* reply)
-{
- // TODO use put request also for network error checking with decent threshold
- if(reply->operation() == QNetworkAccessManager::GetOperation)
+ _username = username;
+
+ //Get Philips Hue Bridge details and configuration
+ if ( _networkmanager == nullptr)
{
- if(reply->error() == QNetworkReply::NoError)
+ _networkmanager = new QNetworkAccessManager();
+ }
+
+ // Read Lights and Light-Ids
+ QString url = getUrl(_hostname, _api_port, _username, API_ROOT );
+ QJsonDocument doc = getJson( url );
+
+ DebugIf(verbose, _log, "doc: [%s]", QString(QJsonDocument(doc).toJson(QJsonDocument::Compact)).toUtf8().constData() );
+
+ if ( this->isInError() )
+ {
+ isInitOK = false;
+ }
+ else
+ {
+ QJsonObject jsonConfigInfo = doc.object()[API_CONFIG].toObject();
+ if ( verbose )
{
- QByteArray response = reply->readAll();
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(response, &error);
- if (error.error != QJsonParseError::NoError)
- {
- Error(_log, "Got invalid response from bridge");
- return;
- }
- // check for authorization
- if(doc.isArray())
- {
- Error(_log, "Authorization failed, username invalid");
- return;
- }
+ std::cout << "jsonConfigInfo: [" << QString(QJsonDocument(jsonConfigInfo).toJson(QJsonDocument::Compact)).toUtf8().constData() << "]" << std::endl;
+ }
- QJsonObject obj = doc.object()["lights"].toObject();
+ QString deviceName = jsonConfigInfo[DEV_DATA_NAME].toString();
+ _deviceModel = jsonConfigInfo[DEV_DATA_MODEL].toString();
+ QString deviceBridgeID = jsonConfigInfo[DEV_DATA_BRIDGEID].toString();
+ _deviceFirmwareVersion = jsonConfigInfo[DEV_DATA_FIRMWAREVERSION].toString();
+ _deviceAPIVersion = jsonConfigInfo[DEV_DATA_APIVERSION].toString();
- if(obj.isEmpty())
- {
- Error(_log, "Bridge has no registered bulbs/stripes");
- return;
- }
+ QStringList apiVersionParts = _deviceAPIVersion.split(".", QString::SkipEmptyParts);
+ if ( !apiVersionParts.isEmpty())
+ {
+ _api_major = apiVersionParts[0].toUInt();
+ _api_minor = apiVersionParts[1].toUInt();
+ _api_patch = apiVersionParts[2].toUInt();
- // get all available light ids and their values
- QStringList keys = obj.keys();
- QMap map;
- for (int i = 0; i < keys.size(); ++i)
+ if ( _api_major > 1 || (_api_major == 1 && _api_minor >= 22) )
{
- map.insert(keys.at(i).toInt(), obj.take(keys.at(i)).toObject());
+ _isHueEntertainmentReady = true;
}
- emit newLights(map);
+ }
+
+ Debug(_log, "Bridge Name : %s", QSTRING_CSTR( deviceName ));
+ Debug(_log, "Model : %s", QSTRING_CSTR( _deviceModel ));
+ Debug(_log, "Bridge-ID : %s", QSTRING_CSTR( deviceBridgeID ));
+ Debug(_log, "SoftwareVersion : %s", QSTRING_CSTR( _deviceFirmwareVersion));
+ Debug(_log, "API-Version : %u.%u.%u", _api_major,_api_minor, _api_patch );
+ Debug(_log, "EntertainmentReady: %d", _isHueEntertainmentReady);
+
+ QJsonObject jsonLightsInfo = doc.object()[API_LIGHTS].toObject();
+
+ DebugIf(verbose, _log, "jsonLightsInfo: [%s]", QString(QJsonDocument(jsonLightsInfo).toJson(QJsonDocument::Compact)).toUtf8().constData() );
+
+ // Get all available light ids and their values
+ QStringList keys = jsonLightsInfo.keys();
+
+ _ledCount = keys.size();
+ _lightsMap.clear();
+
+ for (uint i = 0; i < _ledCount; ++i)
+ {
+ _lightsMap.insert(keys.at(i).toInt(), jsonLightsInfo.take(keys.at(i)).toObject());
+ }
+
+ if (getLedCount() == 0 )
+ {
+ setInError("No light-IDs found at the Philips Hue Bridge");
}
else
{
- Error(_log,"Network Error: %s", QSTRING_CSTR(reply->errorString()));
- bTimer.start();
+ Debug(_log, "Lights found : %u", getLedCount() );
}
}
- reply->deleteLater();
+ return isInitOK;
}
-void PhilipsHueBridge::post(QString route, QString content)
+bool LedDevicePhilipsHueBridge::discoverDevice()
{
- //Debug(_log, "Post %s: %s", QSTRING_CSTR(QString("http://IP/api/USR/%1").arg(route)), QSTRING_CSTR(content));
+ bool isDeviceFound (false);
- QNetworkRequest request(QString("http://%1/api/%2/%3").arg(host).arg(username).arg(route));
- manager.put(request, content.toLatin1());
-}
+ // device searching by ssdp
+ QString address;
+ SSDPDiscover discover;
-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", "LCT011", "LCT012", "LCT010", "LCT014", "LCT015", "LCT016", "LCT024" };
-
-PhilipsHueLight::PhilipsHueLight(Logger* log, PhilipsHueBridge* bridge, unsigned int id, QJsonObject values)
- : _log(log)
- , bridge(bridge)
- , id(id)
-{
- // Get state object values which are subject to change.
- if (!values["state"].toObject().contains("on"))
+ // Discover Philips Hue Bridge
+ address = discover.getFirstService(STY_WEBSERVER, SSDP_ID, SSDP_TIMEOUT);
+ if ( address.isEmpty() )
{
- Error(_log, "Got invalid state object from light ID %d", id);
- }
- QJsonObject state;
- state["on"] = values["state"].toObject()["on"];
- on = false;
- if (values["state"].toObject()["on"].toBool())
- {
- state["xy"] = values["state"].toObject()["xy"];
- state["bri"] = values["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 = values["state"].toObject()["transitiontime"].toInt();
- }
- // Determine the model id.
- modelId = values["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 of light ID %d as gamut A", modelId.toStdString().c_str(), id);
- colorSpace.red =
- { 0.704f, 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 of light ID %d as gamut B", modelId.toStdString().c_str(), id);
- colorSpace.red =
- { 0.675f, 0.322f};
- colorSpace.green =
- { 0.409f, 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 of light ID %d as gamut C", modelId.toStdString().c_str(), id);
- colorSpace.red =
- { 0.6915f, 0.3083f};
- colorSpace.green =
- { 0.17f, 0.7f};
- colorSpace.blue =
- { 0.1532f, 0.0475f};
+ Warning(_log, "No Philips Hue Bridge discovered");
}
else
{
- Warning(_log, "Did not recognize model id %s of light ID %d", modelId.toStdString().c_str(), id);
- colorSpace.red =
- { 1.0f, 0.0f};
- colorSpace.green =
- { 0.0f, 1.0f};
- colorSpace.blue =
- { 0.0f, 0.0f};
+ // Philips Hue Bridge found
+ Info(_log, "Philips Hue Bridge discovered at [%s]", QSTRING_CSTR( address ));
+ isDeviceFound = true;
+ QStringList addressparts = address.split(":", QString::SkipEmptyParts);
+ _hostname = addressparts[0];
+ _api_port = addressparts[1];
+ }
+ return isDeviceFound;
+}
+
+const QMap& LedDevicePhilipsHueBridge::getLightMap(void)
+{
+ return _lightsMap;
+}
+
+QString LedDevicePhilipsHueBridge::getUrl(QString host, QString port, QString auth_token, QString endpoint) const {
+ return QString(API_URL_FORMAT).arg(host, port, auth_token, endpoint);
+}
+
+QJsonDocument LedDevicePhilipsHueBridge::getJson(QString url)
+{
+
+ DebugIf(verbose, _log, "GET: [%s]", QSTRING_CSTR( url ));
+
+ // Perfrom request
+ QNetworkRequest request(url);
+ QNetworkReply* reply = _networkmanager->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();
+
+ QJsonDocument jsonDoc;
+ if(reply->operation() == QNetworkAccessManager::GetOperation)
+ {
+ jsonDoc = handleReply( reply );
+ }
+ // Free space.
+ reply->deleteLater();
+ // Return response
+ return jsonDoc;
+}
+
+QJsonDocument LedDevicePhilipsHueBridge::putJson(QString url, QString json)
+{
+
+ DebugIf(verbose, _log, "PUT: [%s] [%s]", QSTRING_CSTR( url ), QSTRING_CSTR( json ) );
+ // Perfrom request
+ QNetworkRequest request(url);
+ QNetworkReply* reply = _networkmanager->put(request, json.toUtf8());
+ // 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();
+
+ QJsonDocument jsonDoc;
+ if(reply->operation() == QNetworkAccessManager::PutOperation)
+ {
+ jsonDoc = handleReply( reply );
+ }
+ // Free space.
+ reply->deleteLater();
+
+ // Return response
+ return jsonDoc;
+}
+
+QJsonDocument LedDevicePhilipsHueBridge::handleReply(QNetworkReply* const &reply )
+{
+ QJsonDocument jsonDoc;
+
+ int httpStatusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
+ DebugIf(verbose, _log, "Reply.httpStatusCode [%d]", httpStatusCode );
+ QString errorReason;
+
+ if(reply->error() == QNetworkReply::NoError)
+ {
+ if ( httpStatusCode != 204 ){
+ QByteArray response = reply->readAll();
+ QJsonParseError error;
+ jsonDoc = QJsonDocument::fromJson(response, &error);
+ if (error.error != QJsonParseError::NoError)
+ {
+ this->setInError ( "Got invalid response" );
+ }
+ else
+ {
+ QString strJson(jsonDoc.toJson(QJsonDocument::Compact));
+ DebugIf(verbose, _log, "Reply: [%s]", strJson.toUtf8().constData() );
+
+ QVariantList rspList = jsonDoc.toVariant().toList();
+ if ( !rspList.isEmpty() )
+ {
+ QVariantMap map = rspList.first().toMap();
+ if ( map.contains(API_ERROR) )
+ {
+ // API call failsed to execute an error message was returned
+ QString errorAddress = map.value(API_ERROR).toMap().value(API_ERROR_ADDRESS).toString();
+ QString errorDesc = map.value(API_ERROR).toMap().value(API_ERROR_DESCRIPTION).toString();
+ QString errorType = map.value(API_ERROR).toMap().value(API_ERROR_TYPE).toString();
+
+ Debug(_log, "Error Type : %s", QSTRING_CSTR( errorType ));
+ Debug(_log, "Error Address : %s", QSTRING_CSTR( errorAddress ));
+ Debug(_log, "Error Description : %s", QSTRING_CSTR( errorDesc ));
+
+ errorReason = QString ("(%1) %2, Resource:%3").arg(errorType, errorDesc, errorAddress);
+ this->setInError ( errorReason );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( httpStatusCode > 0 ) {
+ QString httpReason = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString();
+ QString advise;
+ switch ( httpStatusCode ) {
+ case 400:
+ advise = "Check Request Body";
+ break;
+ case 401:
+ advise = "Check Authentication Token (API Key)";
+ break;
+ case 404:
+ advise = "Check Resource given";
+ break;
+ default:
+ break;
+ }
+ errorReason = QString ("%1:%2 [%3 %4] - %5").arg(_hostname, _api_port, QString(httpStatusCode) , httpReason, advise);
+ }
+ else {
+ errorReason = QString ("%1:%2 - %3").arg(_hostname, _api_port, reply->errorString());
+ }
+ this->setInError ( errorReason );
+ }
+ // Return response
+ return jsonDoc;
+}
+
+void LedDevicePhilipsHueBridge::post(const QString& route, const QString& content)
+{
+ QString url = getUrl(_hostname, _api_port, _username, route );
+ putJson( url, content );
+}
+
+void LedDevicePhilipsHueBridge::setLightState(const unsigned int lightId, QString state)
+{
+ Debug(_log, "SetLightState [%u]: %s", lightId, QSTRING_CSTR(state));
+ post( QString("%1/%2/%3").arg(API_LIGHTS).arg(lightId).arg(API_STATE), state );
+}
+
+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", "LCT011", "LCT012", "LCT010", "LCT014", "LCT015", "LCT016", "LCT024" };
+
+PhilipsHueLight::PhilipsHueLight(Logger* log, unsigned int id, QJsonObject values, unsigned int ledidx)
+ : _log(log)
+ , _id(id)
+ , _ledidx(ledidx)
+ , _on(false)
+ , _transitionTime(0)
+ , _colorBlack({0.0, 0.0, 0.0})
+ , _modelId (values[API_MODEID].toString().trimmed().replace("\"", ""))
+{
+ // 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 of light ID %d as gamut A", QSTRING_CSTR(_modelId), id);
+ _colorSpace.red = {0.704, 0.296};
+ _colorSpace.green = {0.2151, 0.7106};
+ _colorSpace.blue = {0.138, 0.08};
+ _colorBlack = {0.138, 0.08, 0.0};
+ }
+ else if (GAMUT_B_MODEL_IDS.find(_modelId) != GAMUT_B_MODEL_IDS.end())
+ {
+ Debug(_log, "Recognized model id %s of light ID %d as gamut B", QSTRING_CSTR(_modelId), id);
+ _colorSpace.red = {0.675, 0.322};
+ _colorSpace.green = {0.409, 0.518};
+ _colorSpace.blue = {0.167, 0.04};
+ _colorBlack = {0.167, 0.04, 0.0};
+ }
+ else if (GAMUT_C_MODEL_IDS.find(_modelId) != GAMUT_C_MODEL_IDS.end())
+ {
+ Debug(_log, "Recognized model id %s of light ID %d as gamut C", QSTRING_CSTR(_modelId), id);
+ _colorSpace.red = {0.6915, 0.3083};
+ _colorSpace.green = {0.17, 0.7};
+ _colorSpace.blue = {0.1532, 0.0475};
+ _colorBlack = {0.1532, 0.0475, 0.0};
+ }
+ else
+ {
+ Warning(_log, "Did not recognize model id %s of light ID %d", QSTRING_CSTR(_modelId), id);
+ _colorSpace.red = {1.0, 0.0};
+ _colorSpace.green = {0.0, 1.0};
+ _colorSpace.blue = {0.0, 0.0};
+ _colorBlack = {0.0, 0.0, 0.0};
}
- Info(_log,"Light ID %d created", id);
+ saveOriginalState(values);
+
+ _lightname = values["name"].toString().trimmed().replace("\"", "");
+ Info(_log,"Light ID %d (\"%s\", LED index \"%d\") created", id, QSTRING_CSTR(_lightname), ledidx);
}
PhilipsHueLight::~PhilipsHueLight()
{
- // Restore the original state.
- set(originalState);
}
-void PhilipsHueLight::set(QString state)
+unsigned int PhilipsHueLight::getId() const
{
- bridge->post(QString("lights/%1/state").arg(id), state);
+ return _id;
}
-void PhilipsHueLight::setOn(bool on)
+QString PhilipsHueLight::getOriginalState()
{
- if (this->on != on)
+ return _originalState;
+}
+
+void PhilipsHueLight::saveOriginalState(const QJsonObject& values)
+{
+ // Get state object values which are subject to change.
+ if (!values[API_STATE].toObject().contains("on"))
{
- QString arg = on ? "true" : "false";
- set(QString("{ \"on\": %1 }").arg(arg));
+ Error(_log, "Got invalid state object from light ID %d", _id);
}
- this->on = on;
+ QJsonObject lState = values[API_STATE].toObject();
+ _originalStateJSON = lState;
+
+ QJsonObject state;
+ state["on"] = lState["on"];
+ _originalColor = _colorBlack;
+ QString c;
+ if (state[API_STATE_ON].toBool())
+ {
+ state[API_XY_COORDINATES] = lState[API_XY_COORDINATES];
+ state[API_BRIGHTNESS] = lState[API_BRIGHTNESS];
+ _on = true;
+ _color = {
+ state[API_XY_COORDINATES].toArray()[0].toDouble(),
+ state[API_XY_COORDINATES].toArray()[1].toDouble(),
+ state[API_BRIGHTNESS].toDouble() / 254.0
+ };
+ _originalColor = _color;
+ c = QString("{ \"%1\": [%2, %3], \"%4\": %5 }").arg(API_XY_COORDINATES).arg(_originalColor.x, 0, 'd', 4).arg(_originalColor.y, 0, 'd', 4).arg(API_BRIGHTNESS).arg((_originalColor.bri * 254.0), 0, 'd', 4);
+ DebugIf(verbose, _log, "OriginalColor state on: %s", QSTRING_CSTR(c));
+ _transitionTime = values[API_STATE].toObject()[API_TRANSITIONTIME].toInt();
+ }
+ //Determine the original state.
+ _originalState = QJsonDocument(state).toJson(QJsonDocument::JsonFormat::Compact).trimmed();
+}
+
+void PhilipsHueLight::setOnOffState(bool on)
+{
+ this->_on = on;
}
void PhilipsHueLight::setTransitionTime(unsigned int transitionTime)
{
- if (this->transitionTime != transitionTime)
- {
- set(QString("{ \"transitiontime\": %1 }").arg(transitionTime));
- }
- this->transitionTime = transitionTime;
+ this->_transitionTime = transitionTime;
}
-void PhilipsHueLight::setColor(CiColor color, float brightnessFactor)
+void PhilipsHueLight::setColor(const CiColor& color)
{
- 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;
+ this->_color = color;
+}
+
+bool PhilipsHueLight::getOnOffState() const
+{
+ return _on;
+}
+
+unsigned int PhilipsHueLight::getTransitionTime() const
+{
+ return _transitionTime;
}
CiColor PhilipsHueLight::getColor() const
{
- return color;
+ return _color;
}
CiColorTriangle PhilipsHueLight::getColorSpace() const
{
- return colorSpace;
+ return _colorSpace;
+}
+
+LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject& deviceConfig)
+ : LedDevicePhilipsHueBridge(deviceConfig)
+ , _switchOffOnBlack (false)
+ , _brightnessFactor(1.0)
+ , _transitionTime (1)
+ , _isRestoreOrigState (true)
+{
+ _devConfig = deviceConfig;
+ _deviceReady = false;
}
LedDevice* LedDevicePhilipsHue::construct(const QJsonObject &deviceConfig)
@@ -345,74 +697,132 @@ LedDevice* LedDevicePhilipsHue::construct(const QJsonObject &deviceConfig)
return new LedDevicePhilipsHue(deviceConfig);
}
-LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject& deviceConfig)
- : LedDevice(deviceConfig)
- , _bridge(nullptr)
-{
-
-}
-
LedDevicePhilipsHue::~LedDevicePhilipsHue()
{
- switchOff();
- delete _bridge;
-}
-
-void LedDevicePhilipsHue::start()
-{
- _bridge = new PhilipsHueBridge(_log, _devConfig["output"].toString(), _devConfig["username"].toString());
- _deviceReady = init(_devConfig);
-
- connect(_bridge, &PhilipsHueBridge::newLights, this, &LedDevicePhilipsHue::newLights);
- connect(this, &LedDevice::enableStateChanged, this, &LedDevicePhilipsHue::stateChanged);
}
bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig)
{
- switchOffOnBlack = deviceConfig["switchOffOnBlack"].toBool(true);
- brightnessFactor = (float) deviceConfig["brightnessFactor"].toDouble(1.0);
- transitionTime = deviceConfig["transitiontime"].toInt(1);
- QJsonArray lArray = deviceConfig["lightIds"].toArray();
+ bool isInitOK = LedDevicePhilipsHueBridge::init(deviceConfig);
- QJsonObject newDC = deviceConfig;
- if(!lArray.empty())
+ if ( isInitOK )
{
- for(const auto i : lArray)
+ // Initiatiale LedDevice configuration and execution environment
+ _switchOffOnBlack = _devConfig[CONFIG_ON_OFF_BLACK].toBool(true);
+ _brightnessFactor = _devConfig[CONFIG_BRIGHTNESSFACTOR].toDouble(1.0);
+ _transitionTime = _devConfig[CONFIG_TRANSITIONTIME].toInt(1);
+ _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(true);
+ QJsonArray lArray = _devConfig[CONFIG_LIGHTIDS].toArray();
+
+ _lightIds.clear();
+ if(!lArray.empty())
{
- lightIds.push_back(i.toInt());
+ for(const auto i : lArray)
+ {
+ _lightIds.push_back(i.toInt());
+ }
}
- // get light info from bridge
- _bridge->bConnect();
- // adapt latchTime to count of user lightIds (bridge 10Hz max overall)
- newDC.insert("latchTime",QJsonValue(100*(int)lightIds.size()));
+ uint configuredLightsCount = _lightIds.size();
+ Debug(_log, "Off on Black : %d", _switchOffOnBlack );
+ Debug(_log, "Brightness Factor : %f", _brightnessFactor );
+ Debug(_log, "Transition Time : %d", _transitionTime );
+ Debug(_log, "Light IDs defined : %d", configuredLightsCount );
+
+ if ( configuredLightsCount == 0)
+ {
+ setInError("No light-IDs configured");
+ isInitOK = false;
+ }
}
- else
- {
- Error(_log,"No light ID provided, abort");
- }
-
- LedDevice::init(newDC);
-
- return true;
+ return isInitOK;
}
-void LedDevicePhilipsHue::newLights(QMap map)
+bool LedDevicePhilipsHue::initLeds()
{
- if(!lightIds.empty())
+ bool isInitOK = false;
+
+ if ( !isInError() )
+ {
+ updateLights( getLightMap() );
+ // adapt latchTime to count of user lightIds (bridge 10Hz max overall)
+ setLatchTime( static_cast( 100 * getLightsCount() ) );
+ isInitOK = true;
+ }
+
+ return isInitOK;
+}
+
+void LedDevicePhilipsHue::updateLights(QMap map)
+{
+ if(!_lightIds.empty())
{
// search user lightid inside map and create light if found
- lights.clear();
- for(const auto id : lightIds)
+ _lights.clear();
+
+ unsigned int ledidx = 0;
+ _lights.reserve(_lightIds.size());
+ for(const auto id : _lightIds)
{
if (map.contains(id))
{
- lights.push_back(PhilipsHueLight(_log, _bridge, id, map.value(id)));
+ _lights.emplace_back(_log, id, map.value(id), ledidx);
}
else
{
- Error(_log,"Light id %d isn't used on this bridge", id);
+ Warning(_log,"Configured light-ID %d is not available at this bridge", id);
}
+ ledidx++;
+ }
+ setLightsCount ( _lights.size() );
+ }
+}
+
+int LedDevicePhilipsHue::open()
+{
+ int retval = -1;
+ QString errortext;
+ _deviceReady = false;
+
+ // General initialisation and configuration of LedDevice
+ if ( init(_devConfig) )
+ {
+ if ( LedDevicePhilipsHueBridge::open() )
+ // Open/Start LedDevice based on configuration
+ {
+ if ( initLeds() )
+ {
+ // Everything is OK -> enable device
+ _deviceReady = true;
+ setEnable(true);
+ retval = 0;
+ }
+ }
+ }
+ return retval;
+}
+
+void LedDevicePhilipsHue::restoreOriginalState()
+{
+ if(!_lightIds.empty())
+ {
+ for (PhilipsHueLight& light : _lights)
+ {
+ setLightState(light.getId(),light.getOriginalState());
+ }
+ }
+}
+
+void LedDevicePhilipsHue::close()
+{
+ LedDevicePhilipsHueBridge::close();
+
+ if ( _deviceReady)
+ {
+ if ( _isRestoreOrigState )
+ {
+ //Restore Philips Hue devices state
+ restoreOriginalState();
}
}
}
@@ -420,47 +830,139 @@ void LedDevicePhilipsHue::newLights(QMap map)
int LedDevicePhilipsHue::write(const std::vector & ledValues)
{
// lights will be empty sometimes
- if(lights.empty()) return -1;
+ if(_lights.empty()) return -1;
// more lights then leds, stop always
- if(ledValues.size() < lights.size())
+ if(ledValues.size() < getLightsCount() )
{
- Error(_log,"More LightIDs configured than leds, each LightID requires one led!");
+ Error(_log,"More light-IDs configured than leds, each light-ID requires one led!");
return -1;
}
- // Iterate through lights and set colors.
- unsigned int idx = 0;
- for (PhilipsHueLight& light : lights)
- {
- // Get color.
- ColorRgb color = ledValues.at(idx);
- // Scale colors from [0, 255] to [0, 1] and convert to xy space.
- CiColor xy = CiColor::rgbToCiColor(color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f,
- light.getColorSpace());
-
- if (switchOffOnBlack && xy.bri == 0)
- {
- light.setOn(false);
- }
- else
- {
- light.setOn(true);
- // Write color if color has been changed.
- light.setTransitionTime(transitionTime);
- light.setColor(xy, brightnessFactor);
- }
-
- idx++;
- }
+ writeSingleLights (ledValues);
return 0;
}
-void LedDevicePhilipsHue::stateChanged(bool newState)
+int LedDevicePhilipsHue::writeSingleLights(const std::vector& ledValues)
{
- if(newState)
- _bridge->bConnect();
- else
- lights.clear();
+ // Iterate through lights and set colors.
+ unsigned int idx = 0;
+ for (PhilipsHueLight& light : _lights)
+ {
+ // Get color.
+ ColorRgb color = ledValues.at(idx);
+ // Scale colors from [0, 255] to [0, 1] and convert to xy space.
+ CiColor xy = CiColor::rgbToCiColor(color.red / 255.0, color.green / 255.0, color.blue / 255.0, light.getColorSpace());
+
+ if (_switchOffOnBlack && xy.bri == 0.0)
+ {
+ this->setOnOffState(light, false);
+ }
+ else
+ {
+ // Write color if color has been changed.
+ this->setState(light, true, xy, _brightnessFactor,_transitionTime);
+ }
+ idx++;
+ }
+ return 0;
+}
+
+void LedDevicePhilipsHue::setOnOffState(PhilipsHueLight& light, bool on)
+{
+ if (light.getOnOffState() != on)
+ {
+ light.setOnOffState( on );
+ QString state = on ? API_STATE_VALUE_TRUE : API_STATE_VALUE_FALSE;
+ setLightState( light.getId(), QString("{\"%1\": %2 }").arg(API_STATE_ON, state) );
+ }
+}
+
+void LedDevicePhilipsHue::setTransitionTime(PhilipsHueLight& light, unsigned int transitionTime)
+{
+ if (light.getTransitionTime() != transitionTime)
+ {
+ light.setTransitionTime( transitionTime );
+ setLightState( light.getId(), QString("{\"%1\": %2 }").arg(API_TRANSITIONTIME).arg( transitionTime ) );
+ }
+}
+
+void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, const CiColor& color, double brightnessFactor)
+{
+ const int bri = qRound(qMin(254.0, brightnessFactor * qMax(1.0, color.bri * 254.0)));
+ if (light.getColor() != color)
+ {
+ light.setColor( color) ;
+ QString stateCmd = QString("\"%1\":[%2,%3],\"%4\":%5").arg(API_XY_COORDINATES).arg(color.x, 0, 'd', 4).arg(color.y, 0, 'd', 4).arg (API_BRIGHTNESS).arg(bri);
+ setLightState( light.getId(), stateCmd );
+ }
+}
+
+void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColor& color, double brightnessFactor, unsigned int transitionTime)
+{
+
+ QString stateCmd;
+
+ if (light.getOnOffState() != on)
+ {
+ light.setOnOffState( on );
+ QString state = on ? API_STATE_VALUE_TRUE : API_STATE_VALUE_FALSE;
+ stateCmd += QString("\"%1\":%2,").arg(API_STATE_ON, state);
+ }
+
+ if (light.getTransitionTime() != transitionTime)
+ {
+ light.setTransitionTime( transitionTime );
+ stateCmd += QString("\"%1\":%2,").arg(API_TRANSITIONTIME).arg( transitionTime );
+ }
+
+ const int bri = qRound(qMin(254.0, brightnessFactor * qMax(1.0, color.bri * 254.0)));
+ if (light.getColor() != color)
+ {
+ light.setColor( color) ;
+ stateCmd += QString("\"%1\":[%2,%3],\"%4\":%5").arg(API_XY_COORDINATES).arg(color.x, 0, 'd', 4).arg(color.y, 0, 'd', 4).arg (API_BRIGHTNESS).arg(bri);
+
+ }
+
+ if ( !stateCmd.isEmpty() )
+ {
+ setLightState( light.getId(), "{" + stateCmd + "}" );
+ }
+}
+
+
+void LedDevicePhilipsHue::setLightsCount( unsigned int lightsCount )
+{
+ _lightsCount = lightsCount;
+}
+
+
+int LedDevicePhilipsHue::switchOn()
+{
+ if ( _deviceReady)
+ {
+ //Switch on Philips Hue devices physically
+ for (PhilipsHueLight& light : _lights)
+ {
+ setOnOffState(light,true);
+ }
+ }
+ return 0;
+}
+
+int LedDevicePhilipsHue::switchOff()
+{
+ //Set all LEDs to Black
+ int rc = LedDevice::switchOff();
+
+ if ( _deviceReady)
+ {
+ //Switch off Philips Hue devices physically
+ for (PhilipsHueLight& light : _lights)
+ {
+ setOnOffState(light,false);
+ }
+ }
+ return rc;
}
diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h
index 655fe1cc..96332796 100644
--- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h
+++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h
@@ -19,11 +19,11 @@ struct CiColorTriangle;
struct CiColor
{
/// X component.
- float x;
+ double x;
/// Y component.
- float y;
+ double y;
/// The brightness.
- float bri;
+ double bri;
///
/// Converts an RGB color to the Hue xy color space and brightness.
@@ -37,7 +37,7 @@ struct CiColor
///
/// @return color point
///
- static CiColor rgbToCiColor(float red, float green, float blue, CiColorTriangle colorSpace);
+ static CiColor rgbToCiColor(double red, double green, double blue, CiColorTriangle colorSpace);
///
/// @param p the color point to check
@@ -53,7 +53,7 @@ struct CiColor
///
/// @return the cross product between p1 and p2
///
- static float crossProduct(CiColor p1, CiColor p2);
+ static double crossProduct(CiColor p1, CiColor p2);
///
/// @param a reference point one
@@ -73,11 +73,11 @@ struct CiColor
///
/// @return the distance between the two points
///
- static float getDistanceBetweenTwoPoints(CiColor p1, CiColor p2);
+ static double getDistanceBetweenTwoPoints(CiColor p1, CiColor p2);
};
-bool operator==(CiColor p1, CiColor p2);
-bool operator!=(CiColor p1, CiColor p2);
+bool operator==(const CiColor& p1, const CiColor& p2);
+bool operator!=(const CiColor& p1, const CiColor& p2);
/**
* Color triangle to define an available color space for the hue lamps.
@@ -87,74 +87,11 @@ struct CiColorTriangle
CiColor red, green, blue;
};
-class PhilipsHueBridge : public QObject
-{
- Q_OBJECT
-
-private:
- Logger* _log;
- /// QNetworkAccessManager for sending requests.
- QNetworkAccessManager manager;
- /// Ip address of the bridge
- QString host;
- /// User name for the API ("newdeveloper")
- QString username;
- /// Timer for bridge reconnect interval
- QTimer bTimer;
-
-private slots:
- ///
- /// Receive all replies and check for error, schedule reconnect on issues
- /// Emits newLights() on success when triggered from connect()
- ///
- void resolveReply(QNetworkReply* reply);
-
-public slots:
- ///
- /// Connect to bridge to check availbility and user
- ///
- void bConnect(void);
-
-signals:
- ///
- /// Emits with a QMap of current bridge light/value pairs
- ///
- void newLights(QMap map);
-
-public:
- PhilipsHueBridge(Logger* log, QString host, QString username);
-
- ///
- /// @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
{
-private:
- Logger* _log;
- PhilipsHueBridge* bridge;
- /// light id
- unsigned int id;
- 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).
@@ -172,32 +109,183 @@ public:
/// @param bridge the bridge
/// @param id the light id
///
- PhilipsHueLight(Logger* log, PhilipsHueBridge* bridge, unsigned int id, QJsonObject values);
+ PhilipsHueLight(Logger* log, unsigned int id, QJsonObject values, unsigned int ledidx);
~PhilipsHueLight();
///
/// @param on
///
- void setOn(bool on);
+ void setOnOffState(bool on);
///
/// @param transitionTime the transition time between colors in multiples of 100 ms
///
- void setTransitionTime(unsigned int transitionTime);
+ void setTransitionTime(unsigned int _transitionTime);
///
/// @param color the color to set
- /// @param brightnessFactor the factor to apply to the CiColor#bri value
///
- void setColor(CiColor color, float brightnessFactor = 1.0f);
+ void setColor(const CiColor& _color);
+
+
+ unsigned int getId() const;
+
+ bool getOnOffState() const;
+ unsigned int getTransitionTime() const;
CiColor getColor() const;
///
/// @return the color space of the light determined by the model id reported by the bridge.
CiColorTriangle getColorSpace() const;
+
+ QString getOriginalState();
+
+
+private:
+
+ void saveOriginalState(const QJsonObject& values);
+
+ Logger* _log;
+ /// light id
+ unsigned int _id;
+ unsigned int _ledidx;
+ bool _on;
+ unsigned int _transitionTime;
+ CiColor _color;
+ /// darkes blue color in hue lamp GAMUT = black
+ CiColor _colorBlack;
+ /// The model id of the hue lamp which is used to determine the color space.
+ QString _modelId;
+ QString _lightname;
+ CiColorTriangle _colorSpace;
+
+ /// The json string of the original state.
+ QJsonObject _originalStateJSON;
+
+ QString _originalState;
+ CiColor _originalColor;
};
+class LedDevicePhilipsHueBridge : public LedDevice
+{
+ Q_OBJECT
+
+public:
+
+ explicit LedDevicePhilipsHueBridge(const QJsonObject &deviceConfig);
+ ~LedDevicePhilipsHueBridge();
+
+ ///
+ /// Sets configuration
+ ///
+ /// @param deviceConfig the json device config
+ /// @return true if success
+ virtual bool init(const QJsonObject &deviceConfig) override;
+
+
+
+ ///
+ /// @param route the route of the POST request.
+ ///
+ /// @param content the content of the POST request.
+ ///
+ void post(const QString& route, const QString& content);
+
+ void setLightState(unsigned int lightId = 0, QString state = "");
+
+ const QMap& getLightMap();
+
+// /// Set device in error state
+// ///
+// /// @param errorMsg The error message to be logged
+// ///
+// virtual void setInError( const QString& errorMsg) override;
+
+public slots:
+ ///
+ /// Connect to bridge to check availbility and user
+ ///
+ virtual int open(void) override;
+ virtual int open( const QString& hostname, const QString& port, const QString& username );
+
+ //signals:
+ // ///
+ // /// Emits with a QMap of current bridge light/value pairs
+ // ///
+ // void newLights(QMap map);
+protected:
+
+ /// Ip address of the bridge
+ QString _hostname;
+ QString _api_port;
+ /// User name for the API ("newdeveloper")
+ QString _username;
+
+private:
+
+ ///
+ /// Discover device via SSDP identifiers
+ ///
+ /// @return True, if device was found
+ ///
+ bool discoverDevice();
+
+ ///
+ /// Get command as url
+ ///
+ /// @param host Hostname or IP
+ /// @param port IP-Port
+ /// @param _auth_token Authorization token
+ /// @param Endpoint command for request
+ /// @return Url to execute endpoint/command
+ ///
+ QString getUrl(QString host, QString port, QString auth_token, QString endpoint) const;
+
+ ///
+ /// Execute GET request
+ ///
+ /// @param url GET request for url
+ /// @return Response from device
+ ///
+ QJsonDocument getJson(QString url);
+
+ ///
+ /// Execute PUT request
+ ///
+ /// @param Url for PUT request
+ /// @param json Command for request
+ /// @return Response from device
+ ///
+ QJsonDocument putJson(QString url, QString json);
+
+ ///
+ /// Handle replys for GET and PUT requests
+ ///
+ /// @param reply Network reply
+ /// @return Response for request, if no error
+ ///
+ QJsonDocument handleReply(QNetworkReply* const &reply );
+
+ /// QNetworkAccessManager for sending requests.
+ QNetworkAccessManager* _networkmanager;
+
+ //Philips Hue Bridge details
+ QString _deviceModel;
+ QString _deviceFirmwareVersion;
+ QString _deviceAPIVersion;
+
+ uint _api_major;
+ uint _api_minor;
+ uint _api_patch;
+
+ bool _isHueEntertainmentReady;
+
+ QMap _lightsMap;
+};
+
+
+
/**
* Implementation for the Philips Hue system.
*
@@ -206,7 +294,7 @@ public:
*
* @author ntim (github), bimsarck (github)
*/
-class LedDevicePhilipsHue: public LedDevice
+class LedDevicePhilipsHue: public LedDevicePhilipsHueBridge
{
Q_OBJECT
@@ -227,20 +315,66 @@ public:
/// constructs leddevice
static LedDevice* construct(const QJsonObject &deviceConfig);
-public slots:
- /// thread start
- virtual void start() override;
+ ///
+ /// Sets configuration
+ ///
+ /// @param deviceConfig the json device config
+ /// @return true if success
+ virtual bool init(const QJsonObject &deviceConfig) override;
+
+ /// Switch the device on
+ virtual int switchOn() override;
+
+ /// Switch the device off
+ virtual int switchOff() override;
-private slots:
/// creates new PhilipsHueLight(s) based on user lightid with bridge feedback
///
/// @param map Map of lightid/value pairs of bridge
///
void newLights(QMap map);
- void stateChanged(bool newState);
+ unsigned int getLightsCount() const { return _lightsCount; }
+ void setLightsCount( unsigned int lightsCount);
+
+ void setOnOffState(PhilipsHueLight& light, bool on);
+ void setTransitionTime(PhilipsHueLight& light, unsigned int transitionTime);
+ void setColor(PhilipsHueLight& light, const CiColor& color, double brightnessFactor);
+ void setState(PhilipsHueLight& light, bool on, const CiColor& color, double brightnessFactor, unsigned int transitionTime);
+
+ void restoreOriginalState();
+
+public slots:
+
+ ///
+ /// Closes the output device.
+ /// Includes switching-off the device and stopping refreshes
+ ///
+ virtual void close() override;
+
+private slots:
+ /// creates new PhilipsHueLight(s) based on user lightid with bridge feedback
+ ///
+ /// @param map Map of lightid/value pairs of bridge
+ ///
+ void updateLights(QMap map);
protected:
+
+ ///
+ /// Opens and initiatialises the output device
+ ///
+ /// @return Zero on succes (i.e. device is ready and enabled) else negative
+ ///
+ virtual int open() override;
+
+ ///
+ /// Get Philips Hue device details and configuration
+ ///
+ /// @return True, if Nanoleaf device capabilities fit configuration
+ ///
+ bool initLeds();
+
///
/// Writes the RGB-Color values to the leds.
///
@@ -249,21 +383,27 @@ protected:
/// @return Zero on success else negative
///
virtual int write(const std::vector & ledValues) override;
- bool init(const QJsonObject &deviceConfig) override;
private:
- /// bridge class
- PhilipsHueBridge* _bridge;
+
+ int writeSingleLights(const std::vector& ledValues);
///
- bool switchOffOnBlack;
+ bool _switchOffOnBlack;
/// The brightness factor to multiply on color change.
- float brightnessFactor;
- /// Transition time in multiples of 100 ms.
+ double _brightnessFactor;
+ /// Transition time in multiples of 100 ms.
/// The default of the Hue lights is 400 ms, but we may want it snapier.
- int transitionTime;
+ unsigned int _transitionTime;
+
+ bool _isRestoreOrigState;
+
/// Array of the light ids.
- std::vector lightIds;
+ std::vector _lightIds;
/// Array to save the lamps.
- std::vector lights;
+ std::vector _lights;
+
+ unsigned int _lightsCount;
+
+
};
diff --git a/libsrc/leddevice/dev_spi/ProviderSpi.cpp b/libsrc/leddevice/dev_spi/ProviderSpi.cpp
index 4787484a..588c8ddd 100644
--- a/libsrc/leddevice/dev_spi/ProviderSpi.cpp
+++ b/libsrc/leddevice/dev_spi/ProviderSpi.cpp
@@ -93,7 +93,7 @@ int ProviderSpi::open()
}
if ( retval < 0 )
{
- errortext = QString ("Failed to open device (%1). Error Code: %2").arg(_deviceName, retval);
+ errortext = QString ("Failed to open device (%1). Error Code: %2").arg(_deviceName).arg(retval);
}
}
diff --git a/libsrc/leddevice/schemas/schema-philipshue.json b/libsrc/leddevice/schemas/schema-philipshue.json
index 9ebe806d..d90a85d6 100644
--- a/libsrc/leddevice/schemas/schema-philipshue.json
+++ b/libsrc/leddevice/schemas/schema-philipshue.json
@@ -35,6 +35,13 @@
"maximum" : 10.0,
"propertyOrder" : 5
},
+ "restoreOriginalState": {
+ "type": "boolean",
+ "title":"edt_dev_spec_restoreOriginalState_title",
+ "default" : true,
+ "propertyOrder" : 6
+ },
+
"lightIds": {
"type": "array",
"title":"edt_dev_spec_lightid_title",
@@ -45,7 +52,7 @@
"minimum" : 0,
"title" : "edt_dev_spec_lightid_itemtitle"
},
- "propertyOrder" : 6
+ "propertyOrder" : 7
}
},
"additionalProperties": true