node-red-contrib-huemagic 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ module.exports = function(RED)
8
8
 
9
9
  const scope = this;
10
10
  const bridge = RED.nodes.getNode(config.bridge);
11
+ const async = require('async');
11
12
 
12
13
  // SAVE FUTURE PATCH
13
14
  this.futurePatchState = {};
@@ -125,7 +126,7 @@ module.exports = function(RED)
125
126
  let currentState = bridge.get("light", tempLightID, { colornames: config.colornamer ? true : false });
126
127
  if(!currentState)
127
128
  {
128
- scope.error("The light in not yet available. Please wait until HueMagic has established a connection with the bridge or check whether the resource ID in the configuration is valid..");
129
+ scope.error("The light in not yet available. Please wait until HueMagic has established a connection with the bridge or check whether the resource ID in the configuration is valid.");
129
130
  return false;
130
131
  }
131
132
 
@@ -171,18 +172,43 @@ module.exports = function(RED)
171
172
  "bri": msg.payload.brightness ? Math.round((254/100)*msg.payload.brightness) : currentState.brightnessLevel
172
173
  };
173
174
 
174
- bridge.patch("light", currentState.info.idV1 + "/state", patchObject, 1)
175
- .then(function(status) {
176
- // RESET COLORLOOP ANIMATION AFTER X SECONDS
177
- setTimeout(function()
178
- {
179
- bridge.patch("light", currentState.info.idV1 + "/state", { "effect": "none" }, 1);
180
- if(done) { done(); }
181
- }, parseInt(msg.payload.colorloop) * 1000);
182
- })
183
- .catch(function(errors) {
184
- scope.error(errors);
185
- scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
175
+ // PATCH!
176
+ async.retry({
177
+ times: 3,
178
+ errorFilter: function(err) {
179
+ return (err.status == 503);
180
+ },
181
+ interval: function(retryCount) { return retryCount*2000; }
182
+ },
183
+ function(callback, results)
184
+ {
185
+ bridge.patch("light", currentState.info.idV1 + "/state", patchObject, 1)
186
+ .then(function(status)
187
+ {
188
+ // RESET COLORLOOP ANIMATION AFTER X SECONDS
189
+ setTimeout(function()
190
+ {
191
+ bridge.patch("light", currentState.info.idV1 + "/state", { "effect": "none" }, 1);
192
+ }, parseInt(msg.payload.colorloop) * 1000);
193
+
194
+ callback(null, true);
195
+ })
196
+ .catch(function(errors)
197
+ {
198
+ callback(errors, null);
199
+ });
200
+ },
201
+ function(errors, success)
202
+ {
203
+ if(errors)
204
+ {
205
+ scope.error(errors);
206
+ scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
207
+ }
208
+ else if(done)
209
+ {
210
+ done();
211
+ }
186
212
  });
187
213
 
188
214
  return false;
@@ -261,48 +287,72 @@ module.exports = function(RED)
261
287
  scope.status({fill: "grey", shape: "ring", text: "hue-light.node.command"});
262
288
  }
263
289
 
264
- // 1. TURN ON THE LIGHT BULB
265
- bridge.patch("light", tempLightID, patchObject)
266
- .then(function(status) {
267
- // 2. APPLY ALERT EFFECT
268
- const alertEffect = { alert: { action: "breathe" }};
269
- return bridge.patch("light", tempLightID, alertEffect);
270
- })
271
- .then(function(status) {
272
- // 3. RESET PREVIOUS STATE (AFTER X SECONDS)
273
- setTimeout(function()
274
- {
275
- const tempPreviousState = scope.context().get('lightPreviousState');
276
- var tempPreviousStatePatch = {};
277
-
278
- tempPreviousStatePatch.dimming = { brightness: tempPreviousState.payload.brightness };
279
- if(tempPreviousState.payload.xyColor)
280
- {
281
- tempPreviousStatePatch.color = { xy: tempPreviousState.payload.xyColor };
282
- }
283
- else if(tempPreviousState.payload.colorTemp)
284
- {
285
- tempPreviousStatePatch.color_temperature = { mirek: tempPreviousState.payload.colorTemp };
286
- }
287
290
 
288
- bridge.patch("light", tempLightID, tempPreviousStatePatch).
289
- then(function(status)
291
+ // APPLY THE EFFECT
292
+ async.retry({
293
+ times: 3,
294
+ errorFilter: function(err) {
295
+ return (err.status == 503);
296
+ },
297
+ interval: function(retryCount) { return retryCount*2000; }
298
+ },
299
+ function(callback, results)
300
+ {
301
+ // 1. TURN ON THE LIGHT BULB
302
+ bridge.patch("light", tempLightID, patchObject)
303
+ .then(function(status) {
304
+ // 2. APPLY ALERT EFFECT
305
+ const alertEffect = { alert: { action: "breathe" }};
306
+ return bridge.patch("light", tempLightID, alertEffect);
307
+ })
308
+ .then(function(status) {
309
+ // 3. RESET PREVIOUS STATE (AFTER X SECONDS)
310
+ setTimeout(function()
290
311
  {
291
- return bridge.patch("light", tempLightID, { on: { on: false } })
292
- .then(function() { if(done) { done(); }});
293
- })
294
- .then(function(status) {
295
- if(tempPreviousState.payload.on === true)
312
+ const tempPreviousState = scope.context().get('lightPreviousState');
313
+ var tempPreviousStatePatch = {};
314
+
315
+ tempPreviousStatePatch.dimming = { brightness: tempPreviousState.payload.brightness };
316
+ if(tempPreviousState.payload.xyColor)
296
317
  {
297
- bridge.patch("light", tempLightID, { on: { on: true } })
298
- .then(function() { if(done) { done(); }});
318
+ tempPreviousStatePatch.color = { xy: tempPreviousState.payload.xyColor };
319
+ }
320
+ else if(tempPreviousState.payload.colorTemp)
321
+ {
322
+ tempPreviousStatePatch.color_temperature = { mirek: tempPreviousState.payload.colorTemp };
299
323
  }
300
- });
301
- }, parseInt(msg.payload.alert) * 1000);
302
- })
303
- .catch(function(errors) {
304
- scope.error(errors);
305
- scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
324
+
325
+ bridge.patch("light", tempLightID, tempPreviousStatePatch).
326
+ then(function(status)
327
+ {
328
+ return bridge.patch("light", tempLightID, { on: { on: false } })
329
+ .then(function() { if(done) { done(); }});
330
+ })
331
+ .then(function(status) {
332
+ if(tempPreviousState.payload.on === true)
333
+ {
334
+ bridge.patch("light", tempLightID, { on: { on: true } })
335
+ .then(function() { if(done) { done(); }});
336
+ }
337
+ });
338
+ }, parseInt(msg.payload.alert) * 1000);
339
+ callback(null, true);
340
+ })
341
+ .catch(function(errors) {
342
+ callback(errors, null);
343
+ });
344
+ },
345
+ function(errors, success)
346
+ {
347
+ if(errors)
348
+ {
349
+ scope.error(errors);
350
+ scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
351
+ }
352
+ else if(done)
353
+ {
354
+ done();
355
+ }
306
356
  });
307
357
  }
308
358
  // ANIMATION STARTED?
@@ -327,21 +377,44 @@ module.exports = function(RED)
327
377
  tempPreviousStatePatch.color_temperature = { mirek: tempPreviousState.payload.colorTemp };
328
378
  }
