node-red-contrib-huemagic 2.8.6 → 4.0.2
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/CHANGELOG.md +229 -0
- package/README.md +807 -766
- 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/001-sunny.json +22 -22
- package/huemagic/animations/002-cloudy.json +46 -46
- package/huemagic/animations/003-nightsky.json +20 -20
- package/huemagic/animations/004-sunrise.json +54 -54
- package/huemagic/animations/005-sunset.json +46 -46
- package/huemagic/animations/006-fireplace.json +74 -74
- package/huemagic/animations/007-spring.json +31 -31
- package/huemagic/animations/008-autmn.json +23 -23
- package/huemagic/animations/009-winter.json +23 -23
- package/huemagic/animations/010-dirtyfog.json +23 -23
- package/huemagic/animations/011-lgbt.json +49 -50
- package/huemagic/animations/012-rasta.json +28 -29
- package/huemagic/animations/013-strobo.json +30 -31
- package/huemagic/animations/014-randomrainbow.json +30 -31
- package/huemagic/animations/015-sos.json +158 -159
- package/huemagic/animations/016-milkyway.json +83 -0
- package/huemagic/animations/017-beach.json +40 -0
- package/huemagic/animations/018-forest.json +43 -0
- 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/001-sunny.gif +0 -0
- package/huemagic/animations/previews/002-cloudy.gif +0 -0
- package/huemagic/animations/previews/003-nightsky.gif +0 -0
- package/huemagic/animations/previews/004-sunrise.gif +0 -0
- package/huemagic/animations/previews/005-sunset.gif +0 -0
- package/huemagic/animations/previews/006-fireplace.gif +0 -0
- package/huemagic/animations/previews/007-spring.gif +0 -0
- package/huemagic/animations/previews/008-autmn.gif +0 -0
- package/huemagic/animations/previews/009-winter.gif +0 -0
- package/huemagic/animations/previews/010-dirtyfog.gif +0 -0
- package/huemagic/animations/previews/011-lgbt.gif +0 -0
- package/huemagic/animations/previews/012-rasta.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/015-sos.gif +0 -0
- package/huemagic/animations/previews/016-milkyway.gif +0 -0
- package/huemagic/animations/previews/017-beach.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 +629 -320
- package/huemagic/hue-bridge.html +41 -21
- package/huemagic/hue-bridge.js +191 -398
- package/huemagic/hue-brightness.html +130 -49
- package/huemagic/hue-brightness.js +131 -35
- package/huemagic/hue-buttons.html +197 -0
- package/huemagic/hue-buttons.js +152 -0
- package/huemagic/hue-group.html +137 -60
- package/huemagic/hue-group.js +394 -368
- package/huemagic/hue-light.html +133 -59
- package/huemagic/hue-light.js +551 -337
- package/huemagic/hue-magic.html +91 -240
- package/huemagic/hue-magic.js +57 -27
- package/huemagic/hue-motion.html +128 -50
- package/huemagic/hue-motion.js +116 -87
- package/huemagic/hue-rules.html +121 -50
- package/huemagic/hue-rules.js +84 -64
- package/huemagic/hue-scene.html +96 -115
- package/huemagic/hue-scene.js +65 -96
- package/huemagic/hue-temperature.html +130 -49
- package/huemagic/hue-temperature.js +136 -32
- 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 +52 -38
- package/huemagic/locales/de/hue-brightness.json +7 -2
- package/huemagic/locales/de/hue-buttons.html +58 -0
- package/huemagic/locales/de/{hue-button.json → hue-buttons.json} +8 -8
- package/huemagic/locales/de/hue-group.html +46 -122
- package/huemagic/locales/de/hue-group.json +4 -2
- package/huemagic/locales/de/hue-light.html +68 -116
- package/huemagic/locales/de/hue-light.json +5 -2
- 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 +48 -44
- package/huemagic/locales/de/hue-motion.json +6 -3
- 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 +53 -36
- package/huemagic/locales/de/hue-temperature.json +7 -2
- 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 +52 -38
- package/huemagic/locales/en-US/hue-brightness.json +10 -3
- package/huemagic/locales/en-US/hue-buttons.html +58 -0
- package/huemagic/locales/en-US/{hue-button.json → hue-buttons.json} +11 -5
- package/huemagic/locales/en-US/hue-group.html +46 -122
- package/huemagic/locales/en-US/hue-group.json +7 -3
- package/huemagic/locales/en-US/hue-light.html +68 -116
- package/huemagic/locales/en-US/hue-light.json +8 -3
- 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 +48 -44
- package/huemagic/locales/en-US/hue-motion.json +9 -4
- 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 +53 -36
- package/huemagic/locales/en-US/hue-temperature.json +10 -3
- package/huemagic/utils/api.js +290 -0
- package/huemagic/utils/color.js +382 -0
- package/{utils → huemagic/utils}/merge.js +9 -6
- package/huemagic/utils/messages.js +447 -0
- package/package.json +11 -12
- package/examples/Hue Switch.json +0 -40
- package/examples/Hue Tap.json +0 -40
- package/huemagic/assets/anime.js +0 -8
- package/huemagic/hue-button.html +0 -119
- package/huemagic/hue-button.js +0 -94
- package/huemagic/hue-switch.html +0 -119
- package/huemagic/hue-switch.js +0 -113
- package/huemagic/hue-tap.html +0 -119
- package/huemagic/hue-tap.js +0 -74
- 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 -50
- package/huemagic/locales/de/hue-switch.html +0 -50
- package/huemagic/locales/de/hue-switch.json +0 -31
- package/huemagic/locales/de/hue-tap.html +0 -41
- package/huemagic/locales/de/hue-tap.json +0 -23
- package/huemagic/locales/en-US/hue-button.html +0 -50
- package/huemagic/locales/en-US/hue-switch.html +0 -50
- package/huemagic/locales/en-US/hue-switch.json +0 -31
- package/huemagic/locales/en-US/hue-tap.html +0 -41
- package/huemagic/locales/en-US/hue-tap.json +0 -23
- 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
|
-
|
|
113
|
-
|
|
180
|
+
if(!config.disableupdates)
|
|
181
|
+
{
|
|
182
|
+
scope.log("Keeping nodes up-to-date…");
|
|
114
183
|
|
|
184
|
+
// REFRESH STATES (SSE)
|
|
185
|
+
this.refreshStatesSSE();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
115
188
|
|
|
116
|
-
//
|
|
117
|
-
this.
|
|
189
|
+
// GET UPDATED STATES (SSE)
|
|
190
|
+
this.refreshStatesSSE = function()
|
|
118
191
|
{
|
|
119
|
-
|
|
120
|
-
|
|
192
|
+
scope.log("Subscribing to bridge events…");
|
|
193
|
+
API.subscribe(function(updates)
|
|
121
194
|
{
|
|
122
|
-
|
|
123
|
-
var updated = false;
|
|
124
|
-
var uniqueStatus = "";
|
|
195
|
+
const currentDateTime = dayjs().format();
|
|
125
196
|
|
|
126
|
-
|
|
197
|
+
for(let ressource of updates)
|
|
127
198
|
{
|
|
128
|
-
|
|
199
|
+
let id = ressource.id;
|
|
200
|
+
let type = ressource.type;
|
|
201
|
+
|
|
202
|
+
let previousState = false;
|
|
129
203
|
|
|
130
|
-
|
|
204
|
+
// HAS OWNER?
|
|
205
|
+
if(ressource["owner"])
|
|
131
206
|
{
|
|
132
|
-
|
|
207
|
+
let targetId = ressource["owner"]["rid"];
|
|
208
|
+
|
|
209
|
+
if(scope.ressources[targetId])
|
|
133
210
|
{
|
|
134
|
-
|
|
135
|
-
|
|
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
|
+
}
|
|
136
222
|
}
|
|
137
223
|
}
|
|
138
|
-
else
|
|
224
|
+
else if(scope.ressources[id])
|
|
139
225
|
{
|
|
140
|
-
|
|
141
|
-
|
|
226
|
+
// GET PREVIOUS STATE
|
|
227
|
+
previousState = scope.ressources[id];
|
|
142
228
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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)
|
|
148
238
|
{
|
|
149
|
-
if(
|
|
239
|
+
if(ressource["owner"])
|
|
150
240
|
{
|
|
151
|
-
|
|
152
|
-
|
|
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);
|
|
153
256
|
}
|
|
154
257
|
}
|
|
155
|
-
else
|
|
156
|
-
{
|
|
157
|
-
this.sensors[device.id] = uniqueStatus;
|
|
158
|
-
updated = true;
|
|
159
|
-
}
|
|
160
258
|
}
|
|
161
|
-
|
|
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])
|
|
162
291
|
{
|
|
163
|
-
|
|
164
|
-
|
|
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")
|
|
165
297
|
{
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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;
|
|
171
388
|
}
|
|
172
389
|
else
|
|
173
390
|
{
|
|
174
|
-
|
|
175
|
-
updated = true;
|
|
391
|
+
return false;
|
|
176
392
|
}
|
|
177
393
|
}
|
|
178
|
-
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))
|
|
179
405
|
{
|
|
180
|
-
|
|
181
|
-
uniqueStatus = rule.lastTriggered + rule.status;
|
|
406
|
+
const isGroup = (ressource["type"] == "room" || ressource["type"] == "zone" || ressource["type"] == "bridge_home");
|
|
182
407
|
|
|
183
|
-
|
|
408
|
+
// NORMAL DEVICES
|
|
409
|
+
if(!isGroup && ressource["services"] && ressource["services"][type])
|
|
184
410
|
{
|
|
185
|
-
|
|
411
|
+
for (const [serviceID, targetDevice] of Object.entries(ressource["services"][type]))
|
|
186
412
|
{
|
|
187
|
-
|
|
188
|
-
updated = true;
|
|
413
|
+
allFilteredRessources[rootID] = scope.get(type, rootID);
|
|
189
414
|
}
|
|
190
415
|
}
|
|
191
|
-
|
|
416
|
+
// GROUPED RESSOURCES
|
|
417
|
+
else if(isGroup && type === "group")
|
|
192
418
|
{
|
|
193
|
-
|
|
194
|
-
updated = true;
|
|
419
|
+
allFilteredRessources[rootID] = scope.get(type, rootID);
|
|
195
420
|
}
|
|
196
421
|
}
|
|
197
422
|
|
|
198
|
-
|
|
199
|
-
|
|
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])
|
|
200
434
|
{
|
|
201
|
-
|
|
435
|
+
const targetRessource = Object.values(scope.ressources[id]["services"][type])[0];
|
|
436
|
+
id = targetRessource.id;
|
|
202
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;
|
|
203
492
|
}
|
|
204
493
|
|
|
205
|
-
//
|
|
206
|
-
|
|
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
|
+
}
|
|
207
535
|
}
|
|
208
536
|
|
|
209
|
-
//
|
|
210
|
-
this.
|
|
537
|
+
// AUTO UPDATES?
|
|
538
|
+
this.autoUpdateFirmware = function()
|
|
211
539
|
{
|
|
212
|
-
|
|
540
|
+
if((config.autoupdates && config.autoupdates == true) || typeof config.autoupdates == 'undefined')
|
|
213
541
|
{
|
|
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
706
|
|
|
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;
|
|
707
|
+
// CONVERT TO ARRAY
|
|
708
|
+
targetRules = Object.values(targetRules);
|
|
352
709
|
|
|
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)
|
|
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
|
+
};
|