mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	Improved philip hue device:
add lamp types autodetection add full xy-Colorspace implementations reduce http requests to the hue bridge. This prevents DDOS -> 503 add color black -> lamps off save state is temporary disabled Former-commit-id: 5a0328fe80a06a9f670c5190190e239857cbd15c
This commit is contained in:
		| @@ -1,3 +1,4 @@ | ||||
| #include <iostream> | ||||
| // Local-Hyperion includes | ||||
| #include "LedDevicePhilipsHue.h" | ||||
|  | ||||
| @@ -10,43 +11,91 @@ | ||||
| #include <QHttpRequestHeader> | ||||
| #include <QEventLoop> | ||||
|  | ||||
| LedDevicePhilipsHue::LedDevicePhilipsHue(const std::string& output) : | ||||
| 		host(output.c_str()), username("newdeveloper") { | ||||
| LedDevicePhilipsHue::LedDevicePhilipsHue(const std::string &output) : | ||||
| 	host(output.c_str()), username("newdeveloper") { | ||||
| 	http = new QHttp(host); | ||||
| 	timer.setInterval(3000); | ||||
| /*	timer.setInterval(3000); | ||||
| 	timer.setSingleShot(true); | ||||
| 	connect(&timer, SIGNAL(timeout()), this, SLOT(restoreStates())); | ||||
| 	connect(&timer, SIGNAL(timeout()), this, SLOT(restoreStates()));*/ | ||||
| } | ||||
|  | ||||
| LedDevicePhilipsHue::~LedDevicePhilipsHue() { | ||||
| 	delete http; | ||||
| } | ||||
|  | ||||
| int LedDevicePhilipsHue::write(const std::vector<ColorRgb> & ledValues) { | ||||
| int LedDevicePhilipsHue::write(const std::vector<ColorRgb> &ledValues) { | ||||
| 	// Save light states if not done before. | ||||
| 	if (!statesSaved()) { | ||||
| 	if (!statesSaved()) | ||||
| 		saveStates(ledValues.size()); | ||||
| 		switchOn(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. | ||||
| 	unsigned int lightId = 0; | ||||
| 	for (const ColorRgb &color : ledValues) { | ||||
| 		lightId++; | ||||
| 		// Send only request to the brigde if color changed (prevents DDOS --> 503) | ||||
| 		if (!oldLedValues.empty()) | ||||
| 			if(!hasColorChanged(lightId, &color)) | ||||
| 				continue; | ||||
|  | ||||
| 		float r = color.red / 255.0f; | ||||
| 		float g = color.green / 255.0f; | ||||
| 		float b = color.blue / 255.0f; | ||||
|  | ||||
| 		//set color gamut triangle | ||||
| 		if(std::find(hueBulbs.begin(), hueBulbs.end(), modelIds[(lightId - 1)]) != hueBulbs.end()) { | ||||
| 			Red = {0.675f, 0.322f}; | ||||
| 			Green = {0.4091f, 0.518f}; | ||||
| 			Blue = {0.167f, 0.04f}; | ||||
| 		} else if (std::find(livingColors.begin(), | ||||
| 				 livingColors.end(), modelIds[(lightId - 1)]) != livingColors.end()) { | ||||
| 			Red = {0.703f, 0.296f}; | ||||
| 			Green = {0.214f, 0.709f}; | ||||
| 			Blue = {0.139f, 0.081f}; | ||||
| 		} else { | ||||
| 			Red = {1.0f, 0.0f}; | ||||
| 			Green = {0.0f, 1.0f}; | ||||
| 			Blue = {0.0f, 0.0f}; | ||||
| 		} | ||||
| 		// if color equal black, switch off lamp ... | ||||
| 		if (r == 0.0f && g == 0.0f && b == 0.0f) { | ||||
| 			switchLampOff(lightId); | ||||
| 			continue; | ||||
| 		} | ||||
| 		// ... and if lamp off, switch on | ||||
| 		if (!checkOnStatus(states[(lightId - 1)])) | ||||
| 			switchLampOn(lightId); | ||||
|  | ||||
| 		float bri; | ||||
| 		CGPoint p = CGPointMake(0, 0); | ||||
| 		// Scale colors from [0, 255] to [0, 1] and convert to xy space. | ||||
| 		rgbToXYBrightness(r, g, b, &p, bri); | ||||
| 		// Send adjust color and brightness command in JSON format. | ||||
| 		put(getStateRoute(lightId), | ||||
| 			 QString("{\"xy\": [%1, %2], \"bri\": %3}").arg(p.x).arg(p.y).arg(qRound(b * 255.0f))); | ||||
| 	} | ||||
| 	timer.start(); | ||||
| 	oldLedValues = ledValues; | ||||
| 	//timer.start(); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| bool LedDevicePhilipsHue::hasColorChanged(unsigned int lightId, const ColorRgb *color) { | ||||
| 	bool matchFound = true; | ||||
| 	const ColorRgb &tmpOldColor = oldLedValues[(lightId - 1)]; | ||||
| 	if ((*color).red == tmpOldColor.red) | ||||
| 		matchFound = false; | ||||
| 	if (!matchFound && (*color).green == tmpOldColor.green) | ||||
| 		matchFound = false; | ||||
| 	else | ||||
| 		matchFound = true; | ||||
| 	if (!matchFound && (*color).blue == tmpOldColor.blue) | ||||
| 		matchFound = false; | ||||
| 	else | ||||
| 		matchFound = true; | ||||
|  | ||||
| 	return matchFound; | ||||
| } | ||||
|  | ||||
| int LedDevicePhilipsHue::switchOff() { | ||||
| 	timer.stop(); | ||||
| 	//timer.stop(); | ||||
| 	// If light states have been saved before, ... | ||||
| 	if (statesSaved()) { | ||||
| 		// ... restore them. | ||||
| @@ -55,6 +104,10 @@ int LedDevicePhilipsHue::switchOff() { | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| bool LedDevicePhilipsHue::checkOnStatus(QString status) { | ||||
| 	return status.contains("\"on\":true"); | ||||
| } | ||||
|  | ||||
| void LedDevicePhilipsHue::put(QString route, QString content) { | ||||
| 	QString url = QString("/api/%1/%2").arg(username).arg(route); | ||||
| 	QHttpRequestHeader header("PUT", url); | ||||
| @@ -69,6 +122,7 @@ void LedDevicePhilipsHue::put(QString route, QString content) { | ||||
| 	http->request(header, content.toAscii()); | ||||
| 	// Go into the loop until the request is finished. | ||||
| 	loop.exec(); | ||||
| 	//std::cout << http->readAll().data() << std::endl; | ||||
| } | ||||
|  | ||||
| QByteArray LedDevicePhilipsHue::get(QString route) { | ||||
| @@ -96,6 +150,7 @@ QString LedDevicePhilipsHue::getRoute(unsigned int lightId) { | ||||
| void LedDevicePhilipsHue::saveStates(unsigned int nLights) { | ||||
| 	// Clear saved light states. | ||||
| 	states.clear(); | ||||
| 	modelIds.clear(); | ||||
| 	// Use json parser to parse reponse. | ||||
| 	Json::Reader reader; | ||||
| 	Json::FastWriter writer; | ||||
| @@ -117,14 +172,19 @@ void LedDevicePhilipsHue::saveStates(unsigned int nLights) { | ||||
| 			state["bri"] = json["state"]["bri"]; | ||||
| 		} | ||||
| 		// Save state object. | ||||
| 		modelIds.push_back(QString(writer.write(json["modelid"]).c_str()).trimmed().replace("\"", "")); | ||||
| 		states.push_back(QString(writer.write(state).c_str()).trimmed()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void LedDevicePhilipsHue::switchOn(unsigned int nLights) { | ||||
| 	for (unsigned int i = 0; i < nLights; i++) { | ||||
| 		put(getStateRoute(i + 1), "{\"on\": true}"); | ||||
| 	} | ||||
| void LedDevicePhilipsHue::switchLampOn(unsigned int lightId) { | ||||
| 	put(getStateRoute(lightId), "{\"on\": true}"); | ||||
| 	states[(lightId - 1)].replace("\"on\":false", "\"on\":true"); | ||||
| } | ||||
|  | ||||
| void LedDevicePhilipsHue::switchLampOff(unsigned int lightId) { | ||||
| 	put(getStateRoute(lightId), "{\"on\": false}"); | ||||
| 	states[(lightId - 1)].replace("\"on\":true", "\"on\":false"); | ||||
| } | ||||
|  | ||||
| void LedDevicePhilipsHue::restoreStates() { | ||||
| @@ -135,30 +195,119 @@ void LedDevicePhilipsHue::restoreStates() { | ||||
| 	} | ||||
| 	// Clear saved light states. | ||||
| 	states.clear(); | ||||
| 	modelIds.clear(); | ||||
| 	oldLedValues.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; | ||||
| CGPoint LedDevicePhilipsHue::CGPointMake(float x, float y) { | ||||
| 	CGPoint p; | ||||
| 	p.x = x; | ||||
| 	p.y = y; | ||||
|  | ||||
| 	return p; | ||||
| } | ||||
|  | ||||
| float LedDevicePhilipsHue::CrossProduct(CGPoint p1, CGPoint p2) { | ||||
| 	return (p1.x * p2.y - p1.y * p2.x); | ||||
| } | ||||
|  | ||||
| bool LedDevicePhilipsHue::CheckPointInLampsReach(CGPoint p) { | ||||
| 	CGPoint v1 = CGPointMake(Green.x - Red.x, Green.y - Red.y); | ||||
| 	CGPoint v2 = CGPointMake(Blue.x - Red.x, Blue.y - Red.y); | ||||
|  | ||||
| 	CGPoint q = CGPointMake(p.x - Red.x, p.y - 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; | ||||
| 	else | ||||
| 		return false; | ||||
| } | ||||
|  | ||||
| CGPoint LedDevicePhilipsHue::GetClosestPointToPoint(CGPoint A, CGPoint B, CGPoint P) { | ||||
| 	CGPoint AP = CGPointMake(P.x - A.x, P.y - A.y); | ||||
| 	CGPoint AB = CGPointMake(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 CGPointMake(A.x + AB.x * t, A.y + AB.y * t); | ||||
| } | ||||
|  | ||||
| float LedDevicePhilipsHue::GetDistanceBetweenTwoPoints(CGPoint one, CGPoint two) { | ||||
| 	float dx = one.x - two.x; // horizontal difference | ||||
| 	float dy = one.y - two.y; // vertical difference | ||||
| 	float dist = sqrt(dx * dx + dy * dy); | ||||
|  | ||||
| 	return dist; | ||||
| } | ||||
|  | ||||
| void LedDevicePhilipsHue::rgbToXYBrightness(float red, float green, float blue, CGPoint *xyPoint, float &brightness) { | ||||
| 	//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; | ||||
| 	//Convert to x,y space. | ||||
| 	float cx = X / (X + Y + Z + 0.0000001f); | ||||
| 	float cy = Y / (X + Y + Z + 0.0000001f); | ||||
|  | ||||
| 	if (isnan(cx)) | ||||
| 		cx = 0.0f; | ||||
| 	if (isnan(cy)) | ||||
| 		cy = 0.0f; | ||||
|  | ||||
| 	(*xyPoint).x = cx; | ||||
| 	(*xyPoint).y = cy; | ||||
|  | ||||
| 	//Check if the given XY value is within the colourreach of our lamps. | ||||
| 	bool inReachOfLamps = CheckPointInLampsReach(*xyPoint); | ||||
|  | ||||
| 	if (!inReachOfLamps) { | ||||
| 		//It seems the colour is out of reach | ||||
| 		//let's find the closes colour we can produce with our lamp and send this XY value out. | ||||
|  | ||||
| 		//Find the closest point on each line in the triangle. | ||||
| 		CGPoint pAB = GetClosestPointToPoint(Red, Green, *xyPoint); | ||||
| 		CGPoint pAC = GetClosestPointToPoint(Blue, Red, *xyPoint); | ||||
| 		CGPoint pBC = GetClosestPointToPoint(Green, Blue, *xyPoint); | ||||
|  | ||||
| 		//Get the distances per point and see which point is closer to our Point. | ||||
| 		float dAB = GetDistanceBetweenTwoPoints(*xyPoint, pAB); | ||||
| 		float dAC = GetDistanceBetweenTwoPoints(*xyPoint, pAC); | ||||
| 		float dBC = GetDistanceBetweenTwoPoints(*xyPoint, pBC); | ||||
|  | ||||
| 		float lowest = dAB; | ||||
| 		CGPoint 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. | ||||
| 		(*xyPoint).x = closestPoint.x; | ||||
| 		(*xyPoint).y = closestPoint.y; | ||||
| 	} | ||||
|  | ||||
| 	// Brightness is simply Y in the XYZ space. | ||||
| 	brightness = Y; | ||||
| } | ||||
|   | ||||
| @@ -22,6 +22,11 @@ | ||||
|  * | ||||
|  * @author ntim (github) | ||||
|  */ | ||||
| struct CGPoint; | ||||
| struct CGPoint { | ||||
| 	float x; | ||||
| 	float y; | ||||
| }; | ||||
| class LedDevicePhilipsHue: public QObject, public LedDevice { | ||||
| Q_OBJECT | ||||
| public: | ||||
| @@ -44,7 +49,7 @@ public: | ||||
| 	/// | ||||
| 	/// @return Zero on success else negative | ||||
| 	/// | ||||
| 	virtual int write(const std::vector<ColorRgb> & ledValues); | ||||
| 	virtual int write(const std::vector<ColorRgb> &ledValues); | ||||
|  | ||||
| 	/// Restores the original state of the leds. | ||||
| 	virtual int switchOff(); | ||||
| @@ -54,6 +59,19 @@ private slots: | ||||
| 	void restoreStates(); | ||||
|  | ||||
| private: | ||||
| 	// ModelIds | ||||
| 	const std::vector<QString> hueBulbs = {"LCT001", "LCT002", "LCT003"}; | ||||
| 	const std::vector<QString> livingColors = {"LLC001", "LLC005", "LLC006", "LLC007", | ||||
| 			 "LLC011", "LLC012", "LLC013", "LST001"}; | ||||
| 	/// LivingColors color gamut triangle | ||||
| 	CGPoint Red , Green, Blue; | ||||
|  | ||||
| 	CGPoint CGPointMake(float x, float y); | ||||
| 	float CrossProduct(CGPoint p1, CGPoint p2); | ||||
| 	bool CheckPointInLampsReach(CGPoint p); | ||||
| 	CGPoint GetClosestPointToPoint(CGPoint A, CGPoint B, CGPoint P); | ||||
| 	float GetDistanceBetweenTwoPoints(CGPoint one, CGPoint two); | ||||
|  | ||||
| 	/// Array to save the light states. | ||||
| 	std::vector<QString> states; | ||||
| 	/// Ip address of the bridge | ||||
| @@ -65,6 +83,13 @@ private: | ||||
| 	/// Use timer to reset lights when we got into "GRABBINGMODE_OFF". | ||||
| 	QTimer timer; | ||||
|  | ||||
| 	std::vector<ColorRgb> oldLedValues; | ||||
| 	std::vector<QString> modelIds; | ||||
|  | ||||
| 	bool hasColorChanged(unsigned int lightId, const ColorRgb *color); | ||||
|  | ||||
| 	bool checkOnStatus(QString status); | ||||
|  | ||||
| 	/// | ||||
| 	/// Sends a HTTP GET request (blocking). | ||||
| 	/// | ||||
| @@ -109,7 +134,9 @@ private: | ||||
| 	/// | ||||
| 	/// @param nLights the number of lights | ||||
| 	/// | ||||
| 	void switchOn(unsigned int nLights); | ||||
| 	void switchLampOn(unsigned int lightId); | ||||
|  | ||||
| 	void switchLampOff(unsigned int lightId); | ||||
|  | ||||
| 	/// | ||||
| 	/// @return true if light states have been saved. | ||||
| @@ -132,6 +159,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, CGPoint *xyPoint, float &brightness); | ||||
|  | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user