329
379
 
330
- bridge.patch("light", tempLightID, tempPreviousStatePatch).
331
- then(function(status)
380
+ // PATCH!
381
+ async.retry({
382
+ times: 3,
383
+ errorFilter: function(err) {
384
+ return (err.status == 503);
385
+ },
386
+ interval: function(retryCount) { return retryCount*2000; }
387
+ },
388
+ function(callback, results)
332
389
  {
333
- if(tempPreviousState.payload.on === false)
390
+ bridge.patch("light", tempLightID, tempPreviousStatePatch)
391
+ .then(function(status)
334
392
  {
335
- bridge.patch("light", tempLightID, { on: { on: false } })
336
- .then(function() { if(done) { done(); }});
393
+ if(tempPreviousState.payload.on === false)
394
+ {
395
+ bridge.patch("light", tempLightID, { on: { on: false } })
396
+ .then(function() { callback(null, true); });
397
+ }
398
+ else
399
+ {
400
+ bridge.patch("light", tempLightID, { on: { on: false } })
401
+ .then(function(status) {
402
+ callback(null, true);
403
+ return bridge.patch("light", tempLightID, { on: { on: true } });
404
+ });
405
+ }
406
+ })
407
+ .catch(function(errors) { callback(errors, null); });
408
+ },
409
+ function(errors, success)
410
+ {
411
+ if(errors)
412
+ {
413
+ scope.error(errors);
337
414
  }
338
- else
415
+ else if(done)
339
416
  {
340
- bridge.patch("light", tempLightID, { on: { on: false } })
341
- .then(function(status) {
342
- if(done) { done(); }
343
- return bridge.patch("light", tempLightID, { on: { on: true } })
344
- });
417
+ done();
345
418
  }
346
419
  });
347
420
  }
