node-red-contrib-huemagic 4.0.4 → 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
  // EXPORT CONFIG
13
14
  this.exportedConfig = config;
@@ -110,6 +111,11 @@ module.exports = function(RED)
110
111
  // DEFINE SENSOR ID & CURRENT STATE
111
112
  const tempGroupID = (!config.groupid && typeof msg.topic != 'undefined' && bridge.validResourceID.test(msg.topic) === true) ? msg.topic : config.groupid;
112
113
  let currentState = bridge.get("group", tempGroupID, { colornames: config.colornamer ? true : false });
114
+ if(!currentState)
115
+ {
116
+ scope.error("The group 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.");
117
+ return false;
118
+ }
113
119
 
114
120
  // CHECK IF LIGHT ID IS SET
115
121
  if(!tempGroupID)
@@ -153,18 +159,41 @@ module.exports = function(RED)
153
159
  "bri": msg.payload.brightness ? Math.round((254/100)*msg.payload.brightness) : 254
154
160
  };
155
161
 
156
- bridge.patch("group", currentState.info.idV1 + "/action", patchObject, 1)
157
- .then(function(status) {
158
- // RESET COLORLOOP ANIMATION AFTER X SECONDS
159
- setTimeout(function()
160
- {
161
- bridge.patch("group", currentState.info.idV1 + "/action", { "effect": "none" }, 1)
162
- .then(function() { if(done) { done(); }});
163
- }, parseInt(msg.payload.colorloop) * 1000);
164
- })
165
- .catch(function(errors) {
166
- scope.error(errors);
167
- scope.status({fill: "red", shape: "ring", text: "hue-group.node.error-input"});
162
+ // PATCH!
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("group", currentState.info.idV1 + "/action", patchObject, 1)
173
+ .then(function(status) {
174
+ // RESET COLORLOOP ANIMATION AFTER X SECONDS
175
+ setTimeout(function()
176
+ {
177
+ bridge.patch("group", currentState.info.idV1 + "/action", { "effect": "none" }, 1)
178
+ .then(function() { if(done) { done(); }});
179
+ }, parseInt(msg.payload.colorloop) * 1000);
180
+ callback(null, true);
181
+ })
182
+ .catch(function(errors) {
183
+ callback(errors, null);
184
+ });
185
+ },
186
+ function(errors, success)
187
+ {
188
+ if(errors)
189
+ {
190
+ scope.error(errors);
191
+ scope.status({fill: "red", shape: "ring", text: "hue-group.node.error-input"});
192
+ }
193
+ else if(done)
194
+ {
195
+ done();
196
+ }
168
197
  });
169
198
 
170
199
  return false;
@@ -235,42 +264,65 @@ module.exports = function(RED)
235
264
  // SET ALERT EFFECT
236
265
  patchObject["alert"] = "lselect";
237
266
 
