node-red-contrib-huemagic 4.0.0 → 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.
@@ -16,7 +16,7 @@
16
16
  "connected": "connected",
17
17
  "starting-tl": "starting TouchLink…",
18
18
  "started-tl": "TouchLink started…",
19
- "f-ressources": "fetching ressources…",
19
+ "f-resources": "fetching resources…",
20
20
  "updating-settings": "updating settings…",
21
21
  "settings-updated": "settings updated",
22
22
  "input": "settings / fetch",
@@ -3,7 +3,7 @@ The "Hue Buttons" node receives switching events from input devices connected to
3
3
 
4
4
  ### Setup
5
5
 
6
- Select the pre-configured Hue Bridge and hit the search button to find all the available switches/buttons. If you already know the ID of the switch, you can also enter it here manually. You can either assign a new name for the switch internally or keep the predefined name of the device. Optionally, you can also deactivate all automatic status messages for this node by clicking the setting "Skip events from node". The node will then no longer output any switching events. Alternatively, you can also choose whether the node's initialization message should not be suppressed when Node-RED is started. If you activate this setting, you will receive a status message for the currently selected switch after each deployment.
6
+ Select the pre-configured Hue Bridge and hit the search button to find all the available switches/buttons. If you already know the ID of the switches/buttons, you can also enter it here manually. You can either assign a new name for the switches/buttons internally or keep the predefined name of the device. Optionally, you can also deactivate all automatic status messages for this node by clicking the setting "Skip events from node". The node will then no longer output any switching events. Alternatively, you can also choose whether the node's initialization message should not be suppressed when Node-RED is started. If you activate this setting, you will receive a status message for the currently selected switches/buttons after each deployment.
7
7
 
8
8
  If you do not select a switch/button and use the node configuration in this way, the node works in the so-called "universal mode". In this mode, the node receives and outputs all status messages of the same type.
9
9
 
@@ -34,8 +34,8 @@ As soon as a key has been pressed, the following status message is returned by t
34
34
  : name (string): The currently set name of the switch/button
35
35
  : type (string): The type of the switch/button (always `button`)
36
36
  : softwareVersion (string): The current firmware of the switch/button
37
- : battery (float): The current battery level of the switch/button
38
- : batteryState (string): The current status of the battery level. Can contain `normal`, `low` or `critical` as a value
37
+ : battery (float | boolean): The current battery level of the switch/button, `false`, if there is no battery
38
+ : batteryState (string | boolean): The current status of the battery level. Can contain `normal`, `low` or `critical` as a value, `false`, if there is no battery
39
39
  : model (object): Contains the model information of the switch/button under `id` , `manufacturer`, `name`, `type` and `certified`
40
40
 
41
41
  #### Status changes under `msg.updated`
@@ -22,11 +22,11 @@ Create an `array` with the respective animation steps in the form of an object a
22
22
  : delay (int): Number of milliseconds to wait until this step is carried out
23
23
  : animation (object): The object contains the action(s) to be carried out in this step. Identical parameters as for the "Hue Light" & "Hue Group" nodes
24
24
 
25
- ### Example of your own animation
25
+ ### Example of a custom animation
26
26
 
27
27
  This example shows what a simple animation could look like. In the first step, the delay of 500 milliseconds is waited for. The color of the light is then slowly changed to red over a period of one second. As soon as the light has completely changed to red, the second step is carried out, which also has a delay of 500 milliseconds. Finally the color changes to blue.
28
28
 
29
- If you have set the animation to play indefinitely, this process is repeated indefinitely until you manually stop the animation or redeploy the node.
29
+ If you have set the animation to loop, this process is repeated indefinitely until you manually stop the animation or redeploy the node.
30
30
 
31
31
  Pass the following object in `msg.payload` to play the example animation.
32
32
 
@@ -17,7 +17,7 @@ Outputs the current status of the sensor as soon as a `msg.payload` object with
17
17
 
18
18
  If necessary, the sensor can be turned on and off remotely. If the sensor has been turned off, it no longer registers any movements and accordingly no longer outputs them. To do this, pass an object with the following content:
19
19
 
20
- : payload (boolean): `true` turns the sensor on,` false` turns it off
20
+ : payload (boolean): `true` turns the sensor on, `false` turns it off
21
21
 