@@ -613,16 +686,42 @@ module.exports = function(RED)
613
686
  }
614
687
 
615
688
  // SET DOMINANT COLORS FROM IMAGE
616
- if(typeof msg.payload != 'undefined' && typeof msg.payload.image != 'undefined' && typeof currentState.payload.xyColor != 'undefined')
689
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.image != 'undefined' && (typeof currentState.payload.xyColor != 'undefined' || typeof currentState.payload.gradient != 'undefined'))
617
690
  {
618
- var colors = await colorUtils.getColors(msg.payload.image);
691
+ let colors = await colorUtils.getColors(msg.payload.image);
619
692
  if(colors.length > 0)
620
693
  {
621
- var colorsHEX = colors.map(color => color.hex());
622
- let rgbFromHex = colorUtils.hexRgb(colorsHEX[0]);
623
- patchObject["color"] = {
624
- xy: colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut)
625
- };
694
+ let colorsHEX = colors.map(color => color.hex());
695
+
696
+ // SET MULTIPLE COLORS ON SUPPORTED LIGHTS
697
+ if(typeof currentState.payload.gradient != 'undefined')
698
+ {
699
+ let XYColorSet = [];
700
+
701
+ for (var i = 0; i < currentState.payload.gradient.totalColors; i++)
702
+ {
703
+ if(typeof colorsHEX[i] != 'undefined')
704
+ {
705
+ let rgbFromHex = colorUtils.hexRgb(colorsHEX[i]);
706
+ XYColorSet.push({
707
+ color: { xy: colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut) }
708
+ });
709
+ }
710
+ }
711
+
712
+ if(XYColorSet.length > 0)
713
+ {
714
+ patchObject["gradient"] = { points: XYColorSet };
715
+ }
716
+ }
717
+ // SET SINGLE COLOR
718
+ else
719
+ {
720
+ let rgbFromHex = colorUtils.hexRgb(colorsHEX[0]);
721
+ patchObject["color"] = {
722
+ xy: colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut)
723
+ };
724
+ }
626
725
  }
627
726
  }
628
727
 
@@ -718,9 +817,30 @@ module.exports = function(RED)
718
817
  }
719
818
 
720
819
  // PATCH!
721
- bridge.patch("light", tempLightID, patchObject)
722
- .then(function() { if(done) { done(); }})
723
- .catch(function(errors) { scope.error(errors); });
820
+ async.retry({
821
+ times: 3,
822
+ errorFilter: function(err) {
823
+ return (err.status == 503);
824
+ },
825
+ interval: function(retryCount) { return retryCount*2000; }
826
+ },
827
+ function(callback, results)
828
+ {
829
+ bridge.patch("light", tempLightID, patchObject)
830
+ .then(function() { callback(null, true); })
831
+ .catch(function(errors) { callback(errors, null); });
832
+ },
833
+ function(errors, success)
834
+ {
835
+ if(errors)
836
+ {
837
+ scope.error(errors);
838
+ }
839
+ else if(done)
840
+ {
841
+ done();
842
+ }
843
+ });
724
844
  }
