node-red-contrib-huemagic 4.0.2 → 4.0.3
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 +10 -10
- package/examples/Hue Bridge.json +1 -1
- package/huemagic/hue-bridge-config.html +4 -4
- package/huemagic/hue-bridge-config.js +130 -127
- 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.json +1 -1
- package/huemagic/locales/en-US/hue-bridge.json +1 -1
- package/huemagic/locales/en-US/hue-buttons.html +1 -1
- 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 +45 -45
- 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,11 +28,14 @@ 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
|
+
|
|
36
39
|
// CREATE NODE
|
|
37
40
|
RED.nodes.createNode(scope, config);
|
|
38
41
|
|
|
@@ -43,20 +46,20 @@ module.exports = function(RED)
|
|
|
43
46
|
API.init({ ip: config.bridge, key: config.key })
|
|
44
47
|
.then(function(bridge) {
|
|
45
48
|
scope.log("Connected to bridge");
|
|
46
|
-
return scope.
|
|
49
|
+
return scope.getAllResources();
|
|
47
50
|
})
|
|
48
|
-
.then(function(
|
|
51
|
+
.then(function(allResources)
|
|
49
52
|
{
|
|
50
|
-
scope.log("Process
|
|
51
|
-
return API.
|
|
53
|
+
scope.log("Process resources…");
|
|
54
|
+
return API.processResources(allResources);
|
|
52
55
|
})
|
|
53
|
-
.then(function(
|
|
56
|
+
.then(function(allResources)
|
|
54
57
|
{
|
|
55
|
-
// SAVE CURRENT
|
|
56
|
-
scope.
|
|
58
|
+
// SAVE CURRENT RESOURCES
|
|
59
|
+
scope.resources = allResources;
|
|
57
60
|
|
|
58
61
|
// EMIT INITIAL STATES -> NODES
|
|
59
|
-
scope.log("Initial emit of
|
|
62
|
+
scope.log("Initial emit of resource states…");
|
|
60
63
|
return scope.emitInitialStates();
|
|
61
64
|
})
|
|
62
65
|
.then(function(emitted)
|
|
@@ -77,22 +80,22 @@ module.exports = function(RED)
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
// FETCH BRIDGE INFORMATION
|
|
80
|
-
this.getBridgeInformation = function(
|
|
83
|
+
this.getBridgeInformation = function(replaceResources = false)
|
|
81
84
|
{
|
|
82
85
|
return new Promise(function(resolve, reject)
|
|
83
86
|
{
|
|
84
|
-
API.request({
|
|
87
|
+
API.request({ resource: "/config", version: 1 })
|
|
85
88
|
.then(function(bridgeInformation)
|
|
86
89
|
{
|
|
87
|
-
// PREPARE TO MATCH V2
|
|
90
|
+
// PREPARE TO MATCH V2 RESOURCES
|
|
88
91
|
bridgeInformation.id = "bridge";
|
|
89
92
|
bridgeInformation.id_v1 = "/config";
|
|
90
93
|
bridgeInformation.updated = dayjs().format();
|
|
91
94
|
|
|
92
|
-
// ALSO REPLACE CURRENT
|
|
93
|
-
if(
|
|
95
|
+
// ALSO REPLACE CURRENT RESOURCE?
|
|
96
|
+
if(replaceResources === true)
|
|
94
97
|
{
|
|
95
|
-
scope.
|
|
98
|
+
scope.resources[bridgeInformation.id] = bridgeInformation;
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
// GIVE BACK
|
|
@@ -105,30 +108,30 @@ module.exports = function(RED)
|
|
|
105
108
|
});
|
|
106
109
|
}
|
|
107
110
|
|
|
108
|
-
// GET ALL
|
|
109
|
-
this.
|
|
111
|
+
// GET ALL RESOURCES + RULES
|
|
112
|
+
this.getAllResources = function()
|
|
110
113
|
{
|
|
111
114
|
return new Promise(function(resolve, reject)
|
|
112
115
|
{
|
|
113
|
-
var
|
|
116
|
+
var allResources = [];
|
|
114
117
|
|
|
115
118
|
// GET BRIDGE INFORMATION
|
|
116
119
|
scope.getBridgeInformation()
|
|
117
120
|
.then(function(bridgeInformation)
|
|
118
121
|
{
|
|
119
|
-
// PUSH TO
|
|
120
|
-
|
|
122
|
+
// PUSH TO RESOURCES
|
|
123
|
+
allResources.push(bridgeInformation);
|
|
121
124
|
|
|
122
|
-
// CONTINUE WITH ALL
|
|
123
|
-
return API.request({
|
|
125
|
+
// CONTINUE WITH ALL RESOURCES
|
|
126
|
+
return API.request({ resource: "all" });
|
|
124
127
|
})
|
|
125
|
-
.then(function(
|
|
128
|
+
.then(function(v2Resources)
|
|
126
129
|
{
|
|
127
|
-
// MERGE
|
|
128
|
-
|
|
130
|
+
// MERGE RESOURCES
|
|
131
|
+
allResources = allResources.concat(v2Resources);
|
|
129
132
|
|
|
130
133
|
// GET RULES (LEGACY API)
|
|
131
|
-
return API.request({
|
|
134
|
+
return API.request({ resource: "/rules", version: 1 });
|
|
132
135
|
})
|
|
133
136
|
.then(function(rules)
|
|
134
137
|
{
|
|
@@ -146,17 +149,17 @@ module.exports = function(RED)
|
|
|
146
149
|
rule["type"] = "rule";
|
|
147
150
|
|
|
148
151
|
// PUSH RULES
|
|
149
|
-
|
|
152
|
+
allResources.push(rule);
|
|
150
153
|
}
|
|
151
154
|
|
|
152
|
-
resolve(
|
|
155
|
+
resolve(allResources);
|
|
153
156
|
})
|
|
154
157
|
.catch(function(error) { reject(error); });
|
|
155
158
|
});
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
// EMIT INITIAL STATES -> NODES
|
|
159
|
-
this.emitInitialStates = function(
|
|
162
|
+
this.emitInitialStates = function(resources = false)
|
|
160
163
|
{
|
|
161
164
|
return new Promise(function(resolve, reject)
|
|
162
165
|
{
|
|
@@ -164,9 +167,9 @@ module.exports = function(RED)
|
|
|
164
167
|
setTimeout(function()
|
|
165
168
|
{
|
|
166
169
|
// PUSH ALL STATES
|
|
167
|
-
for (const [id,
|
|
170
|
+
for (const [id, resource] of Object.entries(scope.resources))
|
|
168
171
|
{
|
|
169
|
-
scope.pushUpdatedState(
|
|
172
|
+
scope.pushUpdatedState(resource, resource.type, true);
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
resolve(true);
|
|
@@ -194,65 +197,65 @@ module.exports = function(RED)
|
|
|
194
197
|
{
|
|
195
198
|
const currentDateTime = dayjs().format();
|
|
196
199
|
|
|
197
|
-
for(let
|
|
200
|
+
for(let resource of updates)
|
|
198
201
|
{
|
|
199
|
-
let id =
|
|
200
|
-
let type =
|
|
202
|
+
let id = resource.id;
|
|
203
|
+
let type = resource.type;
|
|
201
204
|
|
|
202
205
|
let previousState = false;
|
|
203
206
|
|
|
204
207
|
// HAS OWNER?
|
|
205
|
-
if(
|
|
208
|
+
if(resource["owner"])
|
|
206
209
|
{
|
|
207
|
-
let targetId =
|
|
210
|
+
let targetId = resource["owner"]["rid"];
|
|
208
211
|
|
|
209
|
-
if(scope.
|
|
212
|
+
if(scope.resources[targetId])
|
|
210
213
|
{
|
|
211
214
|
// GET PREVIOUS STATE
|
|
212
|
-
previousState = scope.
|
|
215
|
+
previousState = scope.resources[targetId]["services"][type][id];
|
|
213
216
|
|
|
214
217
|
// IS BUTTON? -> REMOVE PREVIOUS STATES
|
|
215
218
|
if(type === "button")
|
|
216
219
|
{
|
|
217
|
-
for (const [oneButtonID, oneButton] of Object.entries(scope.
|
|
220
|
+
for (const [oneButtonID, oneButton] of Object.entries(scope.resources[targetId]["services"]["button"]))
|
|
218
221
|
{
|
|
219
|
-
delete scope.
|
|
222
|
+
delete scope.resources[targetId]["services"]["button"][oneButtonID]["button"];
|
|
220
223
|
}
|
|
221
224
|
}
|
|
222
225
|
}
|
|
223
226
|
}
|
|
224
|
-
else if(scope.
|
|
227
|
+
else if(scope.resources[id])
|
|
225
228
|
{
|
|
226
229
|
// GET PREVIOUS STATE
|
|
227
|
-
previousState = scope.
|
|
230
|
+
previousState = scope.resources[id];
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
// NO PREVIOUS STATE?
|
|
231
234
|
if(!previousState) { return false; }
|
|
232
235
|
|
|
233
236
|
// CHECK DIFFERENCES
|
|
234
|
-
const mergedState = merge.deep(previousState,
|
|
235
|
-
const
|
|
237
|
+
const mergedState = merge.deep(previousState, resource);
|
|
238
|
+
const updatedResources = diff(previousState, mergedState);
|
|
236
239
|
|
|
237
|
-
if(Object.values(
|
|
240
|
+
if(Object.values(updatedResources).length > 0)
|
|
238
241
|
{
|
|
239
|
-
if(
|
|
242
|
+
if(resource["owner"])
|
|
240
243
|
{
|
|
241
|
-
let targetId =
|
|
244
|
+
let targetId = resource["owner"]["rid"];
|
|
242
245
|
|
|
243
|
-
scope.
|
|
244
|
-
scope.
|
|
246
|
+
scope.resources[targetId]["services"][type][id] = mergedState;
|
|
247
|
+
scope.resources[targetId]["updated"] = currentDateTime;
|
|
245
248
|
|
|
246
249
|
// PUSH STATE
|
|
247
|
-
scope.pushUpdatedState(scope.
|
|
250
|
+
scope.pushUpdatedState(scope.resources[targetId], resource.type);
|
|
248
251
|
}
|
|
249
252
|
else
|
|
250
253
|
{
|
|
251
|
-
scope.
|
|
252
|
-
scope.
|
|
254
|
+
scope.resources[id] = mergedState;
|
|
255
|
+
scope.resources[id]["updated"] = currentDateTime;
|
|
253
256
|
|
|
254
257
|
// PUSH STATE
|
|
255
|
-
scope.pushUpdatedState(scope.
|
|
258
|
+
scope.pushUpdatedState(scope.resources[id], resource.type);
|
|
256
259
|
}
|
|
257
260
|
}
|
|
258
261
|
}
|
|
@@ -260,42 +263,42 @@ module.exports = function(RED)
|
|
|
260
263
|
}
|
|
261
264
|
|
|
262
265
|
// PUSH UPDATED STATE
|
|
263
|
-
this.pushUpdatedState = function(
|
|
266
|
+
this.pushUpdatedState = function(resource, updatedType, suppressMessage = false)
|
|
264
267
|
{
|
|
265
|
-
const msg = { id:
|
|
266
|
-
this.events.emit(
|
|
267
|
-
this.events.emit("
|
|
268
|
+
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);
|
|
268
271
|
|
|
269
|
-
//
|
|
270
|
-
if(this.
|
|
272
|
+
// RESOURCE CONTAINS SERVICES? -> SERVICE IN GROUP? -> EMIT CHANGES TO GROUPS ALSO
|
|
273
|
+
if(this.resources["_groupsOf"][resource.id])
|
|
271
274
|
{
|
|
272
|
-
for (var g = this.
|
|
275
|
+
for (var g = this.resources["_groupsOf"][resource.id].length - 1; g >= 0; g--)
|
|
273
276
|
{
|
|
274
|
-
const groupID = this.
|
|
277
|
+
const groupID = this.resources["_groupsOf"][resource.id][g];
|
|
275
278
|
const groupMessage = { id: groupID, type: "group", updatedType: updatedType, services: [], suppressMessage: suppressMessage };
|
|
276
279
|
|
|
277
280
|
this.events.emit(groupID, groupMessage);
|
|
278
|
-
this.events.emit("
|
|
281
|
+
this.events.emit("globalResourceUpdates", groupMessage);
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
}
|
|
282
285
|
|
|
283
|
-
// GET
|
|
286
|
+
// GET RESOURCE (FROM NODES)
|
|
284
287
|
this.get = function(type, id = false, options = {})
|
|
285
288
|
{
|
|
286
|
-
// GET SPECIFIC
|
|
289
|
+
// GET SPECIFIC RESOURCE
|
|
287
290
|
if(id)
|
|
288
291
|
{
|
|
289
|
-
//
|
|
290
|
-
if(scope.
|
|
292
|
+
// RESOURCE EXISTS? -> PROCEED
|
|
293
|
+
if(scope.resources[id])
|
|
291
294
|
{
|
|
292
295
|
// RESOLVE LINKS
|
|
293
|
-
const
|
|
294
|
-
const lastState = scope.lastStates[type+
|
|
296
|
+
const targetResource = scope.resources[id];
|
|
297
|
+
const lastState = scope.lastStates[type+targetResource.id] ? Object.assign({}, scope.lastStates[type+targetResource.id]) : false;
|
|
295
298
|
|
|
296
299
|
if(type == "bridge")
|
|
297
300
|
{
|
|
298
|
-
const message = new HueBridgeMessage(
|
|
301
|
+
const message = new HueBridgeMessage(targetResource, options);
|
|
299
302
|
|
|
300
303
|
// GET CURRENT STATE MESSAGE
|
|
301
304
|
let currentState = message.msg;
|
|
@@ -303,11 +306,11 @@ module.exports = function(RED)
|
|
|
303
306
|
}
|
|
304
307
|
else if(type == "light")
|
|
305
308
|
{
|
|
306
|
-
const message = new HueLightMessage(
|
|
309
|
+
const message = new HueLightMessage(targetResource, options);
|
|
307
310
|
|
|
308
311
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
309
312
|
let currentState = message.msg;
|
|
310
|
-
scope.lastStates[type+
|
|
313
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
311
314
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
312
315
|
currentState.lastState = lastState;
|
|
313
316
|
|
|
@@ -316,11 +319,11 @@ module.exports = function(RED)
|
|
|
316
319
|
else if(type == "group")
|
|
317
320
|
{
|
|
318
321
|
// GET MESSAGE
|
|
319
|
-
const message = new HueGroupMessage(
|
|
322
|
+
const message = new HueGroupMessage(targetResource, { resources: scope.resources, ...options});
|
|
320
323
|
|
|
321
324
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
322
325
|
let currentState = message.msg;
|
|
323
|
-
scope.lastStates[type+
|
|
326
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
324
327
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
325
328
|
currentState.lastState = lastState;
|
|
326
329
|
|
|
@@ -328,11 +331,11 @@ module.exports = function(RED)
|
|
|
328
331
|
}
|
|
329
332
|
else if(type == "button")
|
|
330
333
|
{
|
|
331
|
-
const message = new HueButtonsMessage(
|
|
334
|
+
const message = new HueButtonsMessage(targetResource, options);
|
|
332
335
|
|
|
333
336
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
334
337
|
let currentState = message.msg;
|
|
335
|
-
scope.lastStates[type+
|
|
338
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
336
339
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
337
340
|
currentState.lastState = lastState;
|
|
338
341
|
|
|
@@ -340,11 +343,11 @@ module.exports = function(RED)
|
|
|
340
343
|
}
|
|
341
344
|
else if(type == "motion")
|
|
342
345
|
{
|
|
343
|
-
const message = new HueMotionMessage(
|
|
346
|
+
const message = new HueMotionMessage(targetResource, options);
|
|
344
347
|
|
|
345
348
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
346
349
|
let currentState = message.msg;
|
|
347
|
-
scope.lastStates[type+
|
|
350
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
348
351
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
349
352
|
currentState.lastState = lastState;
|
|
350
353
|
|
|
@@ -352,11 +355,11 @@ module.exports = function(RED)
|
|
|
352
355
|
}
|
|
353
356
|
else if(type == "temperature")
|
|
354
357
|
{
|
|
355
|
-
const message = new HueTemperatureMessage(
|
|
358
|
+
const message = new HueTemperatureMessage(targetResource, options);
|
|
356
359
|
|
|
357
360
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
358
361
|
let currentState = message.msg;
|
|
359
|
-
scope.lastStates[type+
|
|
362
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
360
363
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
361
364
|
currentState.lastState = lastState;
|
|
362
365
|
|
|
@@ -364,11 +367,11 @@ module.exports = function(RED)
|
|
|
364
367
|
}
|
|
365
368
|
else if(type == "light_level")
|
|
366
369
|
{
|
|
367
|
-
const message = new HueBrightnessMessage(
|
|
370
|
+
const message = new HueBrightnessMessage(targetResource, options);
|
|
368
371
|
|
|
369
372
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
370
373
|
let currentState = message.msg;
|
|
371
|
-
scope.lastStates[type+
|
|
374
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
372
375
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
373
376
|
currentState.lastState = lastState;
|
|
374
377
|
|
|
@@ -376,11 +379,11 @@ module.exports = function(RED)
|
|
|
376
379
|
}
|
|
377
380
|
else if(type == "rule")
|
|
378
381
|
{
|
|
379
|
-
const message = new HueRulesMessage(
|
|
382
|
+
const message = new HueRulesMessage(targetResource, options);
|
|
380
383
|
|
|
381
384
|
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
382
385
|
let currentState = message.msg;
|
|
383
|
-
scope.lastStates[type+
|
|
386
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
384
387
|
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
385
388
|
currentState.lastState = lastState;
|
|
386
389
|
|
|
@@ -398,46 +401,46 @@ module.exports = function(RED)
|
|
|
398
401
|
}
|
|
399
402
|
else
|
|
400
403
|
{
|
|
401
|
-
// FILTER
|
|
402
|
-
let
|
|
404
|
+
// FILTER RESOURCES BY TYPE
|
|
405
|
+
let allFilteredResources = {};
|
|
403
406
|
|
|
404
|
-
for (const [rootID,
|
|
407
|
+
for (const [rootID, resource] of Object.entries(scope.resources))
|
|
405
408
|
{
|
|
406
|
-
const isGroup = (
|
|
409
|
+
const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
|
|
407
410
|
|
|
408
411
|
// NORMAL DEVICES
|
|
409
|
-
if(!isGroup &&
|
|
412
|
+
if(!isGroup && resource["services"] && resource["services"][type])
|
|
410
413
|
{
|
|
411
|
-
for (const [serviceID, targetDevice] of Object.entries(
|
|
414
|
+
for (const [serviceID, targetDevice] of Object.entries(resource["services"][type]))
|
|
412
415
|
{
|
|
413
|
-
|
|
416
|
+
allFilteredResources[rootID] = scope.get(type, rootID);
|
|
414
417
|
}
|
|
415
418
|
}
|
|
416
|
-
// GROUPED
|
|
419
|
+
// GROUPED RESOURCES
|
|
417
420
|
else if(isGroup && type === "group")
|
|
418
421
|
{
|
|
419
|
-
|
|
422
|
+
allFilteredResources[rootID] = scope.get(type, rootID);
|
|
420
423
|
}
|
|
421
424
|
}
|
|
422
425
|
|
|
423
|
-
return Object.values(
|
|
426
|
+
return Object.values(allFilteredResources);
|
|
424
427
|
}
|
|
425
428
|
}
|
|
426
429
|
|
|
427
|
-
// PATCH
|
|
430
|
+
// PATCH RESOURCE (FROM NODES)
|
|
428
431
|
this.patch = function(type, id, patch, version = 2)
|
|
429
432
|
{
|
|
430
433
|
return new Promise(function(resolve, reject)
|
|
431
434
|
{
|
|
432
435
|
// GET SERVICE ID
|
|
433
|
-
if(version !== 1 && scope.
|
|
436
|
+
if(version !== 1 && scope.resources[id] && scope.resources[id]["services"] && scope.resources[id]["services"][type])
|
|
434
437
|
{
|
|
435
|
-
const
|
|
436
|
-
id =
|
|
438
|
+
const targetResource = Object.values(scope.resources[id]["services"][type])[0];
|
|
439
|
+
id = targetResource.id;
|
|
437
440
|
}
|
|
438
441
|
|
|
439
442
|
// ACTION!
|
|
440
|
-
API.request({ method: "PUT",
|
|
443
|
+
API.request({ method: "PUT", resource: (version === 2) ? (type+"/"+id) : id, data: patch, version: version })
|
|
441
444
|
.then(function(response) {
|
|
442
445
|
resolve(response);
|
|
443
446
|
})
|
|
@@ -452,7 +455,7 @@ module.exports = function(RED)
|
|
|
452
455
|
{
|
|
453
456
|
return new Promise(function(resolve, reject)
|
|
454
457
|
{
|
|
455
|
-
API.request({
|
|
458
|
+
API.request({ resource: "/rules/" + id, version: 1 })
|
|
456
459
|
.then(function(rule)
|
|
457
460
|
{
|
|
458
461
|
// "RENAME" OWNER
|
|
@@ -469,8 +472,8 @@ module.exports = function(RED)
|
|
|
469
472
|
// UPDATED TIME
|
|
470
473
|
rule["updated"] = dayjs().format();
|
|
471
474
|
|
|
472
|
-
// ADD BACK TO
|
|
473
|
-
scope.
|
|
475
|
+
// ADD BACK TO RESOURCES
|
|
476
|
+
scope.resources[rule["id"]] = rule;
|
|
474
477
|
|
|
475
478
|
// PUSH UPDATED STATE
|
|
476
479
|
scope.pushUpdatedState(rule, "rule");
|
|
@@ -505,7 +508,7 @@ module.exports = function(RED)
|
|
|
505
508
|
if(!id)
|
|
506
509
|
{
|
|
507
510
|
// UNIVERSAL MODE
|
|
508
|
-
this.events.on("
|
|
511
|
+
this.events.on("globalResourceUpdates", function(info)
|
|
509
512
|
{
|
|
510
513
|
if(type === "bridge")
|
|
511
514
|
{
|
|
@@ -523,7 +526,7 @@ module.exports = function(RED)
|
|
|
523
526
|
}
|
|
524
527
|
else
|
|
525
528
|
{
|
|
526
|
-
// SPECIFIC
|
|
529
|
+
// SPECIFIC RESOURCE MODE
|
|
527
530
|
this.events.on(id, function(info)
|
|
528
531
|
{
|
|
529
532
|
if(type === "bridge" || messageWhitelist[type].includes(info.updatedType))
|
|
@@ -542,7 +545,7 @@ module.exports = function(RED)
|
|
|
542
545
|
scope.log("Checking for Hue Bridge firmware updates…");
|
|
543
546
|
API.request({
|
|
544
547
|
method: "PUT",
|
|
545
|
-
|
|
548
|
+
resource: "/config",
|
|
546
549
|
version: 1,
|
|
547
550
|
data: {
|
|
548
551
|
swupdate2: {
|
|
@@ -678,8 +681,8 @@ module.exports = function(RED)
|
|
|
678
681
|
});
|
|
679
682
|
|
|
680
683
|
//
|
|
681
|
-
// DISCOVER
|
|
682
|
-
RED.httpAdmin.get('/hue/
|
|
684
|
+
// DISCOVER RESOURCES
|
|
685
|
+
RED.httpAdmin.get('/hue/resources', function(req, res, next)
|
|
683
686
|
{
|
|
684
687
|
const targetType = req.query.type;
|
|
685
688
|
|
|
@@ -688,7 +691,7 @@ module.exports = function(RED)
|
|
|
688
691
|
{
|
|
689
692
|
API.init({ ip: req.query.bridge, key: req.query.key })
|
|
690
693
|
.then(function(bridge) {
|
|
691
|
-
return API.request({
|
|
694
|
+
return API.request({ resource: "/rules", version: 1 });
|
|
692
695
|
})
|
|
693
696
|
.then(function(rules)
|
|
694
697
|
{
|
|
@@ -714,58 +717,58 @@ module.exports = function(RED)
|
|
|
714
717
|
res.status(500).send(JSON.stringify(error));
|
|
715
718
|
});
|
|
716
719
|
}
|
|
717
|
-
// GET ALL OTHER
|
|
720
|
+
// GET ALL OTHER RESOURCES
|
|
718
721
|
else
|
|
719
722
|
{
|
|
720
723
|
API.init({ ip: req.query.bridge, key: req.query.key })
|
|
721
724
|
.then(function(bridge) {
|
|
722
|
-
return API.request({
|
|
725
|
+
return API.request({ resource: "all" });
|
|
723
726
|
})
|
|
724
|
-
.then(function(
|
|
727
|
+
.then(function(allResources)
|
|
725
728
|
{
|
|
726
|
-
return API.
|
|
729
|
+
return API.processResources(allResources);
|
|
727
730
|
})
|
|
728
|
-
.then(function(
|
|
731
|
+
.then(function(processedResources)
|
|
729
732
|
{
|
|
730
733
|
let targetDevices = {};
|
|
731
734
|
|
|
732
|
-
for (const [id,
|
|
735
|
+
for (const [id, resource] of Object.entries(processedResources))
|
|
733
736
|
{
|
|
734
|
-
const isGroup = (
|
|
737
|
+
const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
|
|
735
738
|
|
|
736
739
|
// NORMAL DEVICES
|
|
737
|
-
if(!isGroup &&
|
|
740
|
+
if(!isGroup && resource["services"] && resource["services"][targetType])
|
|
738
741
|
{
|
|
739
|
-
for (const [deviceID, targetDevice] of Object.entries(
|
|
742
|
+
for (const [deviceID, targetDevice] of Object.entries(resource["services"][targetType]))
|
|
740
743
|
{
|
|
741
744
|
var oneDevice = {};
|
|
742
745
|
oneDevice.id = id;
|
|
743
|
-
oneDevice.name =
|
|
744
|
-
oneDevice.model =
|
|
746
|
+
oneDevice.name = resource.metadata ? resource.metadata.name : false;
|
|
747
|
+
oneDevice.model = resource.product_data ? resource.product_data.product_name : false;
|
|
745
748
|
|
|
746
749
|
targetDevices[id] = oneDevice;
|
|
747
750
|
}
|
|
748
751
|
}
|
|
749
|
-
// GROUPED (LIGHT)
|
|
752
|
+
// GROUPED (LIGHT) RESOURCES
|
|
750
753
|
else if(isGroup && targetType === "group")
|
|
751
754
|
{
|
|
752
|
-
if(
|
|
755
|
+
if(resource["services"] && resource["services"]["grouped_light"])
|
|
753
756
|
{
|
|
754
757
|
var oneDevice = {};
|
|
755
758
|
oneDevice.id = id;
|
|
756
|
-
oneDevice.name =
|
|
757
|
-
oneDevice.model =
|
|
759
|
+
oneDevice.name = resource.metadata ? resource.metadata.name : false;
|
|
760
|
+
oneDevice.model = resource["type"];
|
|
758
761
|
|
|
759
762
|
targetDevices[id] = oneDevice;
|
|
760
763
|
}
|
|
761
764
|
}
|
|
762
765
|
// SCENES
|
|
763
|
-
else if(targetType === "scene" &&
|
|
766
|
+
else if(targetType === "scene" && resource["type"] == "scene")
|
|
764
767
|
{
|
|
765
768
|
var oneDevice = {};
|
|
766
769
|
oneDevice.id = id;
|
|
767
|
-
oneDevice.name =
|
|
768
|
-
oneDevice.group =
|
|
770
|
+
oneDevice.name = resource.metadata ? resource.metadata.name : false;
|
|
771
|
+
oneDevice.group = processedResources[resource["group"]["rid"]].metadata.name;
|
|
769
772
|
|
|
770
773
|
targetDevices[id] = oneDevice;
|
|
771
774
|
}
|
package/huemagic/hue-bridge.js
CHANGED
|
@@ -79,11 +79,11 @@ module.exports = function(RED)
|
|
|
79
79
|
|
|
80
80
|
//
|
|
81
81
|
// SUBSCRIBE TO UPDATES FROM THE BRIDGE
|
|
82
|
-
bridge.subscribe("bridge", "
|
|
82
|
+
bridge.subscribe("bridge", "globalResourceUpdates", async function(info)
|
|
83
83
|
{
|
|
84
84
|
let currentState = bridge.get(info.updatedType, info.id);
|
|
85
85
|
|
|
86
|
-
//
|
|
86
|
+
// RESOURCE FOUND?
|
|
87
87
|
if(currentState !== false)
|
|
88
88
|
{
|
|
89
89
|
// UPDATE COUNTER
|
|
@@ -138,7 +138,7 @@ module.exports = function(RED)
|
|
|
138
138
|
done = done || function() { scope.done.apply(scope,arguments); }
|
|
139
139
|
|
|
140
140
|
// SET LAST COMMAND
|
|
141
|
-
scope.lastCommand = msg;
|
|
141
|
+
scope.lastCommand = RED.util.cloneMessage(msg);
|
|
142
142
|
|
|
143
143
|
// GET BRIDGE INFORMATION
|
|
144
144
|
scope.getBridgeInformation()
|
|
@@ -171,7 +171,7 @@ module.exports = function(RED)
|
|
|
171
171
|
if(done) { done(error); }
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
|
-
// FETCH
|
|
174
|
+
// FETCH RESOURCES
|
|
175
175
|
else if(typeof msg.payload != 'undefined' && typeof msg.payload.fetch != 'undefined')
|
|
176
176
|
{
|
|
177
177
|
let fetchTypes = [];
|
|
@@ -179,7 +179,7 @@ module.exports = function(RED)
|
|
|
179
179
|
else if(typeof msg.payload.fetch == 'object') { fetchTypes = msg.payload.fetch; }
|
|
180
180
|
else { return false; }
|
|
181
181
|
|
|
182
|
-
scope.status({fill: "blue", shape: "dot", text: "hue-bridge.node.f-
|
|
182
|
+
scope.status({fill: "blue", shape: "dot", text: "hue-bridge.node.f-resources" });
|
|
183
183
|
|
|
184
184
|
// FETCH
|
|
185
185
|
bridgeInformation.results = {};
|