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