725
845
  else
726
846
  {
@@ -8,6 +8,7 @@ module.exports = function(RED)
8
8
 
9
9
  const scope = this;
10
10
  const bridge = RED.nodes.getNode(config.bridge);
11
+ const async = require('async');
11
12
 
12
13
  // SAVE LAST COMMAND
13
14
  this.lastCommand = null;
@@ -110,7 +111,7 @@ module.exports = function(RED)
110
111
  let currentState = bridge.get("motion", tempSensorID);
111
112
  if(!currentState)
112
113
  {
113
- scope.error("The sensor in not yet available. Please wait until HueMagic has established a connection with the bridge or check whether the resource ID in the configuration is valid..");
114
+ scope.error("The sensor in not yet available. Please wait until HueMagic has established a connection with the bridge or check whether the resource ID in the configuration is valid.");
114
115
  return false;
115
116
  }
116
117
 
@@ -151,9 +152,30 @@ module.exports = function(RED)
151
152
  }
152
153
 
153
154
  // PATCH!
154
- bridge.patch("motion", tempSensorID, patchObject)
155
- .then(function() { if(done) { done(); }})
156
- .catch(function(errors) { scope.error(errors); });
155
+ async.retry({
156
+ times: 3,
157
+ errorFilter: function(err) {
158
+ return (err.status == 503);
159
+ },
160
+ interval: function(retryCount) { return retryCount*2000; }
161
+ },
162
+ function(callback, results)
163
+ {
164
+ bridge.patch("motion", tempSensorID, patchObject)
165
+ .then(function() { if(done) { callback(null, true); })
166
+ .catch(function(errors) { callback(errors, null); });
167
+ },
168
+ function(errors, success)
169
+ {
170
+ if(errors)
171
+ {
172
+ scope.error(errors);
173
+ }
174
+ else if(done)
175
+ {
176
+ done();
177
+ }
178
+ });
157
179
  }
158
180
  else
159
181
  {
@@ -8,6 +8,7 @@ module.exports = function(RED)
8
8
 
9
9
  const scope = this;
10
10
  const bridge = RED.nodes.getNode(config.bridge);
11
+ const async = require('async');
11
12
 
12
13
  // SAVE LAST COMMAND
13
14
  this.lastCommand = null;
@@ -100,7 +101,7 @@ module.exports = function(RED)
100
101
  let currentState = bridge.get("rule", "rule_" + tempRuleID);
101
102
  if(!currentState)
102
103
  {
103
- scope.error("The rule in not yet available. Please wait until HueMagic has established a connection with the bridge or check whether the resource ID in the configuration is valid..");
104
+ scope.error("The rule in not yet available. Please wait until HueMagic has established a connection with the bridge or check whether the resource ID in the configuration is valid.");
104
105
  return false;
105
106
  }
106
107
 
@@ -111,12 +112,30 @@ module.exports = function(RED)
111
112
  patchObject["status"] = (msg.payload == true) ? 'enabled' : 'disabled';
112
113
 
113
114
  // PATCH!
114
- bridge.patch("rules", "/rules/"+tempRuleID, patchObject, 1)
115
- .then(function(response) { bridge.refetchRule(tempRuleID); })
116
- .catch(function(errors) { scope.error(errors); });
117
-
118
- // DONE?
119
- if(done) { done(); }
115
+ async.retry({
116
+ times: 3,
117
+ errorFilter: function(err) {
118
+ return (err.status == 503);
119
+ },
120
+ interval: function(retryCount) { return retryCount*2000; }
121
+ },
122
+ function(callback, results)
123
+ {
124
+ bridge.patch("rules", "/rules/"+tempRuleID, patchObject, 1)
125
+ .then(function(response) { bridge.refetchRule(tempRuleID); callback(null, true); })
126
+ .catch(function(errors) { callback(errors, null); });
127
+ },
128
+ function(errors, success)
129
+ {
130
+ if(errors)
131
+ {
132
+ scope.error(errors);
133
+ }
134
+ else if(done)
135
+ {
136
+ done();
137
+ }
138
+ });
120
139
  }
