12
12
13
13
#include < set>
14
14
15
- const ColorPoint LedDevicePhilipsHue::BLACK = { 0 .0f , 0 .0f , 0 .0f };
15
+ bool operator ==(CiColor p1, CiColor p2) {
16
+ return (p1.x == p2.x ) && (p1.y == p2.y ) && (p1.bri == p2.bri );
17
+ }
18
+
19
+ bool operator !=(CiColor p1, CiColor p2) {
20
+ return !(p1 == p2);
21
+ }
22
+
23
+ PhilipsHueLamp::PhilipsHueLamp (unsigned int id, QString originalState, QString modelId) :
24
+ id(id), originalState(originalState) {
25
+ // Hue system model ids.
26
+ const std::set<QString> HUE_BULBS_MODEL_IDS = { " LCT001" , " LCT002" , " LCT003" };
27
+ const std::set<QString> LIVING_COLORS_MODEL_IDS = { " LLC001" , " LLC005" , " LLC006" , " LLC007" , " LLC011" , " LLC012" ,
28
+ " LLC013" , " LST001" };
29
+ // Find id in the sets and set the appropiate color space.
30
+ if (HUE_BULBS_MODEL_IDS.find (modelId) != HUE_BULBS_MODEL_IDS.end ()) {
31
+ colorSpace.red = {0 .675f , 0 .322f };
32
+ colorSpace.green = {0 .4091f , 0 .518f };
33
+ colorSpace.blue = {0 .167f , 0 .04f };
34
+ } else if (LIVING_COLORS_MODEL_IDS.find (modelId) != LIVING_COLORS_MODEL_IDS.end ()) {
35
+ colorSpace.red = {0 .703f , 0 .296f };
36
+ colorSpace.green = {0 .214f , 0 .709f };
37
+ colorSpace.blue = {0 .139f , 0 .081f };
38
+ } else {
39
+ colorSpace.red = {1 .0f , 0 .0f };
40
+ colorSpace.green = {0 .0f , 1 .0f };
41
+ colorSpace.blue = {0 .0f , 0 .0f };
42
+ }
43
+ // Initialize black color.
44
+ black = rgbToCiColor (0 .0f , 0 .0f , 0 .0f );
45
+ // Initialize color with black
46
+ color = {black.x , black.y , black.bri };
47
+ }
48
+
49
+ float PhilipsHueLamp::crossProduct (CiColor p1, CiColor p2) {
50
+ return p1.x * p2.y - p1.y * p2.x ;
51
+ }
52
+
53
+ bool PhilipsHueLamp::isPointInLampsReach (CiColor p) {
54
+ CiColor v1 = { colorSpace.green .x - colorSpace.red .x , colorSpace.green .y - colorSpace.red .y };
55
+ CiColor v2 = { colorSpace.blue .x - colorSpace.red .x , colorSpace.blue .y - colorSpace.red .y };
56
+ CiColor q = { p.x - colorSpace.red .x , p.y - colorSpace.red .y };
57
+ float s = crossProduct (q, v2) / crossProduct (v1, v2);
58
+ float t = crossProduct (v1, q) / crossProduct (v1, v2);
59
+ if ((s >= 0 .0f ) && (t >= 0 .0f ) && (s + t <= 1 .0f )) {
60
+ return true ;
61
+ }
62
+ return false ;
63
+ }
64
+
65
+ CiColor PhilipsHueLamp::getClosestPointToPoint (CiColor a, CiColor b, CiColor p) {
66
+ CiColor AP = { p.x - a.x , p.y - a.y };
67
+ CiColor AB = { b.x - a.x , b.y - a.y };
68
+ float ab2 = AB.x * AB.x + AB.y * AB.y ;
69
+ float ap_ab = AP.x * AB.x + AP.y * AB.y ;
70
+ float t = ap_ab / ab2;
71
+ if (t < 0 .0f ) {
72
+ t = 0 .0f ;
73
+ } else if (t > 1 .0f ) {
74
+ t = 1 .0f ;
75
+ }
76
+ return {a.x + AB.x * t, a.y + AB.y * t};
77
+ }
78
+
79
+ float PhilipsHueLamp::getDistanceBetweenTwoPoints (CiColor p1, CiColor p2) {
80
+ // Horizontal difference.
81
+ float dx = p1.x - p2.x ;
82
+ // Vertical difference.
83
+ float dy = p1.y - p2.y ;
84
+ // Absolute value.
85
+ return sqrt (dx * dx + dy * dy);
86
+ }
87
+
88
+ CiColor PhilipsHueLamp::rgbToCiColor (float red, float green, float blue) {
89
+ // Apply gamma correction.
90
+ float r = (red > 0 .04045f ) ? powf ((red + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (red / 12 .92f );
91
+ float g = (green > 0 .04045f ) ? powf ((green + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (green / 12 .92f );
92
+ float b = (blue > 0 .04045f ) ? powf ((blue + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (blue / 12 .92f );
93
+ // Convert to XYZ space.
94
+ float X = r * 0 .649926f + g * 0 .103455f + b * 0 .197109f ;
95
+ float Y = r * 0 .234327f + g * 0 .743075f + b * 0 .022598f ;
96
+ float Z = r * 0 .0000000f + g * 0 .053077f + b * 1 .035763f ;
97
+ // Convert to x,y space.
98
+ float cx = X / (X + Y + Z);
99
+ float cy = Y / (X + Y + Z);
100
+ if (isnan (cx)) {
101
+ cx = 0 .0f ;
102
+ }
103
+ if (isnan (cy)) {
104
+ cy = 0 .0f ;
105
+ }
106
+ // Brightness is simply Y in the XYZ space.
107
+ CiColor xy = { cx, cy, Y };
108
+ // Check if the given XY value is within the color reach of our lamps.
109
+ if (!isPointInLampsReach (xy)) {
110
+ // 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.
111
+ CiColor pAB = getClosestPointToPoint (colorSpace.red , colorSpace.green , xy);
112
+ CiColor pAC = getClosestPointToPoint (colorSpace.blue , colorSpace.red , xy);
113
+ CiColor pBC = getClosestPointToPoint (colorSpace.green , colorSpace.blue , xy);
114
+ // Get the distances per point and see which point is closer to our Point.
115
+ float dAB = getDistanceBetweenTwoPoints (xy, pAB);
116
+ float dAC = getDistanceBetweenTwoPoints (xy, pAC);
117
+ float dBC = getDistanceBetweenTwoPoints (xy, pBC);
118
+ float lowest = dAB;
119
+ CiColor closestPoint = pAB;
120
+ if (dAC < lowest) {
121
+ lowest = dAC;
122
+ closestPoint = pAC;
123
+ }
124
+ if (dBC < lowest) {
125
+ lowest = dBC;
126
+ closestPoint = pBC;
127
+ }
128
+ // Change the xy value to a value which is within the reach of the lamp.
129
+ xy.x = closestPoint.x ;
130
+ xy.y = closestPoint.y ;
131
+ }
132
+ return xy;
133
+ }
16
134
17
135
LedDevicePhilipsHue::LedDevicePhilipsHue (const std::string& output, bool switchOffOnBlack) :
18
136
host(output.c_str()), username(" newdeveloper" ), switchOffOnBlack(switchOffOnBlack) {
@@ -41,25 +159,35 @@ int LedDevicePhilipsHue::write(const std::vector<ColorRgb> & ledValues) {
41
159
unsigned int idx = 0 ;
42
160
for (const ColorRgb& color : ledValues) {
43
161
// Get lamp.
44
- HueLamp & lamp = lamps.at (idx);
162
+ PhilipsHueLamp & lamp = lamps.at (idx);
45
163
// Scale colors from [0, 255] to [0, 1] and convert to xy space.
46
- ColorPoint xy = rgbToXYBrightness (color.red / 255 .0f , color.green / 255 .0f , color.blue / 255 .0f , lamp);
47
- // Switch lamp off if switchOffOnBlack is enabled and the lamp is currently on.
48
- if (switchOffOnBlack && xy == BLACK && lamp.color != BLACK) {
49
- put (getStateRoute (lamp.id ), QString (" {\" on\" : false}" ));
50
- }
164
+ CiColor xy = lamp.rgbToCiColor (color.red / 255 .0f , color.green / 255 .0f , color.blue / 255 .0f );
51
165
// Write color if color has been changed.
52
- else if (xy != lamp.color ) {
166
+ if (xy != lamp.color ) {
53
167
// Switch on if the lamp has been previously switched off.
54
- if (switchOffOnBlack && lamp.color == BLACK ) {
55
- put ( getStateRoute (lamp. id ), QString ( " { \" on \" : true} " ));
168
+ if (switchOffOnBlack && lamp.color == lamp. black ) {
169
+
56
170
}
57
171
// Send adjust color and brightness command in JSON format.
58
172
put (getStateRoute (lamp.id ),
59
173
QString (" {\" xy\" : [%1, %2], \" bri\" : %3}" ).arg (xy.x ).arg (xy.y ).arg (qRound (xy.bri * 255 .0f )));
60
- // Remember written color.
61
- lamp.color = xy;
174
+
175
+ }
176
+ // Switch lamp off if switchOffOnBlack is enabled and the lamp is currently on.
177
+ if (switchOffOnBlack) {
178
+ // From black to a color.
179
+ if (lamp.color == lamp.black && xy != lamp.black ) {
180
+ put (getStateRoute (lamp.id ), QString (" {\" on\" : true}" ));
181
+ std::cout << " switchon" << std::endl;
182
+ }
183
+ // From a color to black.
184
+ else if (lamp.color != lamp.black && xy == lamp.black ) {
185
+ put (getStateRoute (lamp.id ), QString (" {\" on\" : false}" ));
186
+ std::cout << " switchoff" << std::endl;
187
+ }
62
188
}
189
+ // Remember last color.
190
+ lamp.color = xy;
63
191
// Next light id.
64
192
idx++;
65
193
}
@@ -142,18 +270,18 @@ void LedDevicePhilipsHue::saveStates(unsigned int nLights) {
142
270
QString modelId = QString (writer.write (json[" modelid" ]).c_str ()).trimmed ().replace (" \" " , " " );
143
271
QString originalState = QString (writer.write (state).c_str ()).trimmed ();
144
272
// Save state object.
145
- lamps.push_back (HueLamp (i + 1 , originalState, modelId));
273
+ lamps.push_back (PhilipsHueLamp (i + 1 , originalState, modelId));
146
274
}
147
275
}
148
276
149
277
void LedDevicePhilipsHue::switchOn (unsigned int nLights) {
150
- for (HueLamp lamp : lamps) {
278
+ for (PhilipsHueLamp lamp : lamps) {
151
279
put (getStateRoute (lamp.id ), " {\" on\" : true}" );
152
280
}
153
281
}
154
282
155
283
void LedDevicePhilipsHue::restoreStates () {
156
- for (HueLamp lamp : lamps) {
284
+ for (PhilipsHueLamp lamp : lamps) {
157
285
put (getStateRoute (lamp.id ), lamp.originalState );
158
286
}
159
287
// Clear saved light states.
@@ -163,123 +291,3 @@ void LedDevicePhilipsHue::restoreStates() {
163
291
bool LedDevicePhilipsHue::areStatesSaved () {
164
292
return !lamps.empty ();
165
293
}
166
-
167
- float LedDevicePhilipsHue::crossProduct (ColorPoint p1, ColorPoint p2) {
168
- return p1.x * p2.y - p1.y * p2.x ;
169
- }
170
-
171
- bool LedDevicePhilipsHue::isPointInLampsReach (HueLamp lamp, ColorPoint p) {
172
- ColorTriangle& triangle = lamp.colorSpace ;
173
- ColorPoint v1 = { triangle.green .x - triangle.red .x , triangle.green .y - triangle.red .y };
174
- ColorPoint v2 = { triangle.blue .x - triangle.red .x , triangle.blue .y - triangle.red .y };
175
- ColorPoint q = { p.x - triangle.red .x , p.y - triangle.red .y };
176
- float s = crossProduct (q, v2) / crossProduct (v1, v2);
177
- float t = crossProduct (v1, q) / crossProduct (v1, v2);
178
- if ((s >= 0 .0f ) && (t >= 0 .0f ) && (s + t <= 1 .0f )) {
179
- return true ;
180
- } else {
181
- return false ;
182
- }
183
- }
184
-
185
- ColorPoint LedDevicePhilipsHue::getClosestPointToPoint (ColorPoint a, ColorPoint b, ColorPoint p) {
186
- ColorPoint AP = { p.x - a.x , p.y - a.y };
187
- ColorPoint AB = { b.x - a.x , b.y - a.y };
188
- float ab2 = AB.x * AB.x + AB.y * AB.y ;
189
- float ap_ab = AP.x * AB.x + AP.y * AB.y ;
190
- float t = ap_ab / ab2;
191
- if (t < 0 .0f ) {
192
- t = 0 .0f ;
193
- } else if (t > 1 .0f ) {
194
- t = 1 .0f ;
195
- }
196
- return {a.x + AB.x * t, a.y + AB.y * t};
197
- }
198
-
199
- float LedDevicePhilipsHue::getDistanceBetweenTwoPoints (ColorPoint p1, ColorPoint p2) {
200
- // Horizontal difference.
201
- float dx = p1.x - p2.x ;
202
- // Vertical difference.
203
- float dy = p1.y - p2.y ;
204
- // Absolute value.
205
- return sqrt (dx * dx + dy * dy);
206
- }
207
-
208
- ColorPoint LedDevicePhilipsHue::rgbToXYBrightness (float red, float green, float blue, HueLamp lamp) {
209
- // Apply gamma correction.
210
- float r = (red > 0 .04045f ) ? powf ((red + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (red / 12 .92f );
211
- float g = (green > 0 .04045f ) ? powf ((green + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (green / 12 .92f );
212
- float b = (blue > 0 .04045f ) ? powf ((blue + 0 .055f ) / (1 .0f + 0 .055f ), 2 .4f ) : (blue / 12 .92f );
213
- // Convert to XYZ space.
214
- float X = r * 0 .649926f + g * 0 .103455f + b * 0 .197109f ;
215
- float Y = r * 0 .234327f + g * 0 .743075f + b * 0 .022598f ;
216
- float Z = r * 0 .0000000f + g * 0 .053077f + b * 1 .035763f ;
217
- // Convert to x,y space.
218
- float cx = X / (X + Y + Z);
219
- float cy = Y / (X + Y + Z);
220
- if (isnan (cx)) {
221
- cx = 0 .0f ;
222
- }
223
- if (isnan (cy)) {
224
- cy = 0 .0f ;
225
- }
226
- // Brightness is simply Y in the XYZ space.
227
- ColorPoint xy = { cx, cy, Y };
228
- // Check if the given XY value is within the color reach of our lamps.
229
- if (!isPointInLampsReach (lamp, xy)) {
230
- // 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.
231
- ColorPoint pAB = getClosestPointToPoint (lamp.colorSpace .red , lamp.colorSpace .green , xy);
232
- ColorPoint pAC = getClosestPointToPoint (lamp.colorSpace .blue , lamp.colorSpace .red , xy);
233
- ColorPoint pBC = getClosestPointToPoint (lamp.colorSpace .green , lamp.colorSpace .blue , xy);
234
- // Get the distances per point and see which point is closer to our Point.
235
- float dAB = getDistanceBetweenTwoPoints (xy, pAB);
236
- float dAC = getDistanceBetweenTwoPoints (xy, pAC);
237
- float dBC = getDistanceBetweenTwoPoints (xy, pBC);
238
- float lowest = dAB;
239
- ColorPoint closestPoint = pAB;
240
- if (dAC < lowest) {
241
- lowest = dAC;
242
- closestPoint = pAC;
243
- }
244
- if (dBC < lowest) {
245
- lowest = dBC;
246
- closestPoint = pBC;
247
- }
248
- // Change the xy value to a value which is within the reach of the lamp.
249
- xy.x = closestPoint.x ;
250
- xy.y = closestPoint.y ;
251
- }
252
- return xy;
253
- }
254
-
255
- HueLamp::HueLamp (unsigned int id, QString originalState, QString modelId) :
256
- id(id), originalState(originalState) {
257
- // Hue system model ids.
258
- const std::set<QString> HUE_BULBS_MODEL_IDS = { " LCT001" , " LCT002" , " LCT003" };
259
- const std::set<QString> LIVING_COLORS_MODEL_IDS = { " LLC001" , " LLC005" , " LLC006" , " LLC007" , " LLC011" , " LLC012" ,
260
- " LLC013" , " LST001" };
261
- // Find id in the sets and set the appropiate color space.
262
- if (HUE_BULBS_MODEL_IDS.find (modelId) != HUE_BULBS_MODEL_IDS.end ()) {
263
- colorSpace.red = {0 .675f , 0 .322f };
264
- colorSpace.green = {0 .4091f , 0 .518f };
265
- colorSpace.blue = {0 .167f , 0 .04f };
266
- } else if (LIVING_COLORS_MODEL_IDS.find (modelId) != LIVING_COLORS_MODEL_IDS.end ()) {
267
- colorSpace.red = {0 .703f , 0 .296f };
268
- colorSpace.green = {0 .214f , 0 .709f };
269
- colorSpace.blue = {0 .139f , 0 .081f };
270
- } else {
271
- colorSpace.red = {1 .0f , 0 .0f };
272
- colorSpace.green = {0 .0f , 1 .0f };
273
- colorSpace.blue = {0 .0f , 0 .0f };
274
- }
275
- // Initialize color with black
276
- color = {0 .0f , 0 .0f , 0 .0f };
277
- }
278
-
279
- bool operator ==(ColorPoint p1, ColorPoint p2) {
280
- return (p1.x == p2.x ) && (p1.y == p2.y ) && (p1.bri == p2.bri );
281
- }
282
-
283
- bool operator !=(ColorPoint p1, ColorPoint p2) {
284
- return !(p1 == p2);
285
- }
0 commit comments