22
22
  ### Status messages from the node
23
23
 
@@ -30,7 +30,7 @@ As soon as the sensor has registered a temperature change, the following status
30
30
  : connectionStatus (string): The current connection status with the bridge in the form of a string. Can contain `connected`, `disconnected`, `connectivity_issue` or `unidirectional_incoming` as a value
31
31
  : celsius (float): Indicates the current ambient temperature in °C (degrees Celsius)
32
32
  : fahrenheit (float): Indicates the current ambient temperature in °F (degrees Fahrenheit)
33
- : temperatureIs (string): Describes the current temperature with the values ´very cold´, ´cold´, ´slightly cold´, ´comfortable´, ´slightly warm´, ´warm´, ´hot´ or ´very hot´
33
+ : temperatureIs (string): Describes the current temperature with the values `very cold`, `cold`, `slightly cold`, `comfortable`, `slightly warm`, `warm`, `hot` or `very hot`
34
34
  : deviceValue (float): The original value of the temperature from the sensor
35
35
  : updated (string): Time of the last update of the resource by HueMagic (ISO 8601)
36
36
 
@@ -5,83 +5,77 @@ 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', ressource = 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
  };
73
67
 
74
- // HAS RESSOURCE? -> APPEND
75
- if(ressource !== null)
68
+ // HAS RESOURCE? -> APPEND
69
+ if(resource !== null)
76
70
  {
77
71
  if(version === 2)
78
72
  {
79
- ressource = (ressource !== "all") ? "/"+ressource : "";
80
- request['url'] += "/clip/v2/resource" + ressource;
73
+ resource = (resource !== "all") ? "/"+resource : "";
74
+ request['url'] += "/clip/v2/resource" + resource;
81
75
  }
82
76
  else if(version === 1)
83
77
  {
84
- request['url'] += "/api/" + scope.accessKey + ressource;
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,71 +146,71 @@ 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
  //
185
- // GET FULL/ROOT RESSOURCE
186
- this.fullRessource = function(ressource, allRessources = {})
179
+ // GET FULL/ROOT RESOURCE
180
+ this.fullResource = function(resource, allResources = {})
187
181
  {
188
182
  const scope = this;
189
- var fullRessource = Object.assign({}, ressource);
183
+ var fullResource = Object.assign({}, resource);
190
184
 
191
- if(ressource["owner"])
185
+ if(resource["owner"])
192
186
  {
193
- fullRessource = scope.fullRessource(allRessources[fullRessource["owner"]["rid"]], allRessources);
187
+ fullResource = scope.fullResource(allResources[fullResource["owner"]["rid"]], allResources);
194
188
  }
195
- else if(ressource["type"] === "device" || ressource["type"] === "room" || ressource["type"] === "zone" || ressource["type"] === "bridge_home")
189
+ else if(resource["type"] === "device" || resource["type"] === "room" || resource["type"] === "zone" || resource["type"] === "bridge_home")
196
190
  {
197
191
  // RESOLVE SERVICES
198
192
  var allServices = {};
199
193
 
200
- for (var i = ressource["services"].length - 1; i >= 0; i--)
194
+ for (var i = resource["services"].length - 1; i >= 0; i--)
201
195
  {
202
- const targetService = ressource["services"][i];
196
+ const targetService = resource["services"][i];
203
197
  const targetType = targetService["rtype"];
204
198
  const targetID = targetService["rid"];
205
199
 
206
200
  if(!allServices[targetType]) { allServices[targetType] = {}; }
207
- allServices[targetType][targetID] = Object.assign({}, allRessources[targetID]);
201
+ allServices[targetType][targetID] = Object.assign({}, allResources[targetID]);
208
202
  }
209
203
 
210
204
  // REPLACE SERVICES
211
- fullRessource["services"] = allServices;
205
+ fullResource["services"] = allServices;
212
206
  }
213
207
 
214
- return fullRessource;
208
+ return fullResource;
215
209
  }
216
210
 
217
211
  //
218
- // PROCESS RESSOURCES
219
- this.processRessources = function(ressources)
212
+ // PROCESS RESOURCES
213
+ this.processResources = function(resources)
220
214
  {
221
215
  const scope = this;
222
216
 
@@ -226,62 +220,62 @@ function API()
226
220
  // ACTION!
227
221
  return new Promise(function(resolve, reject)
228
222
  {
229
- let ressourceList = {};
230
- let processedRessources = {
223
+ let resourceList = {};
224
+ let processedResources = {
231
225
  _groupsOf: {}
232
226
  };
233
227
 
234
- // CREATE ID BASED OBJECT OF ALL RESSOURCES
235
- ressources.forEach(function(ressource, index)
228
+ // CREATE ID BASED OBJECT OF ALL RESOURCES
229
+ resources.forEach(function(resource, index)
236
230
  {
237
231
  // IS BUTTON? -> REMOVE PREVIOUS STATE
238
- if(ressource.type === "button")
232
+ if(resource.type === "button")
239
233
  {
240
- delete ressource["button"];
234
+ delete resource["button"];
241
235
  }
242
236
 
243
- ressourceList[ressource.id] = ressource;
237
+ resourceList[resource.id] = resource;
244
238
  });
245
239
 
246
- // GET FULL RESSOURCES OF EACH OBJECT
247
- ressources.forEach(function(ressource, index)
240
+ // GET FULL RESOURCES OF EACH OBJECT
241
+ resources.forEach(function(resource, index)
248
242
  {
249
- // GET FULL RESSOURCE
250
- let fullRessource = scope.fullRessource(ressource, ressourceList);
243
+ // GET FULL RESOURCE
244
+ let fullResource = scope.fullResource(resource, resourceList);
251
245
 
252
246
  // ADD CURRENT DATE/TIME
253
- fullRessource["updated"] = currentDateTime;
247
+ fullResource["updated"] = currentDateTime;
254
248
 
255
- // ALL ALL TYPES BEHIND RESSOURCE
256
- fullRessource["types"] = [ fullRessource["type"] ];
249
+ // ALL ALL TYPES BEHIND RESOURCE
250
+ fullResource["types"] = [ fullResource["type"] ];
257
251
 
258
- // RESSOURCE HAS SERVICES?
259
- if(fullRessource["services"])
252
+ // RESOURCE HAS SERVICES?
253
+ if(fullResource["services"])
260
254
  {
261
- let additionalServiceTypes = Object.keys(fullRessource["services"]);
255
+ let additionalServiceTypes = Object.keys(fullResource["services"]);
262
256
 
263
257
  // SET ADDITIONAL TYPES BEHIND RECCOURCE
264
- fullRessource["types"] = fullRessource["types"].concat(additionalServiceTypes);
258
+ fullResource["types"] = fullResource["types"].concat(additionalServiceTypes);
265
259
  }
266
260
 
267
261
  // RESOURCE HAS GROUPED SERVICES?
268
- if(fullRessource["grouped_services"])
262
+ if(fullResource["grouped_services"])
269
263
  {
270
- for (var g = fullRessource["grouped_services"].length - 1; g >= 0; g--)
264
+ for (var g = fullResource["grouped_services"].length - 1; g >= 0; g--)
271
265
  {
272
- const groupedService = fullRessource["grouped_services"][g];
266
+ const groupedService = fullResource["grouped_services"][g];
273
267
  const groupedServiceID = groupedService["rid"];
274
268
 
275
- if(!processedRessources["_groupsOf"][groupedServiceID]) { processedRessources["_groupsOf"][groupedServiceID] = []; }
276
- processedRessources["_groupsOf"][groupedServiceID].push(fullRessource.id);
269
+ if(!processedResources["_groupsOf"][groupedServiceID]) { processedResources["_groupsOf"][groupedServiceID] = []; }
270
+ processedResources["_groupsOf"][groupedServiceID].push(fullResource.id);
277
271
  }
278
272
  }
279
273
 
280
- // GIVE FULL RESSOURCE BACK TO COLLECTION
281
- processedRessources[fullRessource.id] = fullRessource;
274
+ // GIVE FULL RESOURCE BACK TO COLLECTION
275
+ processedResources[fullResource.id] = fullResource;
282
276
  });
283
277
 
284
- resolve(processedRessources);
278
+ resolve(processedResources);
285
279
  });
286
280
  }
287
281
  }