238
- // 1. TURN ON THE LIGHT BULB
239
- bridge.patch("group", currentState.info.idV1 + "/action", patchObject, 1)
240
- .then(function(status) {
241
- setTimeout(function()
267
+ // PATCH!
268
+ async.retry({
269
+ times: 3,
270
+ errorFilter: function(err) {
271
+ return (err.status == 503);
272
+ },
273
+ interval: function(retryCount) { return retryCount*2000; }
274
+ },
275
+ function(callback, results)
276
+ {
277
+ // 1. TURN ON THE LIGHT BULB
278
+ bridge.patch("group", currentState.info.idV1 + "/action", patchObject, 1)
279
+ .then(function(status)
242
280
  {
243
- const tempPreviousState = scope.context().get('groupPreviousState');
244
- var tempPreviousStatePatch = {};
245
-
246
- tempPreviousStatePatch.dimming = { brightness: tempPreviousState.payload.brightness };
247
- if(tempPreviousState.payload.xyColor)
248
- {
249
- tempPreviousStatePatch.xy = [tempPreviousState.payload.xyColor.x, tempPreviousState.payload.xyColor.y];
250
- }
251
- else if(tempPreviousState.payload.colorTemp)
281
+ setTimeout(function()
252
282
  {
253
- tempPreviousStatePatch.ct = tempPreviousState.payload.colorTemp;
254
- }
283
+ const tempPreviousState = scope.context().get('groupPreviousState');
284
+ var tempPreviousStatePatch = {};
255
285
 
256
- bridge.patch("group", currentState.info.idV1 + "/action", tempPreviousStatePatch, 1)
257
- .then(function(status)
258
- {
259
- return bridge.patch("group", currentState.info.idV1 + "/action", { on: false }, 1);
260
- })
261
- .then(function(status) {
262
- if(done) { done(); }
263
- if(tempPreviousState.payload.on === true)
286
+ tempPreviousStatePatch.dimming = { brightness: tempPreviousState.payload.brightness };
287
+ if(tempPreviousState.payload.xyColor)
264
288
  {
265
- bridge.patch("group", currentState.info.idV1, { on: true }, 1);
289
+ tempPreviousStatePatch.xy = [tempPreviousState.payload.xyColor.x, tempPreviousState.payload.xyColor.y];
266
290
  }
267
- });
268
- }, parseInt(msg.payload.alert) * 1000);
269
- })
270
- .catch(function(errors) {
271
- scope.error(errors);
272
- scope.status({fill: "red", shape: "ring", text: "hue-group.node.error-input"});
273
- if(done) { done(error); }
291
+ else if(tempPreviousState.payload.colorTemp)
292
+ {
293
+ tempPreviousStatePatch.ct = tempPreviousState.payload.colorTemp;
294
+ }
295
+
296
+ bridge.patch("group", currentState.info.idV1 + "/action", tempPreviousStatePatch, 1)
297
+ .then(function(status)
298
+ {
299
+ return bridge.patch("group", currentState.info.idV1 + "/action", { on: false }, 1);
300
+ })
301
+ .then(function(status) {
302
+ if(tempPreviousState.payload.on === true)
303
+ {
304
+ bridge.patch("group", currentState.info.idV1, { on: true }, 1);
305
+ }
306
+ });
307
+ }, parseInt(msg.payload.alert) * 1000);
308
+
309
+ callback(null, true);
310
+ })
311
+ .catch(function(errors) {
312
+ callback(errors, null);
313
+ });
314
+ },
315
+ function(errors, success)
316
+ {
317
+ if(errors)
318
+ {
319
+ scope.error(errors);
320
+ scope.status({fill: "red", shape: "ring", text: "hue-group.node.error-input"});
321
+ }
322
+ else if(done)
323
+ {
324
+ done();
325
+ }
274
326
  });
275
327
  }
276
328
  // ANIMATION STARTED?
@@ -295,19 +347,44 @@ module.exports = function(RED)
295
347
  tempPreviousStatePatch.ct = tempPreviousState.payload.colorTemp;
296
348
  }
297
349
 
298
- bridge.patch("light", currentState.info.lightIds[l], tempPreviousStatePatch).
299
- then(function(status)
350
+ // PATCH!
351
+ async.retry({
352
+ times: 3,
353
+ errorFilter: function(err) {
354
+ return (err.status == 503);
355
+ },
356
+ interval: function(retryCount) { return retryCount*2000; }
357
+ },
358
+ function(callback, results)
300
359
  {
301
- if(tempPreviousState.payload.on === false)
360
+ bridge.patch("light", currentState.info.lightIds[l], tempPreviousStatePatch).
361
+ then(function(status)
302
362
  {
303
- bridge.patch("light", currentState.info.lightIds[l], { on: { on: false } })
363
+ if(tempPreviousState.payload.on === false)
364
+ {
365
+ bridge.patch("light", currentState.info.lightIds[l], { on: { on: false } })
366
+ .then(function() { callback(null, true); });
367
+ }
368
+ else
369
+ {
370
+ bridge.patch("light", currentState.info.lightIds[l], { on: { on: false } })
371
+ .then(function(status) {
372
+ callback(null, true);
373
+ return bridge.patch("light", currentState.info.lightIds[l], { on: { on: true } });
374
+ });
375
+ }
376
+ })
377
+ .catch(function(errors) { callback(errors, null); });
378
+ },
379
+ function(errors, success)
380
+ {
381
+ if(errors)
382
+ {
383
+ scope.error(errors);
304
384
  }
305
- else
385
+ else if(done)
306
386
  {
307
- bridge.patch("light", currentState.info.lightIds[l], { on: { on: false } })
308
- .then(function(status) {
309
- return bridge.patch("light", currentState.info.lightIds[l], { on: { on: true } })
310
- });
387
+ done();
311
388
  }
312
389
  });
313
390
  }
@@ -511,9 +588,30 @@ module.exports = function(RED)
511
588
  }
512
589
 
513
590
  // PATCH!
