node-red-contrib-huemagic 3.0.0 → 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 +764 -603
- package/examples/Hue Bridge.json +189 -35
- package/examples/Hue Brightness.json +124 -14
- package/examples/Hue Buttons.json +88 -0
- package/examples/Hue Group.json +575 -63
- package/examples/Hue Light.json +805 -58
- package/examples/Hue Magic.json +69 -24
- package/examples/Hue Motion.json +110 -42
- package/examples/Hue Rules.json +105 -37
- package/examples/Hue Scene.json +145 -28
- package/examples/Hue Temperature.json +124 -14
- package/huemagic/animations/019-christmas.json +30 -0
- package/huemagic/animations/020-superhero.json +56 -0
- package/huemagic/animations/021-neoncity.json +52 -0
- package/huemagic/animations/previews/006-fireplace.gif +0 -0
- package/huemagic/animations/previews/013-strobo.gif +0 -0
- package/huemagic/animations/previews/014-randomrainbow.gif +0 -0
- package/huemagic/animations/previews/016-milkyway.gif +0 -0
- package/huemagic/animations/previews/018-forest.gif +0 -0
- package/huemagic/animations/previews/019-christmas.gif +0 -0
- package/huemagic/animations/previews/020-superhero.gif +0 -0
- package/huemagic/animations/previews/021-neoncity.gif +0 -0
- package/huemagic/hue-bridge-config.html +105 -61
- package/huemagic/hue-bridge-config.js +631 -319
- package/huemagic/hue-bridge.html +41 -21
- package/huemagic/hue-bridge.js +190 -396
- package/huemagic/hue-brightness.html +127 -59
- package/huemagic/hue-brightness.js +111 -55
- package/huemagic/hue-buttons.html +197 -0
- package/huemagic/hue-buttons.js +158 -0
- package/huemagic/hue-group.html +137 -66
- package/huemagic/hue-group.js +387 -403
- package/huemagic/hue-light.html +132 -64
- package/huemagic/hue-light.js +544 -378
- package/huemagic/hue-magic.html +36 -20
- package/huemagic/hue-magic.js +49 -27
- package/huemagic/hue-motion.html +127 -59
- package/huemagic/hue-motion.js +105 -101
- package/huemagic/hue-rules.html +121 -50
- package/huemagic/hue-rules.js +89 -63
- package/huemagic/hue-scene.html +95 -114
- package/huemagic/hue-scene.js +64 -95
- package/huemagic/hue-temperature.html +127 -59
- package/huemagic/hue-temperature.js +120 -56
- package/huemagic/icons/hue-bridge.png +0 -0
- package/huemagic/icons/hue-bridge.svg +3 -0
- package/huemagic/icons/hue-brightness.png +0 -0
- package/huemagic/icons/hue-brightness.svg +11 -0
- package/huemagic/icons/hue-buttons.png +0 -0
- package/huemagic/icons/hue-buttons.svg +4 -0
- package/huemagic/icons/hue-group.png +0 -0
- package/huemagic/icons/hue-group.svg +12 -0
- package/huemagic/icons/hue-light.png +0 -0
- package/huemagic/icons/hue-light.svg +3 -0
- package/huemagic/icons/hue-magic.png +0 -0
- package/huemagic/icons/hue-magic.svg +3 -0
- package/huemagic/icons/hue-motion.png +0 -0
- package/huemagic/icons/hue-motion.svg +9 -0
- package/huemagic/icons/hue-rules.png +0 -0
- package/huemagic/icons/hue-rules.svg +4 -0
- package/huemagic/icons/hue-scene.png +0 -0
- package/huemagic/icons/hue-scene.svg +5 -0
- package/huemagic/icons/hue-temperature.png +0 -0
- package/huemagic/icons/hue-temperature.svg +7 -0
- package/huemagic/locales/de/hue-bridge-config.html +17 -12
- package/huemagic/locales/de/hue-bridge-config.json +7 -5
- package/huemagic/locales/de/hue-bridge.html +64 -145
- package/huemagic/locales/de/hue-bridge.json +6 -17
- package/huemagic/locales/de/hue-brightness.html +50 -50
- package/huemagic/locales/de/hue-brightness.json +6 -5
- package/huemagic/locales/de/hue-buttons.html +58 -0
- package/huemagic/locales/de/{hue-button.json → hue-buttons.json} +7 -11
- package/huemagic/locales/de/hue-group.html +46 -122
- package/huemagic/locales/de/hue-group.json +5 -5
- package/huemagic/locales/de/hue-light.html +68 -116
- package/huemagic/locales/de/hue-light.json +5 -5
- package/huemagic/locales/de/hue-magic.html +65 -57
- package/huemagic/locales/de/hue-magic.json +2 -1
- package/huemagic/locales/de/hue-motion.html +47 -50
- package/huemagic/locales/de/hue-motion.json +5 -5
- package/huemagic/locales/de/hue-rules.html +68 -73
- package/huemagic/locales/de/hue-rules.json +5 -2
- package/huemagic/locales/de/hue-scene.html +23 -32
- package/huemagic/locales/de/hue-scene.json +5 -2
- package/huemagic/locales/de/hue-temperature.html +51 -48
- package/huemagic/locales/de/hue-temperature.json +6 -5
- package/huemagic/locales/en-US/hue-bridge-config.html +17 -12
- package/huemagic/locales/en-US/hue-bridge-config.json +5 -3
- package/huemagic/locales/en-US/hue-bridge.html +64 -145
- package/huemagic/locales/en-US/hue-bridge.json +5 -16
- package/huemagic/locales/en-US/hue-brightness.html +50 -50
- package/huemagic/locales/en-US/hue-brightness.json +5 -2
- package/huemagic/locales/en-US/hue-buttons.html +58 -0
- package/huemagic/locales/en-US/{hue-button.json → hue-buttons.json} +6 -4
- package/huemagic/locales/en-US/hue-group.html +46 -122
- package/huemagic/locales/en-US/hue-group.json +4 -2
- package/huemagic/locales/en-US/hue-light.html +68 -116
- package/huemagic/locales/en-US/hue-light.json +4 -2
- package/huemagic/locales/en-US/hue-magic.html +65 -57
- package/huemagic/locales/en-US/hue-magic.json +3 -1
- package/huemagic/locales/en-US/hue-motion.html +47 -50
- package/huemagic/locales/en-US/hue-motion.json +4 -2
- package/huemagic/locales/en-US/hue-rules.html +68 -73
- package/huemagic/locales/en-US/hue-rules.json +5 -2
- package/huemagic/locales/en-US/hue-scene.html +23 -32
- package/huemagic/locales/en-US/hue-scene.json +5 -2
- package/huemagic/locales/en-US/hue-temperature.html +51 -48
- package/huemagic/locales/en-US/hue-temperature.json +5 -2
- package/huemagic/utils/api.js +290 -0
- package/huemagic/utils/color.js +382 -0
- package/{utils → huemagic/utils}/merge.js +4 -6
- package/huemagic/utils/messages.js +447 -0
- package/package.json +9 -15
- package/examples/Hue Button.json +0 -40
- package/examples/Hue Switch.json +0 -40
- package/examples/Hue Tap.json +0 -40
- package/huemagic/hue-button.html +0 -132
- package/huemagic/hue-button.js +0 -143
- package/huemagic/hue-switch.html +0 -132
- package/huemagic/hue-switch.js +0 -163
- package/huemagic/hue-tap.html +0 -132
- package/huemagic/hue-tap.js +0 -123
- package/huemagic/icons/hue-button.png +0 -0
- package/huemagic/icons/hue-switch.png +0 -0
- package/huemagic/icons/hue-tap.png +0 -0
- package/huemagic/locales/de/hue-button.html +0 -64
- package/huemagic/locales/de/hue-switch.html +0 -64
- package/huemagic/locales/de/hue-switch.json +0 -35
- package/huemagic/locales/de/hue-tap.html +0 -55
- package/huemagic/locales/de/hue-tap.json +0 -27
- package/huemagic/locales/en-US/hue-button.html +0 -64
- package/huemagic/locales/en-US/hue-switch.html +0 -64
- package/huemagic/locales/en-US/hue-switch.json +0 -35
- package/huemagic/locales/en-US/hue-tap.html +0 -55
- package/huemagic/locales/en-US/hue-tap.json +0 -27
- package/utils/color.js +0 -9
- package/utils/messages.js +0 -658
- package/utils/rgb.js +0 -188
|
@@ -2,226 +2,592 @@ module.exports = function(RED)
|
|
|
2
2
|
{
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
|
+
const API = require('./utils/api');
|
|
6
|
+
const merge = require('./utils/merge');
|
|
7
|
+
const events = require('events');
|
|
8
|
+
const dayjs = require('dayjs');
|
|
9
|
+
const diff = require("deep-object-diff").diff;
|
|
10
|
+
const axios = require('axios');
|
|
11
|
+
const https = require('https');
|
|
12
|
+
|
|
13
|
+
// READABLE RESOURCE MESSAGES
|
|
14
|
+
const { HueBridgeMessage,
|
|
15
|
+
HueLightMessage,
|
|
16
|
+
HueGroupMessage,
|
|
17
|
+
HueMotionMessage,
|
|
18
|
+
HueTemperatureMessage,
|
|
19
|
+
HueBrightnessMessage,
|
|
20
|
+
HueButtonsMessage,
|
|
21
|
+
HueRulesMessage
|
|
22
|
+
} = require('./utils/messages');
|
|
23
|
+
|
|
5
24
|
function HueBridge(config)
|
|
6
25
|
{
|
|
7
|
-
|
|
8
|
-
var events = require('events');
|
|
9
|
-
let huejay = require('huejay');
|
|
26
|
+
const scope = this;
|
|
10
27
|
|
|
11
|
-
//
|
|
28
|
+
// STATES
|
|
12
29
|
this.nodeActive = true;
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.resources = {};
|
|
32
|
+
this.resourcesInGroups = {};
|
|
33
|
+
this.lastStates = {};
|
|
17
34
|
this.events = new events.EventEmitter();
|
|
18
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
|
+
|
|
19
39
|
// CREATE NODE
|
|
20
40
|
RED.nodes.createNode(scope, config);
|
|
21
41
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
this.client = new huejay.Client({
|
|
25
|
-
host: ipHostArray[0],
|
|
26
|
-
port: (ipHostArray > 1) ? parseInt(ipHostArray[1]) : 80,
|
|
27
|
-
username: config.key
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// DELAY
|
|
31
|
-
this.delay = function(ms)
|
|
42
|
+
// INITIALIZE
|
|
43
|
+
this.start = function()
|
|
32
44
|
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{
|
|
39
|
-
scope.client.sensors.getAll().then(sensors => {
|
|
40
|
-
let sensorUpdates = scope.getUpdates("sensor", sensors);
|
|
41
|
-
scope.emitUpdates("sensor", sensorUpdates);
|
|
42
|
-
|
|
43
|
-
return true;
|
|
45
|
+
scope.log("Initializing the bridge…");
|
|
46
|
+
API.init({ ip: config.bridge, key: config.key })
|
|
47
|
+
.then(function(bridge) {
|
|
48
|
+
scope.log("Connected to bridge");
|
|
49
|
+
return scope.getAllResources();
|
|
44
50
|
})
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return true;
|
|
51
|
+
.then(function(allResources)
|
|
52
|
+
{
|
|
53
|
+
scope.log("Process resources…");
|
|
54
|
+
return API.processResources(allResources);
|
|
51
55
|
})
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
scope.
|
|
56
|
+
.then(function(allResources)
|
|
57
|
+
{
|
|
58
|
+
// SAVE CURRENT RESOURCES
|
|
59
|
+
scope.resources = allResources;
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
// EMIT INITIAL STATES -> NODES
|
|
62
|
+
scope.log("Initial emit of resource states…");
|
|
63
|
+
return scope.emitInitialStates();
|
|
58
64
|
})
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
scope.
|
|
65
|
+
.then(function(emitted)
|
|
66
|
+
{
|
|
67
|
+
// START REFRESHING STATES
|
|
68
|
+
scope.keepUpdated();
|
|
63
69
|
|
|
70
|
+
// START LOOKING FOR FIRMWARE-UPDATES
|
|
71
|
+
scope.autoUpdateFirmware();
|
|
64
72
|
return true;
|
|
65
73
|
})
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
.catch(function(error)
|
|
75
|
+
{
|
|
76
|
+
// RETRY AFTER 30 SECONDS
|
|
77
|
+
scope.log(error);
|
|
78
|
+
if(scope.nodeActive == true) { setTimeout(function(){ scope.start(); }, 30000); }
|
|
79
|
+
});
|
|
80
|
+
}
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
// FETCH BRIDGE INFORMATION
|
|
83
|
+
this.getBridgeInformation = function(replaceResources = false)
|
|
84
|
+
{
|
|
85
|
+
return new Promise(function(resolve, reject)
|
|
86
|
+
{
|
|
87
|
+
API.request({ resource: "/config", version: 1 })
|
|
88
|
+
.then(function(bridgeInformation)
|
|
75
89
|
{
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
// PREPARE TO MATCH V2 RESOURCES
|
|
91
|
+
bridgeInformation.id = "bridge";
|
|
92
|
+
bridgeInformation.id_v1 = "/config";
|
|
93
|
+
bridgeInformation.updated = dayjs().format();
|
|
94
|
+
|
|
95
|
+
// ALSO REPLACE CURRENT RESOURCE?
|
|
96
|
+
if(replaceResources === true)
|
|
97
|
+
{
|
|
98
|
+
scope.resources[bridgeInformation.id] = bridgeInformation;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// GIVE BACK
|
|
102
|
+
resolve(bridgeInformation);
|
|
103
|
+
})
|
|
104
|
+
.catch(function(error)
|
|
105
|
+
{
|
|
106
|
+
reject(error);
|
|
107
|
+
});
|
|
78
108
|
});
|
|
79
109
|
}
|
|
80
110
|
|
|
81
|
-
//
|
|
82
|
-
this.
|
|
111
|
+
// GET ALL RESOURCES + RULES
|
|
112
|
+
this.getAllResources = function()
|
|
83
113
|
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
114
|
+
return new Promise(function(resolve, reject)
|
|
115
|
+
{
|
|
116
|
+
var allResources = [];
|
|
87
117
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
})
|
|
95
|
-
.catch(error => {
|
|
96
|
-
scope.debug(error.stack);
|
|
118
|
+
// GET BRIDGE INFORMATION
|
|
119
|
+
scope.getBridgeInformation()
|
|
120
|
+
.then(function(bridgeInformation)
|
|
121
|
+
{
|
|
122
|
+
// PUSH TO RESOURCES
|
|
123
|
+
allResources.push(bridgeInformation);
|
|
97
124
|
|
|
98
|
-
|
|
125
|
+
// CONTINUE WITH ALL RESOURCES
|
|
126
|
+
return API.request({ resource: "all" });
|
|
127
|
+
})
|
|
128
|
+
.then(function(v2Resources)
|
|
99
129
|
{
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
// MERGE RESOURCES
|
|
131
|
+
allResources = allResources.concat(v2Resources);
|
|
132
|
+
|
|
133
|
+
// GET RULES (LEGACY API)
|
|
134
|
+
return API.request({ resource: "/rules", version: 1 });
|
|
135
|
+
})
|
|
136
|
+
.then(function(rules)
|
|
137
|
+
{
|
|
138
|
+
for (var [id, rule] of Object.entries(rules))
|
|
139
|
+
{
|
|
140
|
+
// "RENAME" OWNER
|
|
141
|
+
rule["_owner"] = rule["owner"];
|
|
142
|
+
delete rule["owner"];
|
|
143
|
+
|
|
144
|
+
// ADD RULE ID(S)
|
|
145
|
+
rule["id"] = "rule_" + id;
|
|
146
|
+
rule["id_v1"] = "/rules/" + id;
|
|
147
|
+
|
|
148
|
+
// ADD RULE TYPE
|
|
149
|
+
rule["type"] = "rule";
|
|
150
|
+
|
|
151
|
+
// PUSH RULES
|
|
152
|
+
allResources.push(rule);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
resolve(allResources);
|
|
156
|
+
})
|
|
157
|
+
.catch(function(error) { reject(error); });
|
|
102
158
|
});
|
|
103
159
|
}
|
|
104
160
|
|
|
105
|
-
//
|
|
106
|
-
|
|
161
|
+
// EMIT INITIAL STATES -> NODES
|
|
162
|
+
this.emitInitialStates = function(resources = false)
|
|
107
163
|
{
|
|
108
|
-
|
|
164
|
+
return new Promise(function(resolve, reject)
|
|
165
|
+
{
|
|
166
|
+
// PUSH STATES
|
|
167
|
+
setTimeout(function()
|
|
168
|
+
{
|
|
169
|
+
// PUSH ALL STATES
|
|
170
|
+
for (const [id, resource] of Object.entries(scope.resources))
|
|
171
|
+
{
|
|
172
|
+
scope.pushUpdatedState(resource, resource.type, true);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
resolve(true);
|
|
176
|
+
}, 500);
|
|
177
|
+
});
|
|
109
178
|
}
|
|
110
|
-
|
|
179
|
+
|
|
180
|
+
// KEEEP STATES UP-TO-DATE
|
|
181
|
+
this.keepUpdated = function()
|
|
111
182
|
{
|
|
112
|
-
|
|
183
|
+
if(!config.disableupdates)
|
|
184
|
+
{
|
|
185
|
+
scope.log("Keeping nodes up-to-date…");
|
|
186
|
+
|
|
187
|
+
// REFRESH STATES (SSE)
|
|
188
|
+
this.refreshStatesSSE();
|
|
189
|
+
}
|
|
113
190
|
}
|
|
114
191
|
|
|
115
|
-
//
|
|
116
|
-
this.
|
|
192
|
+
// GET UPDATED STATES (SSE)
|
|
193
|
+
this.refreshStatesSSE = function()
|
|
117
194
|
{
|
|
118
|
-
|
|
119
|
-
|
|
195
|
+
scope.log("Subscribing to bridge events…");
|
|
196
|
+
API.subscribe(function(updates)
|
|
120
197
|
{
|
|
121
|
-
|
|
122
|
-
var updated = false;
|
|
123
|
-
var uniqueStatus = "";
|
|
198
|
+
const currentDateTime = dayjs().format();
|
|
124
199
|
|
|
125
|
-
|
|
200
|
+
for(let resource of updates)
|
|
126
201
|
{
|
|
127
|
-
|
|
202
|
+
let id = resource.id;
|
|
203
|
+
let type = resource.type;
|
|
204
|
+
|
|
205
|
+
let previousState = false;
|
|
128
206
|
|
|
129
|
-
|
|
207
|
+
// HAS OWNER?
|
|
208
|
+
if(resource["owner"])
|
|
130
209
|
{
|
|
131
|
-
|
|
210
|
+
let targetId = resource["owner"]["rid"];
|
|
211
|
+
|
|
212
|
+
if(scope.resources[targetId])
|
|
132
213
|
{
|
|
133
|
-
|
|
134
|
-
|
|
214
|
+
// GET PREVIOUS STATE
|
|
215
|
+
previousState = scope.resources[targetId]["services"][type][id];
|
|
216
|
+
|
|
217
|
+
// IS BUTTON? -> REMOVE PREVIOUS STATES
|
|
218
|
+
if(type === "button")
|
|
219
|
+
{
|
|
220
|
+
for (const [oneButtonID, oneButton] of Object.entries(scope.resources[targetId]["services"]["button"]))
|
|
221
|
+
{
|
|
222
|
+
delete scope.resources[targetId]["services"]["button"][oneButtonID]["button"];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
135
225
|
}
|
|
136
226
|
}
|
|
137
|
-
else
|
|
227
|
+
else if(scope.resources[id])
|
|
138
228
|
{
|
|
139
|
-
|
|
140
|
-
|
|
229
|
+
// GET PREVIOUS STATE
|
|
230
|
+
previousState = scope.resources[id];
|
|
141
231
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
232
|
+
|
|
233
|
+
// NO PREVIOUS STATE?
|
|
234
|
+
if(!previousState) { return false; }
|
|
235
|
+
|
|
236
|
+
// CHECK DIFFERENCES
|
|
237
|
+
const mergedState = merge.deep(previousState, resource);
|
|
238
|
+
const updatedResources = diff(previousState, mergedState);
|
|
239
|
+
|
|
240
|
+
if(Object.values(updatedResources).length > 0)
|
|
147
241
|
{
|
|
148
|
-
if(
|
|
242
|
+
if(resource["owner"])
|
|
149
243
|
{
|
|
150
|
-
|
|
151
|
-
|
|
244
|
+
let targetId = resource["owner"]["rid"];
|
|
245
|
+
|
|
246
|
+
scope.resources[targetId]["services"][type][id] = mergedState;
|
|
247
|
+
scope.resources[targetId]["updated"] = currentDateTime;
|
|
248
|
+
|
|
249
|
+
// PUSH STATE
|
|
250
|
+
scope.pushUpdatedState(scope.resources[targetId], resource.type);
|
|
251
|
+
}
|
|
252
|
+
else
|
|
253
|
+
{
|
|
254
|
+
scope.resources[id] = mergedState;
|
|
255
|
+
scope.resources[id]["updated"] = currentDateTime;
|
|
256
|
+
|
|
257
|
+
// PUSH STATE
|
|
258
|
+
scope.pushUpdatedState(scope.resources[id], resource.type);
|
|
152
259
|
}
|
|
153
260
|
}
|
|
154
|
-
else
|
|
155
|
-
{
|
|
156
|
-
this.sensors[device.id] = uniqueStatus;
|
|
157
|
-
updated = true;
|
|
158
|
-
}
|
|
159
261
|
}
|
|
160
|
-
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// PUSH UPDATED STATE
|
|
266
|
+
this.pushUpdatedState = function(resource, updatedType, suppressMessage = false)
|
|
267
|
+
{
|
|
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);
|
|
271
|
+
|
|
272
|
+
// RESOURCE CONTAINS SERVICES? -> SERVICE IN GROUP? -> EMIT CHANGES TO GROUPS ALSO
|
|
273
|
+
if(this.resources["_groupsOf"][resource.id])
|
|
274
|
+
{
|
|
275
|
+
for (var g = this.resources["_groupsOf"][resource.id].length - 1; g >= 0; g--)
|
|
276
|
+
{
|
|
277
|
+
const groupID = this.resources["_groupsOf"][resource.id][g];
|
|
278
|
+
const groupMessage = { id: groupID, type: "group", updatedType: updatedType, services: [], suppressMessage: suppressMessage };
|
|
279
|
+
|
|
280
|
+
this.events.emit(groupID, groupMessage);
|
|
281
|
+
this.events.emit("globalResourceUpdates", groupMessage);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// GET RESOURCE (FROM NODES)
|
|
287
|
+
this.get = function(type, id = false, options = {})
|
|
288
|
+
{
|
|
289
|
+
// GET SPECIFIC RESOURCE
|
|
290
|
+
if(id)
|
|
291
|
+
{
|
|
292
|
+
// RESOURCE EXISTS? -> PROCEED
|
|
293
|
+
if(scope.resources[id])
|
|
161
294
|
{
|
|
162
|
-
|
|
163
|
-
|
|
295
|
+
// RESOLVE LINKS
|
|
296
|
+
const targetResource = scope.resources[id];
|
|
297
|
+
const lastState = scope.lastStates[type+targetResource.id] ? Object.assign({}, scope.lastStates[type+targetResource.id]) : false;
|
|
298
|
+
|
|
299
|
+
if(type == "bridge")
|
|
164
300
|
{
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
301
|
+
const message = new HueBridgeMessage(targetResource, options);
|
|
302
|
+
|
|
303
|
+
// GET CURRENT STATE MESSAGE
|
|
304
|
+
let currentState = message.msg;
|
|
305
|
+
return currentState;
|
|
306
|
+
}
|
|
307
|
+
else if(type == "light")
|
|
308
|
+
{
|
|
309
|
+
const message = new HueLightMessage(targetResource, options);
|
|
310
|
+
|
|
311
|
+
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
312
|
+
let currentState = message.msg;
|
|
313
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
314
|
+
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
315
|
+
currentState.lastState = lastState;
|
|
316
|
+
|
|
317
|
+
return currentState;
|
|
318
|
+
}
|
|
319
|
+
else if(type == "group")
|
|
320
|
+
{
|
|
321
|
+
// GET MESSAGE
|
|
322
|
+
const message = new HueGroupMessage(targetResource, { resources: scope.resources, ...options});
|
|
323
|
+
|
|
324
|
+
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
325
|
+
let currentState = message.msg;
|
|
326
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
327
|
+
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
328
|
+
currentState.lastState = lastState;
|
|
329
|
+
|
|
330
|
+
return currentState;
|
|
331
|
+
}
|
|
332
|
+
else if(type == "button")
|
|
333
|
+
{
|
|
334
|
+
const message = new HueButtonsMessage(targetResource, options);
|
|
335
|
+
|
|
336
|
+
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
337
|
+
let currentState = message.msg;
|
|
338
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
339
|
+
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
340
|
+
currentState.lastState = lastState;
|
|
341
|
+
|
|
342
|
+
return currentState;
|
|
343
|
+
}
|
|
344
|
+
else if(type == "motion")
|
|
345
|
+
{
|
|
346
|
+
const message = new HueMotionMessage(targetResource, options);
|
|
347
|
+
|
|
348
|
+
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
349
|
+
let currentState = message.msg;
|
|
350
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
351
|
+
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
352
|
+
currentState.lastState = lastState;
|
|
353
|
+
|
|
354
|
+
return currentState;
|
|
355
|
+
}
|
|
356
|
+
else if(type == "temperature")
|
|
357
|
+
{
|
|
358
|
+
const message = new HueTemperatureMessage(targetResource, options);
|
|
359
|
+
|
|
360
|
+
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
361
|
+
let currentState = message.msg;
|
|
362
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
363
|
+
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
364
|
+
currentState.lastState = lastState;
|
|
365
|
+
|
|
366
|
+
return currentState;
|
|
367
|
+
}
|
|
368
|
+
else if(type == "light_level")
|
|
369
|
+
{
|
|
370
|
+
const message = new HueBrightnessMessage(targetResource, options);
|
|
371
|
+
|
|
372
|
+
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
373
|
+
let currentState = message.msg;
|
|
374
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
375
|
+
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
376
|
+
currentState.lastState = lastState;
|
|
377
|
+
|
|
378
|
+
return currentState;
|
|
379
|
+
}
|
|
380
|
+
else if(type == "rule")
|
|
381
|
+
{
|
|
382
|
+
const message = new HueRulesMessage(targetResource, options);
|
|
383
|
+
|
|
384
|
+
// GET & SAVE LAST STATE AND DIFFERENCES
|
|
385
|
+
let currentState = message.msg;
|
|
386
|
+
scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
|
|
387
|
+
currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
|
|
388
|
+
currentState.lastState = lastState;
|
|
389
|
+
|
|
390
|
+
return currentState;
|
|
170
391
|
}
|
|
171
392
|
else
|
|
172
393
|
{
|
|
173
|
-
|
|
174
|
-
updated = true;
|
|
394
|
+
return false;
|
|
175
395
|
}
|
|
176
396
|
}
|
|
177
|
-
else
|
|
397
|
+
else
|
|
398
|
+
{
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else
|
|
403
|
+
{
|
|
404
|
+
// FILTER RESOURCES BY TYPE
|
|
405
|
+
let allFilteredResources = {};
|
|
406
|
+
|
|
407
|
+
for (const [rootID, resource] of Object.entries(scope.resources))
|
|
178
408
|
{
|
|
179
|
-
|
|
180
|
-
uniqueStatus = rule.lastTriggered + rule.status;
|
|
409
|
+
const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
|
|
181
410
|
|
|
182
|
-
|
|
411
|
+
// NORMAL DEVICES
|
|
412
|
+
if(!isGroup && resource["services"] && resource["services"][type])
|
|
183
413
|
{
|
|
184
|
-
|
|
414
|
+
for (const [serviceID, targetDevice] of Object.entries(resource["services"][type]))
|
|
185
415
|
{
|
|
186
|
-
|
|
187
|
-
updated = true;
|
|
416
|
+
allFilteredResources[rootID] = scope.get(type, rootID);
|
|
188
417
|
}
|
|
189
418
|
}
|
|
190
|
-
|
|
419
|
+
// GROUPED RESOURCES
|
|
420
|
+
else if(isGroup && type === "group")
|
|
191
421
|
{
|
|
192
|
-
|
|
193
|
-
updated = true;
|
|
422
|
+
allFilteredResources[rootID] = scope.get(type, rootID);
|
|
194
423
|
}
|
|
195
424
|
}
|
|
196
425
|
|
|
197
|
-
|
|
198
|
-
|
|
426
|
+
return Object.values(allFilteredResources);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// PATCH RESOURCE (FROM NODES)
|
|
431
|
+
this.patch = function(type, id, patch, version = 2)
|
|
432
|
+
{
|
|
433
|
+
return new Promise(function(resolve, reject)
|
|
434
|
+
{
|
|
435
|
+
// GET SERVICE ID
|
|
436
|
+
if(version !== 1 && scope.resources[id] && scope.resources[id]["services"] && scope.resources[id]["services"][type])
|
|
199
437
|
{
|
|
200
|
-
|
|
438
|
+
const targetResource = Object.values(scope.resources[id]["services"][type])[0];
|
|
439
|
+
id = targetResource.id;
|
|
201
440
|
}
|
|
441
|
+
|
|
442
|
+
// ACTION!
|
|
443
|
+
API.request({ method: "PUT", resource: (version === 2) ? (type+"/"+id) : id, data: patch, version: version })
|
|
444
|
+
.then(function(response) {
|
|
445
|
+
resolve(response);
|
|
446
|
+
})
|
|
447
|
+
.catch(function(error) {
|
|
448
|
+
reject(error);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// RE-FETCH RULE (RECEIVES NO UPDATES VIA SSE)
|
|
454
|
+
this.refetchRule = function(id)
|
|
455
|
+
{
|
|
456
|
+
return new Promise(function(resolve, reject)
|
|
457
|
+
{
|
|
458
|
+
API.request({ resource: "/rules/" + id, version: 1 })
|
|
459
|
+
.then(function(rule)
|
|
460
|
+
{
|
|
461
|
+
// "RENAME" OWNER
|
|
462
|
+
rule["_owner"] = rule["owner"];
|
|
463
|
+
delete rule["owner"];
|
|
464
|
+
|
|
465
|
+
// ADD RULE ID(S)
|
|
466
|
+
rule["id"] = "rule_" + id;
|
|
467
|
+
rule["id_v1"] = "/rules/" + id;
|
|
468
|
+
|
|
469
|
+
// ADD RULE TYPE
|
|
470
|
+
rule["type"] = "rule";
|
|
471
|
+
|
|
472
|
+
// UPDATED TIME
|
|
473
|
+
rule["updated"] = dayjs().format();
|
|
474
|
+
|
|
475
|
+
// ADD BACK TO RESOURCES
|
|
476
|
+
scope.resources[rule["id"]] = rule;
|
|
477
|
+
|
|
478
|
+
// PUSH UPDATED STATE
|
|
479
|
+
scope.pushUpdatedState(rule, "rule");
|
|
480
|
+
resolve(resolve);
|
|
481
|
+
})
|
|
482
|
+
.catch(function(error) {
|
|
483
|
+
reject(error);
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// SUBSCRIBE (FROM NODES)
|
|
489
|
+
this.subscribe = function(type, id = null, callback = null)
|
|
490
|
+
{
|
|
491
|
+
// IS RULE?
|
|
492
|
+
if(type == "rule" && !!id)
|
|
493
|
+
{
|
|
494
|
+
id = "rule_" + id;
|
|
202
495
|
}
|
|
203
496
|
|
|
204
|
-
//
|
|
205
|
-
|
|
497
|
+
// PUSH WHITELIST
|
|
498
|
+
const messageWhitelist = {
|
|
499
|
+
"light": ["light", "zigbee_connectivity", "zgp_connectivity", "device"],
|
|
500
|
+
"motion": ["motion", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
|
|
501
|
+
"temperature": ["temperature", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
|
|
502
|
+
"light_level": ["light_level", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
|
|
503
|
+
"button": ["button", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
|
|
504
|
+
"group": ["group", "light", "grouped_light"],
|
|
505
|
+
"rule": ["rule"]
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
if(!id)
|
|
509
|
+
{
|
|
510
|
+
// UNIVERSAL MODE
|
|
511
|
+
this.events.on("globalResourceUpdates", function(info)
|
|
512
|
+
{
|
|
513
|
+
if(type === "bridge")
|
|
514
|
+
{
|
|
515
|
+
callback(info);
|
|
516
|
+
}
|
|
517
|
+
else if(info.services.includes(type) && messageWhitelist[type].includes(info.updatedType))
|
|
518
|
+
{
|
|
519
|
+
callback(info);
|
|
520
|
+
}
|
|
521
|
+
else if(type == "rule" && messageWhitelist[type].includes(info.updatedType))
|
|
522
|
+
{
|
|
523
|
+
callback(info);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
else
|
|
528
|
+
{
|
|
529
|
+
// SPECIFIC RESOURCE MODE
|
|
530
|
+
this.events.on(id, function(info)
|
|
531
|
+
{
|
|
532
|
+
if(type === "bridge" || messageWhitelist[type].includes(info.updatedType))
|
|
533
|
+
{
|
|
534
|
+
callback(info);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
206
538
|
}
|
|
207
539
|
|
|
208
|
-
//
|
|
209
|
-
this.
|
|
540
|
+
// AUTO UPDATES?
|
|
541
|
+
this.autoUpdateFirmware = function()
|
|
210
542
|
{
|
|
211
|
-
|
|
543
|
+
if((config.autoupdates && config.autoupdates == true) || typeof config.autoupdates == 'undefined')
|
|
212
544
|
{
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
545
|
+
scope.log("Checking for Hue Bridge firmware updates…");
|
|
546
|
+
API.request({
|
|
547
|
+
method: "PUT",
|
|
548
|
+
resource: "/config",
|
|
549
|
+
version: 1,
|
|
550
|
+
data: {
|
|
551
|
+
swupdate2: {
|
|
552
|
+
checkforupdate: true,
|
|
553
|
+
install: true
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
})
|
|
557
|
+
.then(function(status)
|
|
558
|
+
{
|
|
559
|
+
if(scope.nodeActive == true)
|
|
560
|
+
{
|
|
561
|
+
setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 * 5);
|
|
562
|
+
}
|
|
563
|
+
})
|
|
564
|
+
.catch(function(error)
|
|
565
|
+
{
|
|
566
|
+
// NO UPDATES AVAILABLE // TRY AGAIN IN 3H
|
|
567
|
+
if(scope.nodeActive == true)
|
|
568
|
+
{
|
|
569
|
+
setTimeout(function(){ scope.autoUpdateFirmware(); }, 60000 * 180);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
217
572
|
}
|
|
218
573
|
}
|
|
219
574
|
|
|
575
|
+
//
|
|
576
|
+
// START THE MAGIC
|
|
577
|
+
this.start();
|
|
578
|
+
|
|
220
579
|
//
|
|
221
580
|
// CLOSE NODE / REMOVE EVENT LISTENER
|
|
222
581
|
this.on('close', function()
|
|
223
582
|
{
|
|
224
583
|
scope.nodeActive = false;
|
|
584
|
+
|
|
585
|
+
// UNSUBSCRIBE FROM BRIDGE EVENTS
|
|
586
|
+
scope.log("Unsubscribing from bridge events…");
|
|
587
|
+
API.unsubscribe();
|
|
588
|
+
|
|
589
|
+
// UNSUBSCRIBE FROM "READY" EVENTS
|
|
590
|
+
scope.events.removeAllListeners();
|
|
225
591
|
});
|
|
226
592
|
}
|
|
227
593
|
|
|
@@ -229,16 +595,29 @@ module.exports = function(RED)
|
|
|
229
595
|
|
|
230
596
|
//
|
|
231
597
|
// DISCOVER HUE BRIDGES ON LOCAL NETWORK
|
|
232
|
-
RED.httpAdmin.get('/hue/bridges', function(req, res, next)
|
|
598
|
+
RED.httpAdmin.get('/hue/bridges', async function(req, res, next)
|
|
233
599
|
{
|
|
234
|
-
|
|
600
|
+
axios({
|
|
601
|
+
"method": "GET",
|
|
602
|
+
"url": "https://discovery.meethue.com",
|
|
603
|
+
"headers": {
|
|
604
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
605
|
+
},
|
|
606
|
+
})
|
|
607
|
+
.then(function(response)
|
|
608
|
+
{
|
|
609
|
+
// PREPARE BRIDGES OUTPUT
|
|
610
|
+
var bridges = {};
|
|
611
|
+
for (var i = response.data.length - 1; i >= 0; i--)
|
|
612
|
+
{
|
|
613
|
+
var ipAddress = response.data[i].internalipaddress;
|
|
614
|
+
bridges[ipAddress] = { ip: ipAddress, name: ipAddress };
|
|
615
|
+
}
|
|
235
616
|
|
|
236
|
-
|
|
237
|
-
.then(bridges => {
|
|
238
|
-
res.end(JSON.stringify(bridges));
|
|
617
|
+
res.end(JSON.stringify(Object.values(bridges)));
|
|
239
618
|
})
|
|
240
|
-
.catch(error
|
|
241
|
-
res.send(
|
|
619
|
+
.catch(function(error) {
|
|
620
|
+
res.send(error);
|
|
242
621
|
});
|
|
243
622
|
});
|
|
244
623
|
|
|
@@ -252,17 +631,12 @@ module.exports = function(RED)
|
|
|
252
631
|
}
|
|
253
632
|
else
|
|
254
633
|
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
var bridgeIP = (req.query.ip).toString();
|
|
258
|
-
var client = new huejay.Client({host: bridgeIP, username: 'default'});
|
|
259
|
-
|
|
260
|
-
client.bridge.get()
|
|
261
|
-
.then(bridge => {
|
|
634
|
+
API.init({ ip: req.query.ip })
|
|
635
|
+
.then(function(bridge) {
|
|
262
636
|
res.end(bridge.name);
|
|
263
637
|
})
|
|
264
|
-
.catch(error
|
|
265
|
-
res.send(
|
|
638
|
+
.catch(function(error) {
|
|
639
|
+
res.send(error);
|
|
266
640
|
});
|
|
267
641
|
}
|
|
268
642
|
});
|
|
@@ -277,200 +651,138 @@ module.exports = function(RED)
|
|
|
277
651
|
}
|
|
278
652
|
else
|
|
279
653
|
{
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
654
|
+
axios({
|
|
655
|
+
"method": "POST",
|
|
656
|
+
"url": "http://"+req.query.ip+"/api",
|
|
657
|
+
"httpsAgent": new https.Agent({ rejectUnauthorized: false }),
|
|
658
|
+
"headers": {
|
|
659
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
660
|
+
},
|
|
661
|
+
"data": {
|
|
662
|
+
"devicetype": "huemagic_" + Math.floor((Math.random() * 100) + 1)
|
|
663
|
+
}
|
|
664
|
+
})
|
|
665
|
+
.then(function(response)
|
|
284
666
|
{
|
|
285
|
-
|
|
667
|
+
var bridge = response.data;
|
|
668
|
+
if(bridge[0].error)
|
|
286
669
|
{
|
|
287
670
|
rescope.end("error");
|
|
288
671
|
}
|
|
289
672
|
else
|
|
290
673
|
{
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if(bridge[0].error)
|
|
294
|
-
{
|
|
295
|
-
rescope.end("error");
|
|
296
|
-
}
|
|
297
|
-
else
|
|
298
|
-
{
|
|
299
|
-
rescope.end(JSON.stringify(bridge));
|
|
300
|
-
}
|
|
674
|
+
rescope.end(JSON.stringify(bridge));
|
|
301
675
|
}
|
|
676
|
+
})
|
|
677
|
+
.catch(function(error) {
|
|
678
|
+
rescope.status(500).send(error);
|
|
302
679
|
});
|
|
303
680
|
}
|
|
304
681
|
});
|
|
305
682
|
|
|
306
683
|
//
|
|
307
|
-
// DISCOVER
|
|
308
|
-
RED.httpAdmin.get('/hue/
|
|
684
|
+
// DISCOVER RESOURCES
|
|
685
|
+
RED.httpAdmin.get('/hue/resources', function(req, res, next)
|
|
309
686
|
{
|
|
310
|
-
|
|
311
|
-
var bridge = (req.query.bridge).toString();
|
|
312
|
-
var username = req.query.key;
|
|
313
|
-
var type = req.query.type;
|
|
314
|
-
|
|
315
|
-
let client = new huejay.Client({
|
|
316
|
-
host: bridge,
|
|
317
|
-
username: username
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
client.sensors.getAll()
|
|
321
|
-
.then(sensors => {
|
|
322
|
-
var allSensorDevices = [];
|
|
687
|
+
const targetType = req.query.type;
|
|
323
688
|
|
|
324
|
-
|
|
689
|
+
// GET ALL RULES
|
|
690
|
+
if(targetType == "rule")
|
|
691
|
+
{
|
|
692
|
+
API.init({ ip: req.query.bridge, key: req.query.key })
|
|
693
|
+
.then(function(bridge) {
|
|
694
|
+
return API.request({ resource: "/rules", version: 1 });
|
|
695
|
+
})
|
|
696
|
+
.then(function(rules)
|
|
325
697
|
{
|
|
326
|
-
|
|
698
|
+
let targetRules = {};
|
|
699
|
+
|
|
700
|
+
for (var [id, rule] of Object.entries(rules))
|
|
327
701
|
{
|
|
328
|
-
var
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
oneSensor.model_id = sensor.attributes.attributes.modelid;
|
|
702
|
+
var oneDevice = {};
|
|
703
|
+
oneDevice.id = id;
|
|
704
|
+
oneDevice.name = rule.name;
|
|
705
|
+
oneDevice.model = false;
|
|
333
706
|
|
|
334
|
-
|
|
707
|
+
targetRules[id] = oneDevice;
|
|
335
708
|
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
res.end(JSON.stringify(allSensorDevices));
|
|
339
|
-
})
|
|
340
|
-
.catch(error => {
|
|
341
|
-
res.send(500).send(error.stack);
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
709
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
RED.httpAdmin.get('/hue/lights', function(req, res, next)
|
|
348
|
-
{
|
|
349
|
-
let huejay = require('huejay');
|
|
350
|
-
var bridge = (req.query.bridge).toString();
|
|
351
|
-
var username = req.query.key;
|
|
710
|
+
// CONVERT TO ARRAY
|
|
711
|
+
targetRules = Object.values(targetRules);
|
|
352
712
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
{
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
allLights.push(oneLightBulb);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
res.end(JSON.stringify(allLights));
|
|
372
|
-
})
|
|
373
|
-
.catch(error => {
|
|
374
|
-
res.send(500).send(error.stack);
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
//
|
|
379
|
-
// DISCOVER GROUPS
|
|
380
|
-
RED.httpAdmin.get('/hue/groups', function(req, res, next)
|
|
381
|
-
{
|
|
382
|
-
let huejay = require('huejay');
|
|
383
|
-
var bridge = (req.query.bridge).toString();
|
|
384
|
-
var username = req.query.key;
|
|
385
|
-
|
|
386
|
-
let client = new huejay.Client({
|
|
387
|
-
host: bridge,
|
|
388
|
-
username: username
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
client.groups.getAll()
|
|
392
|
-
.then(groups => {
|
|
393
|
-
var allGroups = [];
|
|
394
|
-
|
|
395
|
-
for (let group of groups)
|
|
713
|
+
// GIVE BACK
|
|
714
|
+
res.end(JSON.stringify(targetRules));
|
|
715
|
+
})
|
|
716
|
+
.catch(function(error) {
|
|
717
|
+
res.status(500).send(JSON.stringify(error));
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
// GET ALL OTHER RESOURCES
|
|
721
|
+
else
|
|
722
|
+
{
|
|
723
|
+
API.init({ ip: req.query.bridge, key: req.query.key })
|
|
724
|
+
.then(function(bridge) {
|
|
725
|
+
return API.request({ resource: "all" });
|
|
726
|
+
})
|
|
727
|
+
.then(function(allResources)
|
|
396
728
|
{
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
allGroups.push(oneGroup);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
res.end(JSON.stringify(allGroups));
|
|
405
|
-
})
|
|
406
|
-
.catch(error => {
|
|
407
|
-
res.send(500).send(error.stack);
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
//
|
|
412
|
-
// DISCOVER SCENES
|
|
413
|
-
RED.httpAdmin.get('/hue/scenes', function(req, res, next)
|
|
414
|
-
{
|
|
415
|
-
let huejay = require('huejay');
|
|
416
|
-
var bridge = (req.query.bridge).toString();
|
|
417
|
-
var username = req.query.key;
|
|
418
|
-
|
|
419
|
-
let client = new huejay.Client({
|
|
420
|
-
host: bridge,
|
|
421
|
-
username: username
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
client.scenes.getAll()
|
|
425
|
-
.then(scenes => {
|
|
426
|
-
var allScenes = [];
|
|
427
|
-
|
|
428
|
-
for (let scene of scenes)
|
|
729
|
+
return API.processResources(allResources);
|
|
730
|
+
})
|
|
731
|
+
.then(function(processedResources)
|
|
429
732
|
{
|
|
430
|
-
|
|
431
|
-
oneScene.id = scene.id;
|
|
432
|
-
oneScene.name = scene.name;
|
|
433
|
-
|
|
434
|
-
allScenes.push(oneScene);
|
|
435
|
-
}
|
|
733
|
+
let targetDevices = {};
|
|
436
734
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
res.send(500).send(error.stack);
|
|
441
|
-
});
|
|
442
|
-
});
|
|
735
|
+
for (const [id, resource] of Object.entries(processedResources))
|
|
736
|
+
{
|
|
737
|
+
const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
|
|
443
738
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
739
|
+
// NORMAL DEVICES
|
|
740
|
+
if(!isGroup && resource["services"] && resource["services"][targetType])
|
|
741
|
+
{
|
|
742
|
+
for (const [deviceID, targetDevice] of Object.entries(resource["services"][targetType]))
|
|
743
|
+
{
|
|
744
|
+
var oneDevice = {};
|
|
745
|
+
oneDevice.id = id;
|
|
746
|
+
oneDevice.name = resource.metadata ? resource.metadata.name : false;
|
|
747
|
+
oneDevice.model = resource.product_data ? resource.product_data.product_name : false;
|
|
451
748
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
749
|
+
targetDevices[id] = oneDevice;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
// GROUPED (LIGHT) RESOURCES
|
|
753
|
+
else if(isGroup && targetType === "group")
|
|
754
|
+
{
|
|
755
|
+
if(resource["services"] && resource["services"]["grouped_light"])
|
|
756
|
+
{
|
|
757
|
+
var oneDevice = {};
|
|
758
|
+
oneDevice.id = id;
|
|
759
|
+
oneDevice.name = resource.metadata ? resource.metadata.name : false;
|
|
760
|
+
oneDevice.model = resource["type"];
|
|
456
761
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
762
|
+
targetDevices[id] = oneDevice;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
// SCENES
|
|
766
|
+
else if(targetType === "scene" && resource["type"] == "scene")
|
|
767
|
+
{
|
|
768
|
+
var oneDevice = {};
|
|
769
|
+
oneDevice.id = id;
|
|
770
|
+
oneDevice.name = resource.metadata ? resource.metadata.name : false;
|
|
771
|
+
oneDevice.group = processedResources[resource["group"]["rid"]].metadata.name;
|
|
460
772
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
oneRule.id = rule.id;
|
|
465
|
-
oneRule.name = rule.name;
|
|
773
|
+
targetDevices[id] = oneDevice;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
466
776
|
|
|
467
|
-
|
|
468
|
-
|
|
777
|
+
// CONVERT TO ARRAY
|
|
778
|
+
targetDevices = Object.values(targetDevices);
|
|
469
779
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
780
|
+
// GIVE BACK
|
|
781
|
+
res.end(JSON.stringify(targetDevices));
|
|
782
|
+
})
|
|
783
|
+
.catch(function(error) {
|
|
784
|
+
res.status(500).send(JSON.stringify(error));
|
|
785
|
+
});
|
|
786
|
+
}
|
|
475
787
|
});
|
|
476
788
|
};
|