10
10
#include < QHttpRequestHeader>
11
11
#include < QEventLoop>
12
12
13
+ #include < set>
14
+
13
15
LedDevicePhilipsHue::LedDevicePhilipsHue (const std::string& output) :
14
16
host(output.c_str()), username(" newdeveloper" ) {
15
17
http = new QHttp (host);
@@ -24,18 +26,21 @@ LedDevicePhilipsHue::~LedDevicePhilipsHue() {
24
26
25
27
int LedDevicePhilipsHue::write (const std::vector<ColorRgb> & ledValues) {
26
28
// Save light states if not done before.
27
- if (!statesSaved ()) {
28
- saveStates (ledValues.size ());
29
- switchOn (ledValues.size ());
29
+ if (!areStatesSaved ()) {
30
+ saveStates (( unsigned int ) ledValues.size ());
31
+ switchOn (( unsigned int ) ledValues.size ());
30
32
}
31
33
// Iterate through colors and set light states.
32
34
unsigned int lightId = 1 ;
33
35
for (const ColorRgb& color : ledValues) {
34
- float x, y, b;
36
+ // Find triangle.
37
+ CGTriangle triangle = triangles.at (lightId - 1 );
35
38
// Scale colors from [0, 255] to [0, 1] and convert to xy space.
36
- rgbToXYBrightness (color.red / 255 .0f , color.green / 255 .0f , color.blue / 255 .0f , x, y, b);
39
+ CGPoint xy;
40
+ float b;
41
+ rgbToXYBrightness (color.red / 255 .0f , color.green / 255 .0f , color.blue / 255 .0f , triangle, xy, b);
37
42
// Send adjust color command in JSON format.
38
- put (getStateRoute (lightId), QString (" {\" xy\" : [%1, %2]}" ).arg (x).arg (y));
43
+ put (getStateRoute (lightId), QString (" {\" xy\" : [%1, %2]}" ).arg (xy. x ).arg (xy. y ));
39
44
// Send brightness color command in JSON format.
40
45
put (getStateRoute (lightId), QString (" {\" bri\" : %1}" ).arg (qRound (b * 255 .0f )));
41
46
// Next light id.
@@ -48,7 +53,7 @@ int LedDevicePhilipsHue::write(const std::vector<ColorRgb> & ledValues) {
48
53
int LedDevicePhilipsHue::switchOff () {
49
54
timer.stop ();
50
55
// If light states have been saved before, ...
51
- if (statesSaved ()) {
56
+ if (areStatesSaved ()) {
52
57
// ... restore them.
53
58
restoreStates ();
54
59
}
@@ -93,6 +98,27 @@ QString LedDevicePhilipsHue::getRoute(unsigned int lightId) {
93
98
return QString (" lights/%1" ).arg (lightId);
94
99
}
95
100
101
+ CGTriangle LedDevicePhilipsHue::getTriangle (QString modelId) {
102
+ const std::set<QString> HUE_BULBS_MODEL_IDS = { " LCT001" , " LCT002" , " LCT003" };
103
+ const std::set<QString> LIVING_COLORS_MODEL_IDS = { " LLC001" , " LLC005" , " LLC006" , " LLC007" , " LLC011" , " LLC012" ,
104
+ " LLC013" , " LST001" };
105
+ CGTriangle triangle;
106
+ if (HUE_BULBS_MODEL_IDS.find (modelId) != HUE_BULBS_MODEL_IDS.end ()) {
107
+ triangle.red = {0 .675f , 0 .322f };
108
+ triangle.green = {0 .4091f , 0 .518f };
109
+ triangle.blue = {0 .167f , 0 .04f };
110
+ } else if (LIVING_COLORS_MODEL_IDS.find (modelId) != LIVING_COLORS_MODEL_IDS.end ()) {
111
+ triangle.red = {0 .703f , 0 .296f };
112
+ triangle.green = {0 .214f , 0 .709f };
113
+ triangle.blue = {0 .139f , 0 .081f };
114
+ } else {
115
+ triangle.red = {1 .0f , 0 .0f };
116
+ triangle.green = {0 .0f , 1 .0f };
117
+ triangle.blue = {0 .0f , 0 .0f };
118
+ }
119
+ return triangle;
120
+ }
121
+
96
122
void LedDevicePhilipsHue::saveStates (unsigned int nLights) {
97
123
// Clear saved light states.
98
124
states.clear ();
@@ -116,8 +142,12 @@ void LedDevicePhilipsHue::saveStates(unsigned int nLights) {
116
142
state[" xy" ] = json[" state" ][" xy" ];
117
143
state[" bri" ] = json[" state" ][" bri" ];
118
144
}
145
+ // Save id.
146
+ ids.push_back (QString (writer.write (json[" modelid" ]).c_str ()).trimmed ().replace (" \" " , " " ));
119
147
// Save state object.
120
148
states.push_back (QString (writer.write (state).c_str ()).trimmed ());
149
+ // Determine triangle.
150
+ triangles.push_back (getTriangle (ids.back ()));
121
151
}
122
152
}
123
153
@@ -137,27 +167,94 @@ void LedDevicePhilipsHue::restoreStates() {
137
167
states.clear ();
138
168
}
139
169
140
- bool LedDevicePhilipsHue::statesSaved () {
170
+ bool LedDevicePhilipsHue::areStatesSaved () {
141
171
return !states.empty ();
142
172
}
143
173
144
- void LedDevicePhilipsHue::rgbToXYBrightness (float red, float green, float blue, float & x, float & y, float & brightness) {
174
+ float LedDevicePhilipsHue::crossProduct (CGPoint p1, CGPoint p2) {
175
+ return p1.x * p2.y - p1.y * p2.x ;
176
+ }
177
+
178
+ bool LedDevicePhilipsHue::isPointInLampsReach (CGTriangle triangle, CGPoint p) {
179
+ CGPoint v1 = { triangle.green .x - triangle.red .x , triangle.green .y - triangle.red .y };
180
+ CGPoint v2 = { triangle.blue .x - triangle.red .x , triangle.blue .y - triangle.red .y };
181
+ CGPoint q = { p.x - triangle.red .x , p.y - triangle.red .y };
182
+ float s = crossProduct (q, v2) / crossProduct (v1, v2);
183
+ float t = crossProduct (v1, q) / crossProduct (v1, v2);
184
+ if ((s >= 0 .0f ) && (t >= 0 .0f ) && (s + t <= 1 .0f )) {
185
+ return true ;
186
+ } else {
187
+ return false ;
188
+ }
189
+ }
190
+
191
+ CGPoint LedDevicePhilipsHue::getClosestPointToPoint (CGPoint A, CGPoint B, CGPoint P) {
192
+ CGPoint AP = { P.x - A.x , P.y - A.y };
193
+ CGPoint AB = { B.x - A.x , B.y - A.y };
194
+ float ab2 = AB.x * AB.x + AB.y * AB.y ;
195
+ float ap_ab = AP.x * AB.x + AP.y * AB.y ;
196
+ float t = ap_ab / ab2;
197
+ if (t < 0 .0f ) {
198
+ t = 0 .0f ;
199
+ } else if (t > 1 .0f ) {
200
+ t = 1 .0f ;
201
+ }
202
+ return {A.x + AB.x * t, A.y + AB.y * t};
203
+ }
204
+
205
+ float LedDevicePhilipsHue::getDistanceBetweenTwoPoints (CGPoint one, CGPoint two) {
206
+ // Horizontal difference.
207
+ float dx = one.x - two.x ;
208
+ // Vertical difference.
209
+ float dy = one.y - two.y ;
210
+ float dist = sqrt (dx * dx + dy * dy);
211
+ return dist;
212
+ }
213
+
214
+ void LedDevicePhilipsHue::rgbToXYBrightness (float red, float green, float blue, CGTriangle triangle, CGPoint& xyPoint,
215
+ float & brightness) {
145
216
// Apply gamma correction.
146
- red = (red > 0 .04045f ) ? qPow ((red + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (red / 12 .92f );
147
- green = (green > 0 .04045f ) ? qPow ((green + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (green / 12 .92f );
148
- blue = (blue > 0 .04045f ) ? qPow ((blue + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (blue / 12 .92f );
217
+ float r = (red > 0 .04045f ) ? powf ((red + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (red / 12 .92f );
218
+ float g = (green > 0 .04045f ) ? powf ((green + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (green / 12 .92f );
219
+ float b = (blue > 0 .04045f ) ? powf ((blue + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (blue / 12 .92f );
149
220
// Convert to XYZ space.
150
- float X = red * 0 .649926f + green * 0 .103455f + blue * 0 .197109f ;
151
- float Y = red * 0 .234327f + green * 0 .743075f + blue * 0 .022598f ;
152
- float Z = red * 0 .0000000f + green * 0 .053077f + blue * 1 .035763f ;
221
+ float X = r * 0 .649926f + g * 0 .103455f + b * 0 .197109f ;
222
+ float Y = r * 0 .234327f + g * 0 .743075f + b * 0 .022598f ;
223
+ float Z = r * 0 .0000000f + g * 0 .053077f + b * 1 .035763f ;
153
224
// Convert to x,y space.
154
- x = X / (X + Y + Z);
155
- y = Y / (X + Y + Z);
156
- if (isnan (x)) {
157
- x = 0 .0f ;
225
+ float cx = X / (X + Y + Z + 0 .0000001f );
226
+ float cy = Y / (X + Y + Z + 0 .0000001f );
227
+ if (isnan (cx)) {
228
+ cx = 0 .0f ;
229
+ }
230
+ if (isnan (cy)) {
231
+ cy = 0 .0f ;
158
232
}
159
- if (isnan (y)) {
160
- y = 0 .0f ;
233
+ xyPoint.x = cx;
234
+ xyPoint.y = cy;
235
+ // Check if the given XY value is within the colourreach of our lamps.
236
+ if (!isPointInLampsReach (triangle, xyPoint)) {
237
+ // 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.
238
+ CGPoint pAB = getClosestPointToPoint (triangle.red , triangle.green , xyPoint);
239
+ CGPoint pAC = getClosestPointToPoint (triangle.blue , triangle.red , xyPoint);
240
+ CGPoint pBC = getClosestPointToPoint (triangle.green , triangle.blue , xyPoint);
241
+ // Get the distances per point and see which point is closer to our Point.
242
+ float dAB = getDistanceBetweenTwoPoints (xyPoint, pAB);
243
+ float dAC = getDistanceBetweenTwoPoints (xyPoint, pAC);
244
+ float dBC = getDistanceBetweenTwoPoints (xyPoint, pBC);
245
+ float lowest = dAB;
246
+ CGPoint closestPoint = pAB;
247
+ if (dAC < lowest) {
248
+ lowest = dAC;
249
+ closestPoint = pAC;
250
+ }
251
+ if (dBC < lowest) {
252
+ lowest = dBC;
253
+ closestPoint = pBC;
254
+ }
255
+ // Change the xy value to a value which is within the reach of the lamp.
256
+ xyPoint.x = closestPoint.x ;
257
+ xyPoint.y = closestPoint.y ;
161
258
}
162
259
// Brightness is simply Y in the XYZ space.
163
260
brightness = Y;
0 commit comments