514
- bridge.patch("group", currentState.info.idV1 + "/action", patchObject, 1)
515
- .then(function() { if(done) { done(); }})
516
- .catch(function(errors) { scope.error(errors); });
591
+ async.retry({
592
+ times: 3,
593
+ errorFilter: function(err) {
594
+ return (err.status == 503);
595
+ },
596
+ interval: function(retryCount) { return retryCount*2000; }
597
+ },
598
+ function(callback, results)
599
+ {
600
+ bridge.patch("group", currentState.info.idV1 + "/action", patchObject, 1)
601
+ .then(function() { callback(null, true); })
602
+ .catch(function(errors) { callback(errors, null); });
603
+ },
604
+ function(errors, success)
605
+ {
606
+ if(errors)
607
+ {
608
+ scope.error(errors);
609
+ }
610
+ else if(done)
611
+ {
612
+ done();
613
+ }
614
+ });
517
615
  }
518
616
  else
519
617
  {
@@ -88,6 +88,19 @@
88
88
  buttonIcon.removeClass("fa-pencil");
89
89
  buttonIcon.addClass("fa-search");
90
90
  }
91
+
92
+ function sortResourcesBy(prop, resources)
93
+ {
94
+ return resources.sort((a, b) => {
95
+ if ( a[prop] < b[prop] ){
96
+ return -1;
97
+ }
98
+ if ( a[prop] > b[prop] ){
99
+ return 1;
100
+ }
101
+ return 0;
102
+ });
103
+ }
91
104
 
92
105
  function searchAndSelect()
93
106
  {
@@ -105,6 +118,7 @@
105
118
  $.get('hue/resources', { bridge: bridgeConfig.bridge, key: bridgeConfig.key, type: "light" })
106
119
  .done( function(data) {
107
120
  var allResources = JSON.parse(data);
121
+ allResources = sortResourcesBy('name', allResources);
108
122
  if(allResources.length <= 0)
109
123
  {
110
124
  notification.close();
@@ -201,4 +215,4 @@
201
215
  }
202
216
  }
203
217
  });
204
- </script>
218
+ </script>
@@ -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 = {};
@@ -123,6 +124,11 @@ module.exports = function(RED)
123
124
  // DEFINE SENSOR ID & CURRENT STATE
124
125
  const tempLightID = (!config.lightid && typeof msg.topic != 'undefined' && bridge.validResourceID.test(msg.topic) === true) ? msg.topic : config.lightid;
125
126
  let currentState = bridge.get("light", tempLightID, { colornames: config.colornamer ? true : false });
127
+ if(!currentState)
128
+ {
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.");
130
+ return false;
131
+ }
126
132
 
127
133
  // CHECK IF LIGHT ID IS SET
128
134
  if(!tempLightID)
@@ -166,18 +172,43 @@ module.exports = function(RED)
166
172
  "bri": msg.payload.brightness ? Math.round((254/100)*msg.payload.brightness) : currentState.brightnessLevel
167
173
  };
168
174
 
169
- bridge.patch("light", currentState.info.idV1 + "/state", patchObject, 1)
170
- .then(function(status) {
171
- // RESET COLORLOOP ANIMATION AFTER X SECONDS
172
- setTimeout(function()
173
- {
174
- bridge.patch("light", currentState.info.idV1 + "/state", { "effect": "none" }, 1);
175
- if(done) { done(); }
176
- }, parseInt(msg.payload.colorloop) * 1000);
177
- })
178
- .catch(function(errors) {
179
- scope.error(errors);
180
- 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
+ }
181
212
  });
182
213
 
183
214
  return false;
@@ -256,48 +287,72 @@ module.exports = function(RED)
256
287
  scope.status({fill: "grey", shape: "ring", text: "hue-light.node.command"});
257
288
  }
258
289
 
