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
|
@@ -10,7 +10,7 @@ module.exports = function(RED)
|
|
|
10
10
|
const axios = require('axios');
|
|
11
11
|
const https = require('https');
|
|
12
12
|
|
|
13
|
-
// READABLE
|
|
13
|
+
// READABLE RESOURCE MESSAGES
|
|
14
14
|
const { HueBridgeMessage,
|
|
15
15
|
HueLightMessage,
|
|
16
16
|
HueGroupMessage,
|
|
@@ -28,35 +28,41 @@ module.exports = function(RED)
|
|
|
28
28
|
// STATES
|
|
29
29
|
this.nodeActive = true;
|
|
30
30
|
this.config = config;
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
31
|
+
this.resources = {};
|
|
32
|
+
this.resourcesInGroups = {};
|
|
33
33
|
this.lastStates = {};
|
|
34
34
|
this.events = new events.EventEmitter();
|
|
35
35
|
|
|
36
|
+
// RESOURCE ID PATTERN
|
|
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
|
+
|
|
39
|
+
// FIRMWARE UPDATE TIMEOUT
|
|
40
|
+
this.firmwareUpdateTimeout = null;
|
|
41
|
+
|
|
36
42
|
// CREATE NODE
|
|
37
43
|
RED.nodes.createNode(scope, config);
|
|
38
44
|
|
|
39
45
|
// INITIALIZE
|
|
40
46
|
this.start = function()
|
|
41
47
|
{
|
|
42
|
-
scope.log("Initializing the bridge…");
|
|
43
|
-
API.init({
|
|
48
|
+
scope.log("Initializing the bridge ("+config.bridge+")…");
|
|
49
|
+
API.init({ config: config })
|
|
44
50
|
.then(function(bridge) {
|
|
45
51
|
scope.log("Connected to bridge");
|
|
46
|
-
return scope.
|
|
52
|
+
return scope.getAllResources();
|
|
47
53
|
})
|
|
48
|
-
.then(function(
|
|
54
|
+
.then(function(allResources)
|
|
49
55
|
{
|
|
50
|
-
scope.log("
|
|
51
|
-
return API.
|
|
56
|
+
scope.log("Processing bridge resources…");
|
|
57
|
+
return API.processResources(allResources);
|
|
52
58
|
})
|
|
53
|
-
.then(function(
|
|
59
|
+
.then(function(allResources)
|
|
54
60
|
{
|
|
55
|
-
// SAVE CURRENT
|
|
56
|
-
scope.
|
|
61
|
+
// SAVE CURRENT RESOURCES
|
|
62
|
+
scope.resources = allResources;
|
|
57
63
|
|
|
58
64
|
// EMIT INITIAL STATES -> NODES
|
|
59
|
-
scope.log("Initial emit of
|
|
65
|
+
scope.log("Initial emit of resource states…");
|
|
60
66
|
return scope.emitInitialStates();
|
|
61
67
|
})
|
|
62
68
|
.then(function(emitted)
|
|
@@ -77,22 +83,22 @@ module.exports = function(RED)
|
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
// FETCH BRIDGE INFORMATION
|
|
80
|
-
this.getBridgeInformation = function(
|
|
86
|
+
this.getBridgeInformation = function(replaceResources = false)
|
|
81
87
|
{
|
|
82
88
|
return new Promise(function(resolve, reject)
|
|
83
89
|
{
|
|
84
|
-
API.request({
|
|
90
|
+
API.request({ config: config, resource: "/config", version: 1 })
|
|
85
91
|
.then(function(bridgeInformation)
|
|
86
92
|
{
|
|
87
|
-
// PREPARE TO MATCH V2
|
|
93
|
+
// PREPARE TO MATCH V2 RESOURCES
|
|
88
94
|
bridgeInformation.id = "bridge";
|
|
89
95
|
bridgeInformation.id_v1 = "/config";
|
|
90
96
|
bridgeInformation.updated = dayjs().format();
|
|
91
97
|
|
|
92
|
-
// ALSO REPLACE CURRENT
|
|
93
|
-
if(
|
|
98
|
+
// ALSO REPLACE CURRENT RESOURCE?
|
|
99
|
+
if(replaceResources === true)
|
|
94
100
|
{
|
|
95
|
-
scope.
|
|
101
|
+
scope.resources[bridgeInformation.id] = bridgeInformation;
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
// GIVE BACK
|
|
@@ -105,30 +111,30 @@ module.exports = function(RED)
|
|
|
105
111
|
});
|
|
106
112
|
}
|
|
107
113
|
|
|
108
|
-
// GET ALL
|
|
109
|
-
this.
|
|
114
|
+
// GET ALL RESOURCES + RULES
|
|
115
|
+
this.getAllResources = function()
|
|
110
116
|
{
|
|
111
117
|
return new Promise(function(resolve, reject)
|
|
112
118
|
{
|
|
113
|
-
var
|
|
119
|
+
var allResources = [];
|
|
114
120
|
|
|
115
121
|
// GET BRIDGE INFORMATION
|
|
116
122
|
scope.getBridgeInformation()
|
|
117
123
|
.then(function(bridgeInformation)
|
|
118
124
|
{
|
|
119
|
-
// PUSH TO
|
|
120
|
-
|
|
125
|
+
// PUSH TO RESOURCES
|
|
126
|
+
allResources.push(bridgeInformation);
|
|
121
127
|
|
|
122
|
-
// CONTINUE WITH ALL
|
|
123
|
-
return API.request({
|
|
128
|
+
// CONTINUE WITH ALL RESOURCES
|
|
129
|
+
return API.request({ config: config, resource: "all" });
|
|
124
130
|
})
|
|
125
|
-
.then(function(
|
|
131
|
+
.then(function(v2Resources)
|
|
126
132
|
{
|
|
127
|
-
// MERGE
|
|
128
|
-
|
|
133
|
+
// MERGE RESOURCES
|
|
134
|
+
allResources = allResources.concat(v2Resources);
|
|
129
135
|
|
|
130
136
|
// GET RULES (LEGACY API)
|
|
131
|
-
return API.request({
|
|
137
|
+
return API.request({ config: config, resource: "/rules", version: 1 });
|
|
132
138
|
})
|
|
133
139
|
.then(function(rules)
|
|
134
140
|
{
|
|
@@ -146,17 +152,17 @@ module.exports = function(RED)
|
|
|
146
152
|
rule["type"] = "rule";
|
|
147
153
|
|
|
148
154
|
// PUSH RULES
|
|
149
|
-
|
|
155
|
+
allResources.push(rule);
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
resolve(
|
|
158
|
+
resolve(allResources);
|
|
153
159
|
})
|
|
154
160
|
.catch(function(error) { reject(error); });
|
|
155
161
|
});
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
// EMIT INITIAL STATES -> NODES
|
|
159
|
-
this.emitInitialStates = function(
|
|
165
|
+
this.emitInitialStates = function(resources = false)
|
|
160
166
|
{
|
|
161
167
|
return new Promise(function(resolve, reject)
|
|
162
168
|
{
|
|
@@ -164,9 +170,9 @@ module.exports = function(RED)
|
|
|
164
170
|
setTimeout(function()
|
|
165
171
|
{
|
|
166
172
|
// PUSH ALL STATES
|
|
167
|
-
for (const [id,
|
|
173
|
+
for (const [id, resource] of Object.entries(scope.resources))
|
|
168
174
|
{
|
|
169
|
-
scope.pushUpdatedState(
|
|
175
|
+
scope.pushUpdatedState(resource, resource.type, true);
|
|
170
176
|
}
|
|
171
177
|
|
|
172
178
|
resolve(true);
|
|
@@ -190,69 +196,69 @@ module.exports = function(RED)
|
|
|
190
196
|
this.refreshStatesSSE = function()
|
|
191
197
|
{
|
|
192
198
|
scope.log("Subscribing to bridge events…");
|
|
193
|
-
API.subscribe(function(updates)
|
|
199
|
+
API.subscribe(config, function(updates)
|
|
194
200
|
{
|
|
195
201
|
const currentDateTime = dayjs().format();
|
|
196
202
|
|
|
197
|
-
for(let
|
|
203
|
+
for(let resource of updates)
|
|
198
204
|
{
|
|
199
|
-
let id =
|
|
200
|
-
let type =
|
|
205
|
+
let id = resource.id;
|
|
206
|
+
let type = resource.type;
|
|
201
207
|
|
|
202
208
|
let previousState = false;
|
|
203
209
|
|
|
204
210
|
// HAS OWNER?
|
|
205
|
-
if(
|
|
211
|
+
if(resource["owner"])
|
|
206
212
|
{
|
|
207
|
-
let targetId =
|
|
213
|
+
let targetId = resource["owner"]["rid"];
|
|
208
214
|
|
|
209
|
-
if(scope.
|
|
215
|
+
if(scope.resources[targetId])
|
|
210
216
|
{
|
|
211
217
|
// GET PREVIOUS STATE
|
|
212
|
-
previousState = scope.
|
|
218
|
+
previousState = scope.resources[targetId]["services"][type][id];
|
|
213
219
|
|
|
214
220
|
// IS BUTTON? -> REMOVE PREVIOUS STATES
|
|
215
221
|
if(type === "button")
|
|
216
222
|
{
|
|
217
|
-
for (const [oneButtonID, oneButton] of Object.entries(scope.
|
|
223
|
+
for (const [oneButtonID, oneButton] of Object.entries(scope.resources[targetId]["services"]["button"]))
|
|
218
224
|
{
|
|
219
|
-
delete scope.
|
|
225
|
+
delete scope.resources[targetId]["services"]["button"][oneButtonID]["button"];
|
|
220
226
|
}
|
|
221
227
|
}
|
|
222
228
|
}
|
|
223
229
|
}
|
|
224
|
-
else if(scope.
|
|
230
|
+
else if(scope.resources[id])
|
|
225
231
|
{
|
|
226
232
|
// GET PREVIOUS STATE
|
|
227
|
-
previousState = scope.
|
|
233
|
+
previousState = scope.resources[id];
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
// NO PREVIOUS STATE?
|
|
231
237
|
if(!previousState) { return false; }
|
|
232
238
|
|
|
233
239
|
// CHECK DIFFERENCES
|
|
234
|
-
const mergedState = merge.deep(previousState,
|
|
235
|
-
const
|
|
240
|
+
const mergedState = merge.deep(previousState, resource);
|
|
241
|
+
const updatedResources = diff(previousState, mergedState);
|
|
236
242
|
|
|
237
|
-
if(Object.values(
|
|
243
|
+
if(Object.values(updatedResources).length > 0)
|
|
238
244
|
{
|
|
239
|
-
if(
|
|
245
|
+
if(resource["owner"])
|
|
240
246
|
{
|
|
241
|
-
let targetId =
|
|
247
|
+
let targetId = resource["owner"]["rid"];
|
|
242
248
|
|
|
243
|
-
scope.
|
|
244
|
-
scope.
|
|
249
|
+
scope.resources[targetId]["services"][type][id] = mergedState;
|
|
250
|
+
scope.resources[targetId]["updated"] = currentDateTime;
|
|
245
251
|
|
|
246
252
|
// PUSH STATE
|
|
247
|
-
scope.pushUpdatedState(scope.
|
|
253
|
+
scope.pushUpdatedState(scope.resources[targetId], resource.type);
|
|
248
254
|
}
|
|
249
255
|
else
|
|
250
256
|
{
|
|
251
|
-
scope.
|
|
252
|
-
scope.
|
|
257
|
+
scope.resources[id] = mergedState;
|
|
258
|
+
scope.resources[id]["updated"] = currentDateTime;
|
|
253
259
|
|
|
254
260
|
// PUSH STATE
|
|
255
|
-
scope.pushUpdatedState(scope.
|
|
261
|
+
scope.pushUpdatedState(scope.resources[id], resource.type);
|
|
256
262
|
}
|
|
257
263
|
}
|
|
258
264
|
}
|
|
@@ -260,42 +266,42 @@ module.exports = function(RED)
|
|
|
260
266
|
}
|
|
261
267
|
|
|
262
268
|
// PUSH UPDATED STATE
|
|
263
|
-
this.pushUpdatedState = function(
|
|
269
|
+
this.pushUpdatedState = function(resource, updatedType, suppressMessage = false)
|
|
264
270
|
{
|
|
265
|
-
const msg = { id:
|
|
266
|
-
this.events.emit(
|
|
267
|
-
this.events.emit("
|
|
271
|
+
const msg = { id: resource.id, type: resource.type, updatedType: updatedType, services: resource["services"] ? Object.keys(resource["services"]) : [], suppressMessage: suppressMessage };
|
|
272
|
+
this.events.emit(config.id + "_" + resource.id, msg);
|
|
273
|
+
this.events.emit(config.id + "_" + "globalResourceUpdates", msg);
|
|
268
274
|
|
|
269
|
-
//
|
|
270
|
-
if(this.
|
|
275
|
+
// RESOURCE CONTAINS SERVICES? -> SERVICE IN GROUP? -> EMIT CHANGES TO GROUPS ALSO
|
|
276
|
+
if(this.resources["_groupsOf"][resource.id])
|
|
271
277
|
{
|
|
272
|
-
for (var g = this.
|
|
278
|
+
for (var g = this.resources["_groupsOf"][resource.id].length - 1; g >= 0; g--)
|
|
273
279
|
{
|
|
274
|
-
const groupID = this.
|
|
280
|
+
const groupID = this.resources["_groupsOf"][resource.id][g];
|
|
275
281
|
const groupMessage = { id: groupID, type: "group", updatedType: updatedType, services: [], suppressMessage: suppressMessage };
|
|
276
282
|
|
|
277
|
-
this.events.emit(groupID, groupMessage);
|
|
278
|
-
this.events.emit("
|
|
283
|
+
this.events.emit(config.id + "_" + groupID, groupMessage);
|
|
284
|
+
this.events.emit(config.id + "_" + "globalResourceUpdates", groupMessage);
|
|
279
285
|
}
|
|
280
286
|
}
|
|
281
287
|
}
|
|
282
288
|
|
|
283
|
-
// GET
|
|
289
|
+
// GET RESOURCE (FROM NODES)
|
|
284
290
|
this.get = function(type, id = false, options = {})
|
|
285
291
|
{
|
|
286
|
-
// GET SPECIFIC
|
|
292
|
+
// GET SPECIFIC RESOURCE
|
|
287
293
|
if(id)
|
|
288
294
|
{
|
|
289
|
-
//
|
|
290
|
-
if(scope.
|
|
295
|
+
// RESOURCE EXISTS? -> PROCEED
|
|
296
|
+
if(scope.resources[id])
|
|
291
297
|
{
|
|
292
298
|
// RESOLVE LINKS
|
|
293
|
-
const
|
|
294
|
-
const lastState = scope.lastStates[type+
|
|
299
|
+
const targetResource = scope.resources[id];
|
|
300
|
+
const lastState = scope.lastStates[type+targetResource.id] ? Object.assign({}, scope.lastStates[type+targetResource.id]) : false;
|
|
295
301
|
|
|
296
302
|
if(type == "bridge")
|
|
297
303
|
{
|
|
298
|
-
const message = new HueBridgeMessage(
|
|
304
|
+
const message = new HueBridgeMessage(targetResource, options);
|
|
299
305
|
|
|
300
306
|
// GET CURRENT STATE MESSAGE
|
|
301
307
|
let currentState = message.msg;
|
|
@@ -303,11 +309,11 @@ module.exports = function(RED)
|
|
|
303
309
|
}
|
|
304
310
|
else if(type == "light")
|
|
305
311
|
{
|
|
306
|
-
const message = new HueLightMessage(
|
|
312
|
+
const message = new HueLightMessage(targetResource, options);
|
|
307
313
|
|
|
308
314
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
309
315
|
let currentState = message.msg;
|
|
310
|
-
scope.lastStates[type+
|
|
316
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
311
317
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
312
318
|
currentState.lastState = lastState;
|
|
313
319
|
|
|
@@ -316,11 +322,11 @@ module.exports = function(RED)
|
|
|
316
322
|
else if(type == "group")
|
|
317
323
|
{
|
|
318
324
|
// GET MESSAGE
|
|
319
|
-
const message = new HueGroupMessage(
|
|
325
|
+
const message = new HueGroupMessage(targetResource, { resources: scope.resources, ...options});
|
|
320
326
|
|
|
321
327
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
322
328
|
let currentState = message.msg;
|
|
323
|
-
scope.lastStates[type+
|
|
329
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
324
330
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
325
331
|
currentState.lastState = lastState;
|
|
326
332
|
|
|
@@ -328,11 +334,11 @@ module.exports = function(RED)
|
|
|
328
334
|
}
|
|
329
335
|
else if(type == "button")
|
|
330
336
|
{
|
|
331
|
-
const message = new HueButtonsMessage(
|
|
337
|
+
const message = new HueButtonsMessage(targetResource, options);
|
|
332
338
|
|
|
333
339
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
334
340
|
let currentState = message.msg;
|
|
335
|
-
scope.lastStates[type+
|
|
341
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
336
342
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
337
343
|
currentState.lastState = lastState;
|
|
338
344
|
|
|
@@ -340,11 +346,11 @@ module.exports = function(RED)
|
|
|
340
346
|
}
|
|
341
347
|
else if(type == "motion")
|
|
342
348
|
{
|
|
343
|
-
const message = new HueMotionMessage(
|
|
349
|
+
const message = new HueMotionMessage(targetResource, options);
|
|
344
350
|
|
|
345
351
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
346
352
|
let currentState = message.msg;
|
|
347
|
-
scope.lastStates[type+
|
|
353
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
348
354
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
349
355
|
currentState.lastState = lastState;
|
|
350
356
|
|
|
@@ -352,11 +358,11 @@ module.exports = function(RED)
|
|
|
352
358
|
}
|
|
353
359
|
else if(type == "temperature")
|
|
354
360
|
{
|
|
355
|
-
const message = new HueTemperatureMessage(
|
|
361
|
+
const message = new HueTemperatureMessage(targetResource, options);
|
|
356
362
|
|
|
357
363
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
358
364
|
let currentState = message.msg;
|
|
359
|
-
scope.lastStates[type+
|
|
365
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
360
366
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
361
367
|
currentState.lastState = lastState;
|
|
362
368
|
|
|
@@ -364,11 +370,11 @@ module.exports = function(RED)
|
|
|
364
370
|
}
|
|
365
371
|
else if(type == "light_level")
|
|
366
372
|
{
|
|
367
|
-
const message = new HueBrightnessMessage(
|
|
373
|
+
const message = new HueBrightnessMessage(targetResource, options);
|
|
368
374
|
|
|
369
375
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
370
376
|
let currentState = message.msg;
|
|
371
|
-
scope.lastStates[type+
|
|
377
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
372
378
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
373
379
|
currentState.lastState = lastState;
|
|
374
380
|
|
|
@@ -376,11 +382,11 @@ module.exports = function(RED)
|
|
|
376
382
|
}
|
|
377
383
|
else if(type == "rule")
|
|
378
384
|
{
|
|
379
|
-
const message = new HueRulesMessage(
|
|
385
|
+
const message = new HueRulesMessage(targetResource, options);
|
|
380
386
|
|
|
381
387
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
382
388
|
let currentState = message.msg;
|
|
383
|
-
scope.lastStates[type+
|
|
389
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
384
390
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
385
391
|
currentState.lastState = lastState;
|
|
386
392
|
|
|
@@ -398,46 +404,46 @@ module.exports = function(RED)
|
|
|
398
404
|
}
|
|
399
405
|
else
|
|
400
406
|
{
|
|
401
|
-
// FILTER
|
|
402
|
-
let
|
|
407
|
+
// FILTER RESOURCES BY TYPE
|
|
408
|
+
let allFilteredResources = {};
|
|
403
409
|
|
|
404
|
-
for (const [rootID,
|
|
410
|
+
for (const [rootID, resource] of Object.entries(scope.resources))
|
|
405
411
|
{
|
|
406
|
-
const isGroup = (
|
|
412
|
+
const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
|
|
407
413
|
|
|
408
414
|
// NORMAL DEVICES
|
|
409
|
-
if(!isGroup &&
|
|
415
|
+
if(!isGroup && resource["services"] && resource["services"][type])
|
|
410
416
|
{
|
|
411
|
-
for (const [serviceID, targetDevice] of Object.entries(
|
|
417
|
+
for (const [serviceID, targetDevice] of Object.entries(resource["services"][type]))
|
|
412
418
|
{
|
|
413
|
-
|
|
419
|
+
allFilteredResources[rootID] = scope.get(type, rootID);
|
|
414
420
|
}
|
|
415
421
|
}
|
|
416
|
-
// GROUPED
|
|
422
|
+
// GROUPED RESOURCES
|
|
417
423
|
else if(isGroup && type === "group")
|
|
418
424
|
{
|
|
419
|
-
|
|
425
|
+
allFilteredResources[rootID] = scope.get(type, rootID);
|
|
420
426
|
}
|
|
421
427
|
}
|
|
422
428
|
|
|
423
|
-
return Object.values(
|
|
429
|
+
return Object.values(allFilteredResources);
|
|
424
430
|
}
|
|
425
431
|
}
|
|
426
432
|
|
|
427
|
-
// PATCH
|
|
433
|
+
// PATCH RESOURCE (FROM NODES)
|
|
428
434
|
this.patch = function(type, id, patch, version = 2)
|
|
429
435
|
{
|
|
430
436
|
return new Promise(function(resolve, reject)
|
|
431
437
|
{
|
|
432
438
|
// GET SERVICE ID
|
|
433
|
-
if(version !== 1 && scope.
|
|
439
|
+
if(version !== 1 && scope.resources[id] && scope.resources[id]["services"] && scope.resources[id]["services"][type])
|
|
434
440
|
{
|
|
435
|
-
const
|
|
436
|
-
id =
|
|
441
|
+
const targetResource = Object.values(scope.resources[id]["services"][type])[0];
|
|
442
|
+
id = targetResource.id;
|
|
437
443
|
}
|
|
438
444
|
|
|
439
445
|
// ACTION!
|
|
440
|
-
API.request({ method: "PUT",
|
|
446
|
+
API.request({ config: config, method: "PUT", resource: (version === 2) ? (type+"/"+id) : id, data: patch, version: version })
|
|
441
447
|
.then(function(response) {
|
|
442
448
|
resolve(response);
|
|
443
449
|
})
|
|
@@ -452,7 +458,7 @@ module.exports = function(RED)
|
|
|
452
458
|
{
|
|
453
459
|
return new Promise(function(resolve, reject)
|
|
454
460
|
{
|
|
455
|
-
API.request({
|
|
461
|
+
API.request({ config: config, resource: "/rules/" + id, version: 1 })
|
|
456
462
|
.then(function(rule)
|
|
457
463
|
{
|
|
458
464
|
// "RENAME" OWNER
|
|
@@ -469,8 +475,8 @@ module.exports = function(RED)
|
|
|
469
475
|
// UPDATED TIME
|
|
470
476
|
rule["updated"] = dayjs().format();
|
|
471
477
|
|
|
472
|
-
// ADD BACK TO
|
|
473
|
-
scope.
|
|
478
|
+
// ADD BACK TO RESOURCES
|
|
479
|
+
scope.resources[rule["id"]] = rule;
|
|
474
480
|
|
|
475
481
|
// PUSH UPDATED STATE
|
|
476
482
|
scope.pushUpdatedState(rule, "rule");
|
|
@@ -505,7 +511,7 @@ module.exports = function(RED)
|
|
|
505
511
|
if(!id)
|
|
506
512
|
{
|
|
507
513
|
// UNIVERSAL MODE
|
|
508
|
-
this.events.on("
|
|
514
|
+
this.events.on(config.id + "_" + "globalResourceUpdates", function(info)
|
|
509
515
|
{
|
|
510
516
|
if(type === "bridge")
|
|
511
517
|
{
|
|
@@ -523,8 +529,8 @@ module.exports = function(RED)
|
|
|
523
529
|
}
|
|
524
530
|
else
|
|
525
531
|
{
|
|
526
|
-
// SPECIFIC
|
|
527
|
-
this.events.on(id, function(info)
|
|
532
|
+
// SPECIFIC RESOURCE MODE
|
|
533
|
+
this.events.on(config.id + "_" + id, function(info)
|
|
528
534
|
{
|
|
529
535
|
if(type === "bridge" || messageWhitelist[type].includes(info.updatedType))
|
|
530
536
|
{
|
|
@@ -539,10 +545,13 @@ module.exports = function(RED)
|
|
|
539
545
|
{
|
|
540
546
|
if((config.autoupdates && config.autoupdates == true) || typeof config.autoupdates == 'undefined')
|
|
541
547
|
{
|
|
548
|
+
if(scope.firmwareUpdateTimeout !== null) { clearTimeout(scope.firmwareUpdateTimeout); };
|
|
549
|
+
|
|
542
550
|
scope.log("Checking for Hue Bridge firmware updates…");
|
|
543
551
|
API.request({
|
|
552
|
+
config: config,
|
|
544
553
|
method: "PUT",
|
|
545
|
-
|
|
554
|
+
resource: "/config",
|
|
546
555
|
version: 1,
|
|
547
556
|
data: {
|
|
548
557
|
swupdate2: {
|
|
@@ -555,7 +564,7 @@ module.exports = function(RED)
|
|
|
555
564
|
{
|
|
556
565
|
if(scope.nodeActive == true)
|
|
557
566
|
{
|
|
558
|
-
setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 *
|
|
567
|
+
scope.firmwareUpdateTimeout = setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 * 180);
|
|
559
568
|
}
|
|
560
569
|
})
|
|
561
570
|
.catch(function(error)
|
|
@@ -563,7 +572,7 @@ module.exports = function(RED)
|
|
|
563
572
|
// NO UPDATES AVAILABLE // TRY AGAIN IN 3H
|
|
564
573
|
if(scope.nodeActive == true)
|
|
565
574
|
{
|
|
566
|
-
setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 * 180);
|
|
575
|
+
scope.firmwareUpdateTimeout = setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 * 180);
|
|
567
576
|
}
|
|
568
577
|
});
|
|
569
578
|
}
|
|
@@ -581,10 +590,13 @@ module.exports = function(RED)
|
|
|
581
590
|
|
|
582
591
|
// UNSUBSCRIBE FROM BRIDGE EVENTS
|
|
583
592
|
scope.log("Unsubscribing from bridge events…");
|
|
584
|
-
API.unsubscribe();
|
|
593
|
+
API.unsubscribe(config);
|
|
585
594
|
|
|
586
595
|
// UNSUBSCRIBE FROM "READY" EVENTS
|
|
587
596
|
scope.events.removeAllListeners();
|
|
597
|
+
|
|
598
|
+
// REMOVE FIRMWARE UPDATE TIMEOUT
|
|
599
|
+
if(scope.firmwareUpdateTimeout !== null) { clearTimeout(scope.firmwareUpdateTimeout); }
|
|
588
600
|
});
|
|
589
601
|
}
|
|
590
602
|
|
|
@@ -628,7 +640,7 @@ module.exports = function(RED)
|
|
|
628
640
|
}
|
|
629
641
|
else
|
|
630
642
|
{
|
|
631
|
-
API.init({
|
|
643
|
+
API.init({ config: { bridge: req.query.ip, key: "huemagic" } })
|
|
632
644
|
.then(function(bridge) {
|
|
633
645
|
res.end(bridge.name);
|
|
634
646
|
})
|
|
@@ -656,7 +668,7 @@ module.exports = function(RED)
|
|
|
656
668
|
"Content-Type": "application/json; charset=utf-8"
|
|
657
669
|
},
|
|
658
670
|
"data": {
|
|
659
|
-
"devicetype": "
|
|
671
|
+
"devicetype": "HueMagic for Node-RED (" + Math.floor((Math.random() * 100) + 1) + ")"
|
|
660
672
|
}
|
|
661
673
|
})
|
|
662
674
|
.then(function(response)
|
|
@@ -678,18 +690,15 @@ module.exports = function(RED)
|
|
|
678
690
|
});
|
|
679
691
|
|
|
680
692
|
//
|
|
681
|
-
// DISCOVER
|
|
682
|
-
RED.httpAdmin.get('/hue/
|
|
693
|
+
// DISCOVER RESOURCES
|
|
694
|
+
RED.httpAdmin.get('/hue/resources', function(req, res, next)
|
|
683
695
|
{
|
|
684
696
|
const targetType = req.query.type;
|
|
685
697
|
|
|
686
698
|
// GET ALL RULES
|
|
687
699
|
if(targetType == "rule")
|
|
688
700
|
{
|
|
689
|
-
API.
|
|
690
|
-
.then(function(bridge) {
|
|
691
|
-
return API.request({ ressource: "/rules", version: 1 });
|
|
692
|
-
})
|
|
701
|
+
API.request({ config: { bridge: req.query.bridge, key: req.query.key }, resource: "/rules", version: 1 })
|
|
693
702
|
.then(function(rules)
|
|
694
703
|
{
|
|
695
704
|
let targetRules = {};
|
|
@@ -714,58 +723,55 @@ module.exports = function(RED)
|
|
|
714
723
|
res.status(500).send(JSON.stringify(error));
|
|
715
724
|
});
|
|
716
725
|
}
|
|
717
|
-
// GET ALL OTHER
|
|
726
|
+
// GET ALL OTHER RESOURCES
|
|
718
727
|
else
|
|
719
728
|
{
|
|
720
|
-
API.
|
|
721
|
-
.then(function(
|
|
722
|
-
return API.request({ ressource: "all" });
|
|
723
|
-
})
|
|
724
|
-
.then(function(allRessources)
|
|
729
|
+
API.request({ config: { bridge: req.query.bridge, key: req.query.key }, resource: "all" })
|
|
730
|
+
.then(function(allResources)
|
|
725
731
|
{
|
|
726
|
-
return API.
|
|
732
|
+
return API.processResources(allResources);
|
|
727
733
|
})
|
|
728
|
-
.then(function(
|
|
734
|
+
.then(function(processedResources)
|
|
729
735
|
{
|
|
730
736
|
let targetDevices = {};
|
|
731
737
|
|
|
732
|
-
for (const [id,
|
|
738
|
+
for (const [id, resource] of Object.entries(processedResources))
|
|
733
739
|
{
|
|
734
|
-
const isGroup = (
|
|
740
|
+
const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
|
|
735
741
|
|
|
736
742
|
// NORMAL DEVICES
|
|
737
|
-
if(!isGroup &&
|
|
743
|
+
if(!isGroup && resource["services"] && resource["services"][targetType])
|
|
738
744
|
{
|
|
739
|
-
for (const [deviceID, targetDevice] of Object.entries(
|
|
745
|
+
for (const [deviceID, targetDevice] of Object.entries(resource["services"][targetType]))
|
|
740
746
|
{
|
|
741
747
|
var oneDevice = {};
|
|
742
748
|
oneDevice.id = id;
|
|
743
|
-
oneDevice.name =
|
|
744
|
-
oneDevice.model =
|
|
749
|
+
oneDevice.name = resource.metadata ? resource.metadata.name : false;
|
|
750
|
+
oneDevice.model = resource.product_data ? resource.product_data.product_name : false;
|
|
745
751
|
|
|
746
752
|
targetDevices[id] = oneDevice;
|
|
747
753
|
}
|
|
748
754
|
}
|
|
749
|
-
// GROUPED (LIGHT)
|
|
755
|
+
// GROUPED (LIGHT) RESOURCES
|
|
750
756
|
else if(isGroup && targetType === "group")
|
|
751
757
|
{
|
|
752
|
-
if(
|
|
758
|
+
if(resource["services"] && resource["services"]["grouped_light"])
|
|
753
759
|
{
|
|
754
760
|
var oneDevice = {};
|
|
755
761
|
oneDevice.id = id;
|
|
756
|
-
oneDevice.name =
|
|
757
|
-
oneDevice.model =
|
|
762
|
+
oneDevice.name = resource.metadata ? resource.metadata.name : false;
|
|
763
|
+
oneDevice.model = resource["type"];
|
|
758
764
|
|
|
759
765
|
targetDevices[id] = oneDevice;
|
|
760
766
|
}
|
|
761
767
|
}
|
|
762
768
|
// SCENES
|
|
763
|
-
else if(targetType === "scene" &&
|
|
769
|
+
else if(targetType === "scene" && resource["type"] == "scene")
|
|
764
770
|
{
|
|
765
771
|
var oneDevice = {};
|
|
766
772
|
oneDevice.id = id;
|
|
767
|
-
oneDevice.name =
|
|
768
|
-
oneDevice.group =
|
|
773
|
+
oneDevice.name = resource.metadata ? resource.metadata.name : false;
|
|
774
|
+
oneDevice.group = processedResources[resource["group"]["rid"]].metadata.name;
|
|
769
775
|
|
|
770
776
|
targetDevices[id] = oneDevice;
|
|
771
777
|
}
|