121
140
  else
122
141
  {
@@ -8,6 +8,7 @@ module.exports = function(RED)
8
8
 
9
9
  const scope = this;
10
10
  const bridge = RED.nodes.getNode(config.bridge);
11
+ const async = require('async');
11
12
 
12
13
  // GET TARGET WIRED GROUPS
13
14
  this.targetGroups = {};
@@ -72,9 +73,32 @@ module.exports = function(RED)
72
73
 
73
74
  if(targetGroup)
74
75
  {
75
- bridge.patch("group", targetGroup.info.idV1 + "/action", { "scene": targetSceneID }, 1)
76
- .catch(function(errors) {
77
- scope.error(errors);
76
+ // PATCH!
77
+ async.retry({
78
+ times: 3,
79
+ errorFilter: function(err) {
80
+ return (err.status == 503);
81
+ },
82
+ interval: function(retryCount) { return retryCount*2000; }
83
+ },
84
+ function(callback, results)
85
+ {
86
+ bridge.patch("group", targetGroup.info.idV1 + "/action", { "scene": targetSceneID }, 1)
87
+ .then(function() { callback(null, true); })
88
+ .catch(function(errors) {
89
+ callback(errors, null);
90
+ });
91
+ },
92
+ function(errors, success)
93
+ {
94
+ if(errors)
95
+ {
96
+ scope.error(errors);
97
+ }
98
+ else if(done)
99
+ {
100
+ done();
101
+ }
78
102
  });
79
103
  }
80
104
  }
@@ -87,27 +111,49 @@ module.exports = function(RED)
87
111
  else
88
112
  {
89
113
  // RECALL SCENE
90
- patchObject["recall"] = { status: "active" };
114
+ patchObject["recall"] = { action: "active" };
91
115
 
92
116
  // CHANGE NODE UI STATE
93
117
  scope.status({fill: "grey", shape: "ring", text: "hue-scene.node.command"});
94
118
 
95
119
  // PATCH!
96
- bridge.patch("scene", sceneDef, patchObject)
97
- .then(function()
120
+ async.retry({
121
+ times: 3,
122
+ errorFilter: function(err) {
123
+ return (err.status == 503);
124
+ },
125
+ interval: function(retryCount) { return retryCount*2000; }
126
+ },
127
+ function(callback, results)
98
128
  {
99
- scope.status({fill: "blue", shape: "dot", text: "hue-scene.node.recalled"});
100
- if(done) { done(); }
101
-
102
- // RESET STATUS AFTER 1 SECOND
103
- setTimeout(function() {
104
- scope.status({});
105
- }, 1000);
106
- })
107
- .catch(function(errors) { scope.error(errors); });
129
+ bridge.patch("scene", sceneDef, patchObject)
130
+ .then(function()
131
+ {
132
+ scope.status({fill: "blue", shape: "dot", text: "hue-scene.node.recalled"});
133
+
134
+ // RESET STATUS AFTER 1 SECOND
135
+ setTimeout(function() {
136
+ scope.status({});
137
+ }, 1000);
138
+
139
+ callback(null, true);
140
+ })
141
+ .catch(function(errors) { callback(errors, null); });
142
+ },
143
+ function(errors, success)
144
+ {
145
+ if(errors)
146
+ {
147
+ scope.error(errors);
148
+ }
149
+ else if(done)
150
+ {
151
+ done();
152
+ }
153
+ });
108
154
  }
109
155
  });
110
156
  }
111
157
 
112
158
  RED.nodes.registerType("hue-scene", HueScene);
113
- }
159
+ }
@@ -8,6 +8,7 @@ module.exports = function(RED)
8
8
 
9
9
  const scope = this;
10
10
  const bridge = RED.nodes.getNode(config.bridge);
11
+ const async = require('async');
11
12
 
12
13
  // SAVE LAST COMMAND
13
14
  this.lastCommand = null;
@@ -118,7 +119,7 @@ module.exports = function(RED)
118
119
  let currentState = bridge.get("temperature", tempSensorID);