259
- // 1. TURN ON THE LIGHT BULB
260
- bridge.patch("light", tempLightID, patchObject)
261
- .then(function(status) {
262
- // 2. APPLY ALERT EFFECT
263
- const alertEffect = { alert: { action: "breathe" }};
264
- return bridge.patch("light", tempLightID, alertEffect);
265
- })
266
- .then(function(status) {
267
- // 3. RESET PREVIOUS STATE (AFTER X SECONDS)
268
- setTimeout(function()
269
- {
270
- const tempPreviousState = scope.context().get('lightPreviousState');
271
- var tempPreviousStatePatch = {};
272
-
273
- tempPreviousStatePatch.dimming = { brightness: tempPreviousState.payload.brightness };
274
- if(tempPreviousState.payload.xyColor)
275
- {
276
- tempPreviousStatePatch.color = { xy: tempPreviousState.payload.xyColor };
277
- }
278
- else if(tempPreviousState.payload.colorTemp)
279
- {
280
- tempPreviousStatePatch.color_temperature = { mirek: tempPreviousState.payload.colorTemp };
281
- }
282
290
 
283
- bridge.patch("light", tempLightID, tempPreviousStatePatch).
284
- 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()
285
311
  {
286
- return bridge.patch("light", tempLightID, { on: { on: false } })
287
- .then(function() { if(done) { done(); }});
288
- })
289
- .then(function(status) {
290
- 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)
291
317
  {
292
- bridge.patch("light", tempLightID, { on: { on: true } })
293
- .then(function() { if(done) { done(); }});
318
+ tempPreviousStatePatch.color = { xy: tempPreviousState.payload.xyColor };
294
319
  }
295
- });
296
- }, parseInt(msg.payload.alert) * 1000);
297
- })
298
- .catch(function(errors) {
299
- scope.error(errors);
300
- scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
320
+ else if(tempPreviousState.payload.colorTemp)
321
+ {
322
+ tempPreviousStatePatch.color_temperature = { mirek: tempPreviousState.payload.colorTemp };
323
+ }
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
+ }
301
356
  });
302
357
  }
303
358
  // ANIMATION STARTED?
@@ -322,21 +377,44 @@ module.exports = function(RED)
322
377
  tempPreviousStatePatch.color_temperature = { mirek: tempPreviousState.payload.colorTemp };
323
378
  }
324
379
 
325
- bridge.patch("light", tempLightID, tempPreviousStatePatch).
326
- 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)
327
389
  {
328
- if(tempPreviousState.payload.on === false)
390
+ bridge.patch("light", tempLightID, tempPreviousStatePatch)
391
+ .then(function(status)
329
392
  {
330
- bridge.patch("light", tempLightID, { on: { on: false } })
331
- .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);
332
414
  }
333
- else
415
+ else if(done)
334
416
  {
335
- bridge.patch("light", tempLightID, { on: { on: false } })
336
- .then(function(status) {
337
- if(done) { done(); }
338
- return bridge.patch("light", tempLightID, { on: { on: true } })
339
- });
417
+ done();
340
418
  }
341
419
  });
342
420
  }
@@ -608,16 +686,42 @@ module.exports = function(RED)
608
686
  }
609
687
 
610
688
  // SET DOMINANT COLORS FROM IMAGE
611
- 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'))
612
690
  {
613
- var colors = await colorUtils.getColors(msg.payload.image);
691
+ let colors = await colorUtils.getColors(msg.payload.image);
614
692
  if(colors.length > 0)
615
693
  {
616
- var colorsHEX = colors.map(color => color.hex());
617
- let rgbFromHex = colorUtils.hexRgb(colorsHEX[0]);
618
- patchObject["color"] = {
619
- xy: colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut)
620
- };
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
+ }
621
725
  }
622
726
  }
623
727
 
@@ -713,9 +817,30 @@ module.exports = function(RED)
713
817
  }
714
818
 
715
819
  // PATCH!
716
- bridge.patch("light", tempLightID, patchObject)
717
- .then(function() { if(done) { done(); }})
718
- .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
+ });
719
844
  }
720
845
  else
721
846
  {
@@ -81,6 +81,19 @@
81
81
  buttonIcon.removeClass("fa-pencil");
82
82
  buttonIcon.addClass("fa-search");
83
83
  }
84
+
85
+ function sortResourcesBy(prop, resources)
86
+ {
87
+ return resources.sort((a, b) => {
88
+ if ( a[prop] < b[prop] ){
89
+ return -1;
90
+ }
91
+ if ( a[prop] > b[prop] ){
92
+ return 1;
93
+ }
94
+ return 0;
95
+ });
96
+ }
84
97
 
85
98
  function searchAndSelect()
86
99
  {
@@ -98,6 +111,7 @@
98
111
  $.get('hue/resources', { bridge: bridgeConfig.bridge, key: bridgeConfig.key, type: "motion" })
99
112
  .done( function(data) {
100
113
  var allResources = JSON.parse(data);
114
+ allResources = sortResourcesBy('name', allResources);
101
115
  if(allResources.length <= 0)
102
116
  {
103
117
  notification.close();
@@ -194,4 +208,4 @@
194
208
  }
195
209
  }
196
210
  });
197
- </script>
211
+ </script>