node-red-contrib-huemagic 4.0.3 → 4.0.4

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.
package/README.md CHANGED
@@ -12,7 +12,6 @@ HueMagic provides several input and output nodes for Node-RED and is the most in
12
12
  * Output and input of multiple color code definitions *(HEX, RGB & human readable color names)*
13
13
  * Automatic color temperature & brightness setting based on the current time
14
14
  * Event-based status messages for all devices connected to the Hue Bridge
15
- * Virtual pressing of the Hue Bridge button (Link Button)
16
15
  * Pairing of new devices without app enforcement (TouchLink)
17
16
  * Automatic firmware updates to the Hue Bridge and connected devices
18
17
  * Activating / deactivating of sensors & rules on the Hue Bridge
@@ -796,7 +795,12 @@ If the status of the node has changed via a certain command, the entire command
796
795
 
797
796
  # Changelog
798
797
 
799
- ### v4.0.3 (latest)
798
+ ### v4.0.4 (latest)
799
+
800
+ * Fixed an issue with the bridge config node checking for updates too frequently ([#246](https://github.com/Foddy/node-red-contrib-huemagic/issues/246#issuecomment-1009376442))
801
+ * Fixed an issue with multiple bridges configured
802
+
803
+ ### v4.0.3
800
804
 
801
805
  > **Attention!** HueMagic v4+ has been almost completely rewritten under the hood and requires at least the (square-shaped) Philips Hue Bridge firmware 1948086000+ from November 1st, 2021 ([Upgrade instructions](https://www.lighting.philips.com/content/B2C/en_US/microsites/meethue/marketing-catalog/huewireless_ca/support/security-advisory/general/where-and-how-can-i-update-my-hue-system-with-the-latest-software.html)) and Node-RED v1+ ([Upgrade instructions](https://nodered.org/docs/getting-started/local#upgrading-node-red)). If you are upgrading from a previous HueMagic version to the v4, you will have to reconfigure (not completely rebuild) all nodes by clicking them and selecting the appropriate device from the list. This also applies to nodes / functions that are operated in universal mode, as the numeric identifiers of the latest Philips Hue API version have been replaced in UUIDs. The nodes "Hue Switch", "Hue Button" & "Hue Tap" have been replaced in v4 by the universal and uniform node "Hue Buttons", which works with all button / switch devices that are connected to the Hue Bridge (please note here also the new API in the documentation). The request and return objects of the individual nodes are largely compatible with older HueMagic versions - with the exception of the nodes "Hue Bridge", "Hue Buttons", "Hue Scene" & "Hue Group". These need to be adjusted in the v4. Make sure that you meet the minimum technical requirements and have a quiet minute for the migration before upgrading to the v4.
802
806
 
@@ -36,21 +36,24 @@ module.exports = function(RED)
36
36
  // RESOURCE ID PATTERN
37
37
  this.validResourceID = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi;
38
38
 
39
+ // FIRMWARE UPDATE TIMEOUT
40
+ this.firmwareUpdateTimeout = null;
41
+
39
42
  // CREATE NODE
40
43
  RED.nodes.createNode(scope, config);
41
44
 
42
45
  // INITIALIZE
43
46
  this.start = function()
44
47
  {
45
- scope.log("Initializing the bridge…");
46
- API.init({ ip: config.bridge, key: config.key })
48
+ scope.log("Initializing the bridge ("+config.bridge+")…");
49
+ API.init({ config: config })
47
50
  .then(function(bridge) {
48
51
  scope.log("Connected to bridge");
49
52
  return scope.getAllResources();
50
53
  })
51
54
  .then(function(allResources)
52
55
  {
53
- scope.log("Process resources…");
56
+ scope.log("Processing bridge resources…");
54
57
  return API.processResources(allResources);
55
58
  })
56
59
  .then(function(allResources)
@@ -84,7 +87,7 @@ module.exports = function(RED)
84
87
  {
85
88
  return new Promise(function(resolve, reject)
86
89
  {
87
- API.request({ resource: "/config", version: 1 })
90
+ API.request({ config: config, resource: "/config", version: 1 })
88
91
  .then(function(bridgeInformation)
89
92
  {
90
93
  // PREPARE TO MATCH V2 RESOURCES
@@ -123,7 +126,7 @@ module.exports = function(RED)
123
126
  allResources.push(bridgeInformation);
124
127
 
125
128
  // CONTINUE WITH ALL RESOURCES
126
- return API.request({ resource: "all" });
129
+ return API.request({ config: config, resource: "all" });
127
130
  })
128
131
  .then(function(v2Resources)
129
132
  {
@@ -131,7 +134,7 @@ module.exports = function(RED)
131
134
  allResources = allResources.concat(v2Resources);
132
135
 
133
136
  // GET RULES (LEGACY API)
134
- return API.request({ resource: "/rules", version: 1 });
137
+ return API.request({ config: config, resource: "/rules", version: 1 });
135
138
  })
136
139
  .then(function(rules)
137
140
  {
@@ -193,7 +196,7 @@ module.exports = function(RED)
193
196
  this.refreshStatesSSE = function()
194
197
  {
195
198
  scope.log("Subscribing to bridge events…");
196
- API.subscribe(function(updates)
199
+ API.subscribe(config, function(updates)
197
200
  {
198
201
  const currentDateTime = dayjs().format();
199
202
 
@@ -266,8 +269,8 @@ module.exports = function(RED)
266
269
  this.pushUpdatedState = function(resource, updatedType, suppressMessage = false)
267
270
  {
268
271
  const msg = { id: resource.id, type: resource.type, updatedType: updatedType, services: resource["services"] ? Object.keys(resource["services"]) : [], suppressMessage: suppressMessage };
269
- this.events.emit(resource.id, msg);
270
- this.events.emit("globalResourceUpdates", msg);
272
+ this.events.emit(config.id + "_" + resource.id, msg);
273
+ this.events.emit(config.id + "_" + "globalResourceUpdates", msg);
271
274
 
272
275
  // RESOURCE CONTAINS SERVICES? -> SERVICE IN GROUP? -> EMIT CHANGES TO GROUPS ALSO
273
276
  if(this.resources["_groupsOf"][resource.id])
@@ -277,8 +280,8 @@ module.exports = function(RED)
277
280
  const groupID = this.resources["_groupsOf"][resource.id][g];
278
281
  const groupMessage = { id: groupID, type: "group", updatedType: updatedType, services: [], suppressMessage: suppressMessage };
279
282
 
280
- this.events.emit(groupID, groupMessage);
281
- this.events.emit("globalResourceUpdates", groupMessage);
283
+ this.events.emit(config.id + "_" + groupID, groupMessage);
284
+ this.events.emit(config.id + "_" + "globalResourceUpdates", groupMessage);
282
285
  }
283
286
  }
284
287
  }
@@ -440,7 +443,7 @@ module.exports = function(RED)
440
443
  }
441
444
 
442
445
  // ACTION!
443
- API.request({ method: "PUT", resource: (version === 2) ? (type+"/"+id) : id, data: patch, version: version })
446
+ API.request({ config: config, method: "PUT", resource: (version === 2) ? (type+"/"+id) : id, data: patch, version: version })
444
447
  .then(function(response) {
445
448
  resolve(response);
446
449
  })
@@ -455,7 +458,7 @@ module.exports = function(RED)
455
458
  {
456
459
  return new Promise(function(resolve, reject)
457
460
  {
458
- API.request({ resource: "/rules/" + id, version: 1 })
461
+ API.request({ config: config, resource: "/rules/" + id, version: 1 })
459
462
  .then(function(rule)
460
463
  {
461
464
  // "RENAME" OWNER
@@ -508,7 +511,7 @@ module.exports = function(RED)
508
511
  if(!id)
509
512
  {
510
513
  // UNIVERSAL MODE
511
- this.events.on("globalResourceUpdates", function(info)
514
+ this.events.on(config.id + "_" + "globalResourceUpdates", function(info)
512
515
  {
513
516
  if(type === "bridge")
514
517
  {
@@ -527,7 +530,7 @@ module.exports = function(RED)
527
530
  else
528
531
  {
529
532
  // SPECIFIC RESOURCE MODE
530
- this.events.on(id, function(info)
533
+ this.events.on(config.id + "_" + id, function(info)
531
534
  {
532
535
  if(type === "bridge" || messageWhitelist[type].includes(info.updatedType))
533
536
  {
@@ -542,8 +545,11 @@ module.exports = function(RED)
542
545
  {
543
546
  if((config.autoupdates && config.autoupdates == true) || typeof config.autoupdates == 'undefined')
544
547
  {
548
+ if(scope.firmwareUpdateTimeout !== null) { clearTimeout(scope.firmwareUpdateTimeout); };
549
+
545
550
  scope.log("Checking for Hue Bridge firmware updates…");
546
551
  API.request({
552
+ config: config,
547
553
  method: "PUT",
548
554
  resource: "/config",
549
555
  version: 1,
@@ -558,7 +564,7 @@ module.exports = function(RED)
558
564
  {
559
565
  if(scope.nodeActive == true)
560
566
  {
561
- setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 * 5);
567
+ scope.firmwareUpdateTimeout = setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 * 180);
562
568
  }
563
569
  })
564
570
  .catch(function(error)
@@ -566,7 +572,7 @@ module.exports = function(RED)
566
572
  // NO UPDATES AVAILABLE // TRY AGAIN IN 3H
567
573
  if(scope.nodeActive == true)
568
574
  {
569
- setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 * 180);
575
+ scope.firmwareUpdateTimeout = setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 * 180);
570
576
  }
571
577
  });
572
578
  }
@@ -584,10 +590,13 @@ module.exports = function(RED)
584
590
 
585
591
  // UNSUBSCRIBE FROM BRIDGE EVENTS
586
592
  scope.log("Unsubscribing from bridge events…");
587
- API.unsubscribe();
593
+ API.unsubscribe(config);
588
594
 
589
595
  // UNSUBSCRIBE FROM "READY" EVENTS
590
596
  scope.events.removeAllListeners();
597
+
598
+ // REMOVE FIRMWARE UPDATE TIMEOUT
599
+ if(scope.firmwareUpdateTimeout !== null) { clearTimeout(scope.firmwareUpdateTimeout); }
591
600
  });
592
601
  }
593
602
 
@@ -631,7 +640,7 @@ module.exports = function(RED)
631
640
  }
632
641
  else
633
642
  {
634
- API.init({ ip: req.query.ip })
643
+ API.init({ config: { bridge: req.query.ip, key: "huemagic" } })
635
644
  .then(function(bridge) {
636
645
  res.end(bridge.name);
637
646
  })
@@ -659,7 +668,7 @@ module.exports = function(RED)
659
668
  "Content-Type": "application/json; charset=utf-8"
660
669
  },
661
670
  "data": {
662
- "devicetype": "huemagic_" + Math.floor((Math.random() * 100) + 1)
671
+ "devicetype": "HueMagic for Node-RED (" + Math.floor((Math.random() * 100) + 1) + ")"
663
672
  }
664
673
  })
665
674
  .then(function(response)
@@ -689,10 +698,7 @@ module.exports = function(RED)
689
698
  // GET ALL RULES
690
699
  if(targetType == "rule")
691
700
  {
692
- API.init({ ip: req.query.bridge, key: req.query.key })
693
- .then(function(bridge) {
694
- return API.request({ resource: "/rules", version: 1 });
695
- })
701
+ API.request({ config: { bridge: req.query.bridge, key: req.query.key }, resource: "/rules", version: 1 })
696
702
  .then(function(rules)
697
703
  {
698
704
  let targetRules = {};
@@ -720,10 +726,7 @@ module.exports = function(RED)
720
726
  // GET ALL OTHER RESOURCES
721
727
  else
722
728
  {
723
- API.init({ ip: req.query.bridge, key: req.query.key })
724
- .then(function(bridge) {
725
- return API.request({ resource: "all" });
726
- })
729
+ API.request({ config: { bridge: req.query.bridge, key: req.query.key }, resource: "all" })
727
730
  .then(function(allResources)
728
731
  {
729
732
  return API.processResources(allResources);
@@ -5,68 +5,62 @@ const EventSource = require('eventsource');
5
5
 
6
6
  function API()
7
7
  {
8
- // STORAGE
9
- this.bridge = null;
10
- this.ipAddress = null;
11
- this.accessKey = null;
12
- this.events = null;
8
+ // EVENTS
9
+ this.events = {};
13
10
 
14
11
  //
15
12
  // INITIALIZE
16
- this.init = function({ ip, key = 'default' })
13
+ this.init = function({ config = null })
17
14
  {
18
15
  const scope = this;
19
16
 
20
- // FORCE RELOAD?
21
- const forceReload = (this.ipAddress != ip.toString().trim());
22
-
23
- // STORE ACCESS INFORMATION
24
- this.ipAddress = ip.toString().trim();
25
- this.accessKey = key.toString().trim();
26
-
27
17
  // GET BRIDGE
28
18
  return new Promise(function(resolve, reject)
29
19
  {
30
- if(scope.bridge !== null && forceReload === false)
20
+ if(!config)
31
21
  {
32
- resolve(scope.bridge);
22
+ reject("Bridge is not configured!");
23
+ return false;
33
24
  }
34
- else
25
+
26
+ // GET BRIDGE INFORMATION
27
+ axios({
28
+ "method": "GET",
29
+ "url": "https://" + config.bridge + "/api/config",
30
+ "headers": { "Content-Type": "application/json; charset=utf-8" },
31
+ "httpsAgent": new https.Agent({ rejectUnauthorized: false }),
32
+ })
33
+ .then(function(response)
35
34
  {
36
- // GET BRIDGE INFORMATION
37
- axios({
38
- "method": "GET",
39
- "url": "https://" + ip + "/api/config",
40
- "headers": { "Content-Type": "application/json; charset=utf-8" },
41
- "httpsAgent": new https.Agent({ rejectUnauthorized: false }),
42
- })
43
- .then(function(response)
44
- {
45
- scope.bridge = response.data;
46
- resolve(scope.bridge);
47
- })
48
- .catch(function(error)
49
- {
50
- reject(error);
51
- });
52
- }
35
+ resolve(response.data);
36
+ })
37
+ .catch(function(error)
38
+ {
39
+ reject(error);
40
+ });
53
41
  });
54
42
  }
55
43
 
56
44
  //
57
45
  // MAKE A REQUEST
58
- this.request = function({ method = 'GET', resource = null, data = null, version = 2 })
46
+ this.request = function({ config = null, method = 'GET', resource = null, data = null, version = 2 })
59
47
  {
60
48
  const scope = this;
61
49
  return new Promise(function(resolve, reject)
62
50
  {
51
+ if(!config)
52
+ {
53
+ reject("Bridge is not configured!");
54
+ return false;
55
+ }
56
+
63
57
  // BUILD REQUEST OBJECT
64
58
  var request = {
65
59
  "method": method,
66
- "url": "https://" + scope.ipAddress,
60
+ "url": "https://" + config.bridge,
67
61
  "headers": {
68
62
  "Content-Type": "application/json; charset=utf-8",
69
- "hue-application-key": scope.accessKey
63
+ "hue-application-key": config.key
70
64
  },
71
65
  "httpsAgent": new https.Agent({ rejectUnauthorized: false }), // Node is somehow not able to parse the official Philips Hue PEM
72
66
  };
@@ -81,7 +75,7 @@ function API()
81
75
  }
82
76
  else if(version === 1)
83
77
  {
84
- request['url'] += "/api/" + scope.accessKey + resource;
78
+ request['url'] += "/api/" + config.key + resource;
85
79
  }
86
80
  }
87
81
 
@@ -119,23 +113,23 @@ function API()
119
113
 
120
114
  //
121
115
  // SUBSCRIBE TO BRIDGE EVENTS
122
- this.subscribe = function(callback)
116
+ this.subscribe = function(config, callback)
123
117
  {
124
118
  const scope = this;
125
119
  return new Promise(function(resolve, reject)
126
120
  {
127
- if(scope.events === null)
121
+ if(!scope.events[config.id])
128
122
  {
129
- var sseURL = "https://" + scope.ipAddress + "/eventstream/clip/v2";
123
+ var sseURL = "https://" + config.bridge + "/eventstream/clip/v2";
130
124
 
131
125
  // INITIALIZE EVENT SOURCE
132
- scope.events = new EventSource(sseURL, {
133
- headers: { 'hue-application-key': scope.accessKey },
126
+ scope.events[config.id] = new EventSource(sseURL, {
127
+ headers: { 'hue-application-key': config.key },
134
128
  https: { rejectUnauthorized: false },
135
129
  });
136
130
 
137
131
  // PIPE MESSAGE TO TARGET FUNCTION
138
- scope.events.onmessage = function(event)
132
+ scope.events[config.id].onmessage = function(event)
139
133
  {
140
134
  if(event && event.type === 'message' && event.data)
141
135
  {
@@ -152,33 +146,33 @@ function API()
152
146
  };
153
147
 
154
148
  // CONNECTED?
155
- scope.events.onopen = function()
149
+ scope.events[config.id].onopen = function()
156
150
  {
157
151
  resolve(true);
158
152
  }
159
153
 
160
154
  // ERROR? -> RETRY?
161
- scope.events.onerror = function(error)
155
+ scope.events[config.id].onerror = function(error)
162
156
  {
163
157
  console.log("HueMagic:", "Connection to bridge lost. Trying to reconnect again in 30 seconds…", err);
164
- setTimeout(function(){ scope.subscribe(callback); }, 30000);
158
+ setTimeout(function(){ scope.subscribe(config, callback); }, 30000);
165
159
  resolve(true);
166
160
  }
167
161
  }
168
162
  else
169
163
  {
170
- scope.unsubscribe();
171
- scope.subscribe(callback);
164
+ scope.unsubscribe(config);
165
+ scope.subscribe(config, callback);
172
166
  }
173
167
  });
174
168
  }
175
169
 
176
170
  //
177
171
  // UNSUBSCRIBE
178
- this.unsubscribe = function()
172
+ this.unsubscribe = function(config)
179
173
  {
180
- if(this.events !== null) { this.events.close(); }
181
- this.events = null;
174
+ if(this.events[config.id] !== null) { this.events[config.id].close(); }
175
+ this.events[config.id] = null;
182
176
  }
183
177
 
184
178
  //
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-huemagic",
3
- "version": "4.0.3",
3
+ "version": "4.0.4",
4
4
  "description": "Philips Hue node to control bridges, lights, groups, scenes, rules, taps, switches, buttons, motion sensors, temperature sensors and Lux sensors using Node-RED.",
5
5
  "author": "foddy",
6
6
  "homepage": "https://github.com/Foddy/node-red-contrib-huemagic",