119
120
  if(!currentState)
120
121
  {
121
- scope.error("The sensor in not yet available. Please wait until HueMagic has established a connection with the bridge or check whether the resource ID in the configuration is valid..");
122
+ scope.error("The sensor in not yet available. Please wait until HueMagic has established a connection with the bridge or check whether the resource ID in the configuration is valid.");
122
123
  return false;
123
124
  }
124
125
 
@@ -159,9 +160,30 @@ module.exports = function(RED)
159
160
  }
160
161
 
161
162
  // PATCH!
162
- bridge.patch("temperature", tempSensorID, patchObject)
163
- .then(function() { if(done) { done(); }})
164
- .catch(function(errors) { scope.error(errors); });
163
+ async.retry({
164
+ times: 3,
165
+ errorFilter: function(err) {
166
+ return (err.status == 503);
167
+ },
168
+ interval: function(retryCount) { return retryCount*2000; }
169
+ },
170
+ function(callback, results)
171
+ {
172
+ bridge.patch("temperature", tempSensorID, patchObject)
173
+ .then(function() { callback(null, true); })
174
+ .catch(function(errors) { callback(errors, null); });
175
+ },
176
+ function(errors, success)
177
+ {
178
+ if(errors)
179
+ {
180
+ scope.error(errors);
181
+ }
182
+ else if(done)
183
+ {
184
+ done();
185
+ }
186
+ });
165
187
  }
166
188
  else
167
189
  {
@@ -15,7 +15,7 @@ Geben Sie den Hue Bridge API-Schlüssel manuell ein oder drücken Sie den Button
15
15
 
16
16
  ### Worker
17
17
 
18
- Der "Worker"-Wert bestimmt die Anzahl der parallelen Befehle, die HueMagic an die Bridge gleichzeitig schicken darf. Laut offizieller Dokumentation der Philips Hue Bridge, sollten nicht mehr als 10 Befehle pro Sekunde abgeschickt werden, da sonst die Gefahr besteht, dass die Bridge die Anfragen nicht so schnell verarbeiten kann und Fehler auswirft.
18
+ Der „Worker“-Wert bestimmt die Anzahl der parallelen Befehle, die HueMagic an die Bridge gleichzeitig schicken darf. Laut offizieller Dokumentation der Philips Hue Bridge, sollten nicht mehr als 10 Befehle pro Sekunde abgeschickt werden, da sonst die Gefahr besteht, dass die Bridge die Anfragen nicht so schnell verarbeiten kann und Fehler auswirft.
19
19
 
20
20
  Wenn mehr Befehle gleichzeitig abgeschickt werden als der hier gesetzte Wert, wird HueMagic die Befehle nacheinander ausführen, sobald der Durchsatz wieder möglich ist.
21
21
 
@@ -90,14 +90,7 @@ function API()
90
90
  {
91
91
  if(version === 2)
92
92
  {
93
- if(response.data.errors.length > 0)
94
- {
95
- reject(response.data.errors);
96
- }
97
- else
98
- {
99
- resolve(response.data.data);
100
- }
93
+ resolve(response.data.data);
101
94
  }
102
95
  else if(version === 1)
103
96
  {
@@ -106,7 +99,7 @@ function API()
106
99
  })
107
100
  .catch(function(error)
108
101
  {
109
- reject(error);
102
+ reject({ status: error.response.status, errors: error.response.data.errors ? error.response.data.errors : error.response.data});
110
103
  });
111
104
  });
112
105
  }
@@ -154,7 +147,7 @@ function API()
154
147
  // ERROR? -> RETRY?
155
148
  scope.events[config.id].onerror = function(error)
156
149
  {
157
- console.log("HueMagic:", "Connection to bridge lost. Trying to reconnect again in 30 seconds…", err);
150
+ console.log("HueMagic:", "Connection to bridge lost. Trying to reconnect again in 30 seconds…", error);
158
151
  setTimeout(function(){ scope.subscribe(config, callback); }, 30000);
159
152
  resolve(true);
160
153
  }