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.
- package/README.md +59 -55
- package/examples/Hue Bridge.json +1 -1
- package/huemagic/hue-bridge-config.html +4 -4
- package/huemagic/hue-bridge-config.js +149 -143
- package/huemagic/hue-bridge.js +5 -5
- package/huemagic/hue-brightness.html +7 -7
- package/huemagic/hue-brightness.js +11 -5
- package/huemagic/hue-buttons.html +7 -7
- package/huemagic/hue-buttons.js +11 -5
- package/huemagic/hue-group.html +11 -11
- package/huemagic/hue-group.js +3 -3
- package/huemagic/hue-light.html +7 -7
- package/huemagic/hue-light.js +3 -3
- package/huemagic/hue-motion.html +7 -7
- package/huemagic/hue-motion.js +11 -5
- package/huemagic/hue-rules.html +5 -5
- package/huemagic/hue-rules.js +10 -4
- package/huemagic/hue-scene.html +5 -5
- package/huemagic/hue-scene.js +1 -1
- package/huemagic/hue-temperature.html +7 -7
- package/huemagic/hue-temperature.js +11 -5
- package/huemagic/locales/de/hue-bridge.html +1 -1
- package/huemagic/locales/de/hue-bridge.json +1 -1
- package/huemagic/locales/de/hue-buttons.html +2 -2
- package/huemagic/locales/en-US/hue-bridge.html +2 -2
- package/huemagic/locales/en-US/hue-bridge.json +1 -1
- package/huemagic/locales/en-US/hue-buttons.html +3 -3
- package/huemagic/locales/en-US/hue-magic.html +2 -2
- package/huemagic/locales/en-US/hue-motion.html +1 -1
- package/huemagic/locales/en-US/hue-temperature.html +1 -1
- package/huemagic/utils/api.js +88 -94
- package/huemagic/utils/messages.js +124 -124
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"connected": "connected",
|
|
17
17
|
"starting-tl": "starting TouchLink…",
|
|
18
18
|
"started-tl": "TouchLink started…",
|
|
19
|
-
"f-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/huemagic/utils/api.js
CHANGED
|
@@ -5,83 +5,77 @@ const EventSource = require('eventsource');
|
|
|
5
5
|
|
|
6
6
|
function API()
|
|
7
7
|
{
|
|
8
|
-
//
|
|
9
|
-
this.
|
|
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({
|
|
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(
|
|
20
|
+
if(!config)
|
|
31
21
|
{
|
|
32
|
-
|
|
22
|
+
reject("Bridge is not configured!");
|
|
23
|
+
return false;
|
|
33
24
|
}
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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',
|
|
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://" +
|
|
60
|
+
"url": "https://" + config.bridge,
|
|
67
61
|
"headers": {
|
|
68
62
|
"Content-Type": "application/json; charset=utf-8",
|
|
69
|
-
"hue-application-key":
|
|
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
|
|
75
|
-
if(
|
|
68
|
+
// HAS RESOURCE? -> APPEND
|
|
69
|
+
if(resource !== null)
|
|
76
70
|
{
|
|
77
71
|
if(version === 2)
|
|
78
72
|
{
|
|
79
|
-
|
|
80
|
-
request['url'] += "/clip/v2/resource" +
|
|
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/" +
|
|
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
|
|
121
|
+
if(!scope.events[config.id])
|
|
128
122
|
{
|
|
129
|
-
var sseURL = "https://" +
|
|
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':
|
|
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
|
|
186
|
-
this.
|
|
179
|
+
// GET FULL/ROOT RESOURCE
|
|
180
|
+
this.fullResource = function(resource, allResources = {})
|
|
187
181
|
{
|
|
188
182
|
const scope = this;
|
|
189
|
-
var
|
|
183
|
+
var fullResource = Object.assign({}, resource);
|
|
190
184
|
|
|
191
|
-
if(
|
|
185
|
+
if(resource["owner"])
|
|
192
186
|
{
|
|
193
|
-
|
|
187
|
+
fullResource = scope.fullResource(allResources[fullResource["owner"]["rid"]], allResources);
|
|
194
188
|
}
|
|
195
|
-
else if(
|
|
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 =
|
|
194
|
+
for (var i = resource["services"].length - 1; i >= 0; i--)
|
|
201
195
|
{
|
|
202
|
-
const targetService =
|
|
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({},
|
|
201
|
+
allServices[targetType][targetID] = Object.assign({}, allResources[targetID]);
|
|
208
202
|
}
|
|
209
203
|
|
|
210
204
|
// REPLACE SERVICES
|
|
211
|
-
|
|
205
|
+
fullResource["services"] = allServices;
|
|
212
206
|
}
|
|
213
207
|
|
|
214
|
-
return
|
|
208
|
+
return fullResource;
|
|
215
209
|
}
|
|
216
210
|
|
|
217
211
|
//
|
|
218
|
-
// PROCESS
|
|
219
|
-
this.
|
|
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
|
|
230
|
-
let
|
|
223
|
+
let resourceList = {};
|
|
224
|
+
let processedResources = {
|
|
231
225
|
_groupsOf: {}
|
|
232
226
|
};
|
|
233
227
|
|
|
234
|
-
// CREATE ID BASED OBJECT OF ALL
|
|
235
|
-
|
|
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(
|
|
232
|
+
if(resource.type === "button")
|
|
239
233
|
{
|
|
240
|
-
delete
|
|
234
|
+
delete resource["button"];
|
|
241
235
|
}
|
|
242
236
|
|
|
243
|
-
|
|
237
|
+
resourceList[resource.id] = resource;
|
|
244
238
|
});
|
|
245
239
|
|
|
246
|
-
// GET FULL
|
|
247
|
-
|
|
240
|
+
// GET FULL RESOURCES OF EACH OBJECT
|
|
241
|
+
resources.forEach(function(resource, index)
|
|
248
242
|
{
|
|
249
|
-
// GET FULL
|
|
250
|
-
let
|
|
243
|
+
// GET FULL RESOURCE
|
|
244
|
+
let fullResource = scope.fullResource(resource, resourceList);
|
|
251
245
|
|
|
252
246
|
// ADD CURRENT DATE/TIME
|
|
253
|
-
|
|
247
|
+
fullResource["updated"] = currentDateTime;
|
|
254
248
|
|
|
255
|
-
// ALL ALL TYPES BEHIND
|
|
256
|
-
|
|
249
|
+
// ALL ALL TYPES BEHIND RESOURCE
|
|
250
|
+
fullResource["types"] = [ fullResource["type"] ];
|
|
257
251
|
|
|
258
|
-
//
|
|
259
|
-
if(
|
|
252
|
+
// RESOURCE HAS SERVICES?
|
|
253
|
+
if(fullResource["services"])
|
|
260
254
|
{
|
|
261
|
-
let additionalServiceTypes = Object.keys(
|
|
255
|
+
let additionalServiceTypes = Object.keys(fullResource["services"]);
|
|
262
256
|
|
|
263
257
|
// SET ADDITIONAL TYPES BEHIND RECCOURCE
|
|
264
|
-
|
|
258
|
+
fullResource["types"] = fullResource["types"].concat(additionalServiceTypes);
|
|
265
259
|
}
|
|
266
260
|
|
|
267
261
|
// RESOURCE HAS GROUPED SERVICES?
|
|
268
|
-
if(
|
|
262
|
+
if(fullResource["grouped_services"])
|
|
269
263
|
{
|
|
270
|
-
for (var g =
|
|
264
|
+
for (var g = fullResource["grouped_services"].length - 1; g >= 0; g--)
|
|
271
265
|
{
|
|
272
|
-
const groupedService =
|
|
266
|
+
const groupedService = fullResource["grouped_services"][g];
|
|
273
267
|
const groupedServiceID = groupedService["rid"];
|
|
274
268
|
|
|
275
|
-
if(!
|
|
276
|
-
|
|
269
|
+
if(!processedResources["_groupsOf"][groupedServiceID]) { processedResources["_groupsOf"][groupedServiceID] = []; }
|
|
270
|
+
processedResources["_groupsOf"][groupedServiceID].push(fullResource.id);
|
|
277
271
|
}
|
|
278
272
|
}
|
|
279
273
|
|
|
280
|
-
// GIVE FULL
|
|
281
|
-
|
|
274
|
+
// GIVE FULL RESOURCE BACK TO COLLECTION
|
|
275
|
+
processedResources[fullResource.id] = fullResource;
|
|
282
276
|
});
|
|
283
277
|
|
|
284
|
-
resolve(
|
|
278
|
+
resolve(processedResources);
|
|
285
279
|
});
|
|
286
280
|
}
|
|
287
281
|
}
|