node-red-contrib-knx-ultimate 2.0.0 → 2.0.1
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 +4 -0
- package/KNXEngine/dptlib/index.js +1 -1
- package/KNXUltimate.code-workspace +11 -1
- package/img/hue.png +0 -0
- package/img/knx.png +0 -0
- package/nodes/hue-config.js +2 -6
- package/nodes/knxUltimateHueLight.html +290 -25
- package/nodes/knxUltimateHueLight.js +68 -13
- package/nodes/locales/en-US/knxUltimateHueLight.json +3 -3
- package/nodes/utils/hueColorConverter.js +237 -0
- package/nodes/utils/hueUtils.js +11 -0
- package/package.json +3 -2
- /package/nodes/locales/{de → de-disabled}/hue-config.html +0 -0
- /package/nodes/locales/{de → de-disabled}/hue-config.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimate-config.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimate-config.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimate.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimate.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateAlerter.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateAlerter.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateGlobalContext.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateGlobalContext.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateHueLight.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateHueLight.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateLoadControl.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateLoadControl.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateLogger.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateLogger.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateSceneController.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateSceneController.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateViewer.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateViewer.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateWatchDog.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateWatchDog.json +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
<p>
|
|
10
|
+
<b>Version 2.0.1</b> - June 2023<br/>
|
|
11
|
+
- NEW: more KNX group addresses in the HUE Light node<br/>
|
|
12
|
+
</p>
|
|
9
13
|
<p>
|
|
10
14
|
<b>Version 2.0.0</b> - June 2023<br/>
|
|
11
15
|
- NEW Added HUE Light node. More HUE nodes to come. Please feel free to try it.<br/>
|
|
@@ -95,7 +95,7 @@ dpts.populateAPDU = function (value, apdu, dptid) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
// generic APDU is assumed to convey an unsigned integer of arbitrary bitlength
|
|
98
|
-
if (dpt.basetype.hasOwnProperty('signedness') && dpt.basetype.signedness
|
|
98
|
+
if (dpt.basetype.hasOwnProperty('signedness') && dpt.basetype.signedness === 'signed') {
|
|
99
99
|
apdu.data.writeIntBE(tgtvalue, 0, nbytes)
|
|
100
100
|
} else {
|
|
101
101
|
apdu.data.writeUIntBE(tgtvalue, 0, nbytes)
|
|
@@ -9,5 +9,15 @@
|
|
|
9
9
|
"path": "../node-red-contrib-knx-ultimate.wiki"
|
|
10
10
|
}
|
|
11
11
|
],
|
|
12
|
-
"settings": {
|
|
12
|
+
"settings": {
|
|
13
|
+
|
|
14
|
+
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint",
|
|
15
|
+
"editor.formatOnPaste": false, // required
|
|
16
|
+
"editor.formatOnType": false, // required
|
|
17
|
+
"editor.formatOnSave": true, // optional
|
|
18
|
+
"editor.formatOnSaveMode": "file", // required to format on save
|
|
19
|
+
"files.autoSave": "onFocusChange", // optional but recommended
|
|
20
|
+
"vs-code-prettier-eslint.prettierLast": "false" // set as "true" to run 'prettier' last not first
|
|
21
|
+
|
|
22
|
+
}
|
|
13
23
|
}
|
package/img/hue.png
ADDED
|
Binary file
|
package/img/knx.png
ADDED
|
Binary file
|
package/nodes/hue-config.js
CHANGED
|
@@ -81,7 +81,7 @@ module.exports = (RED) => {
|
|
|
81
81
|
|
|
82
82
|
// Init HUE Utility
|
|
83
83
|
node.hueManager = new hueClass(node.host, node.credentials.username, node.credentials.clientkey, config.bridgeid)
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
// Event clip V2
|
|
86
86
|
node.hueManager.on('event', _event => {
|
|
87
87
|
node.nodeClients.forEach(_oClient => {
|
|
@@ -110,11 +110,7 @@ module.exports = (RED) => {
|
|
|
110
110
|
}
|
|
111
111
|
})
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
node.setLightState = (/** @type {string} */ _LightID, /** @type {any} */ _state = { on: true }) => {
|
|
115
|
-
node.hueManager.setLightState(_LightID, _state)
|
|
116
|
-
}
|
|
117
|
-
|
|
113
|
+
|
|
118
114
|
node.addClient = (_Node) => {
|
|
119
115
|
// Check if node already exists
|
|
120
116
|
if (node.nodeClients.filter(x => x.id === _Node.id).length === 0) {
|
|
@@ -20,6 +20,22 @@
|
|
|
20
20
|
GALightDIM: { value: "" },
|
|
21
21
|
dptLightDIM: { value: "" },
|
|
22
22
|
|
|
23
|
+
nameLightColor: { value: "" },
|
|
24
|
+
GALightColor: { value: "" },
|
|
25
|
+
dptLightColor: { value: "" },
|
|
26
|
+
|
|
27
|
+
nameLightColorState: { value: "" },
|
|
28
|
+
GALightColorState: { value: "" },
|
|
29
|
+
dptLightColorState: { value: "" },
|
|
30
|
+
|
|
31
|
+
nameLightBrightness: { value: "" },
|
|
32
|
+
GALightBrightness: { value: "" },
|
|
33
|
+
dptLightBrightness: { value: "" },
|
|
34
|
+
|
|
35
|
+
nameLightBrightnessState: { value: "" },
|
|
36
|
+
GALightBrightnessState: { value: "" },
|
|
37
|
+
dptLightBrightnessState: { value: "" },
|
|
38
|
+
|
|
23
39
|
hueLight: { value: "" }
|
|
24
40
|
},
|
|
25
41
|
inputs: 0,
|
|
@@ -69,9 +85,11 @@
|
|
|
69
85
|
// ########################
|
|
70
86
|
$.getJSON('knxUltimateDpts', (data) => {
|
|
71
87
|
data.forEach(dpt => {
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
|
|
88
|
+
if (dpt.value.startsWith("1.")) {
|
|
89
|
+
$("#node-input-dptLightSwitch").append($("<option></option>")
|
|
90
|
+
.attr("value", dpt.value)
|
|
91
|
+
.text(dpt.text))
|
|
92
|
+
}
|
|
75
93
|
});
|
|
76
94
|
$("#node-input-dptLightSwitch").val(this.dptLightSwitch)
|
|
77
95
|
})
|
|
@@ -84,7 +102,7 @@
|
|
|
84
102
|
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
85
103
|
response($.map(data, function (value, key) {
|
|
86
104
|
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
87
|
-
if (fullSearch(sSearch, request.term)) {
|
|
105
|
+
if (fullSearch(sSearch, request.term + " 1.")) {
|
|
88
106
|
return {
|
|
89
107
|
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
90
108
|
value: value.ga // Value
|
|
@@ -102,7 +120,6 @@
|
|
|
102
120
|
} catch (error) {
|
|
103
121
|
}
|
|
104
122
|
$('#node-input-nameLightSwitch').val(sDevName);
|
|
105
|
-
$('#node-input-name').val(sDevName);
|
|
106
123
|
var optVal = $("#node-input-dptLightSwitch option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
107
124
|
// Select the option value
|
|
108
125
|
$("#node-input-dptLightSwitch").val(optVal);
|
|
@@ -116,10 +133,13 @@
|
|
|
116
133
|
// ########################
|
|
117
134
|
$.getJSON('knxUltimateDpts', (data) => {
|
|
118
135
|
data.forEach(dpt => {
|
|
119
|
-
|
|
120
|
-
.
|
|
121
|
-
|
|
136
|
+
if (dpt.value.startsWith("1.")) {
|
|
137
|
+
$("#node-input-dptLightState").append($("<option></option>")
|
|
138
|
+
.attr("value", dpt.value)
|
|
139
|
+
.text(dpt.text))
|
|
140
|
+
}
|
|
122
141
|
});
|
|
142
|
+
|
|
123
143
|
$("#node-input-dptLightState").val(this.dptLightState)
|
|
124
144
|
})
|
|
125
145
|
|
|
@@ -130,7 +150,7 @@
|
|
|
130
150
|
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
131
151
|
response($.map(data, function (value, key) {
|
|
132
152
|
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
133
|
-
if (fullSearch(sSearch, request.term)) {
|
|
153
|
+
if (fullSearch(sSearch, request.term + " 1.")) {
|
|
134
154
|
return {
|
|
135
155
|
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
136
156
|
value: value.ga // Value
|
|
@@ -159,11 +179,15 @@
|
|
|
159
179
|
// ########################
|
|
160
180
|
$.getJSON('knxUltimateDpts', (data) => {
|
|
161
181
|
data.forEach(dpt => {
|
|
162
|
-
|
|
163
|
-
.
|
|
164
|
-
|
|
182
|
+
if (dpt.value === "3.007") {
|
|
183
|
+
$("#node-input-dptLightDIM").append($("<option></option>")
|
|
184
|
+
.attr("value", dpt.value)
|
|
185
|
+
.text(dpt.text))
|
|
186
|
+
}
|
|
165
187
|
});
|
|
188
|
+
|
|
166
189
|
$("#node-input-dptLightDIM").val(this.dptLightDIM)
|
|
190
|
+
|
|
167
191
|
})
|
|
168
192
|
|
|
169
193
|
// Autocomplete suggestion with ETS csv File
|
|
@@ -173,7 +197,7 @@
|
|
|
173
197
|
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
174
198
|
response($.map(data, function (value, key) {
|
|
175
199
|
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
176
|
-
if (fullSearch(sSearch, request.term)) {
|
|
200
|
+
if (fullSearch(sSearch, request.term + " 3.007")) {
|
|
177
201
|
return {
|
|
178
202
|
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
179
203
|
value: value.ga // Value
|
|
@@ -198,6 +222,174 @@
|
|
|
198
222
|
});
|
|
199
223
|
|
|
200
224
|
|
|
225
|
+
// DPT dptLightColor and dptLightColorStateƒ
|
|
226
|
+
// ########################
|
|
227
|
+
$.getJSON('knxUltimateDpts', (data) => {
|
|
228
|
+
data.forEach(dpt => {
|
|
229
|
+
if (dpt.value === "232.600") {
|
|
230
|
+
$("#node-input-dptLightColor").append($("<option></option>")
|
|
231
|
+
.attr("value", dpt.value)
|
|
232
|
+
.text(dpt.text))
|
|
233
|
+
$("#node-input-dptLightColorState").append($("<option></option>")
|
|
234
|
+
.attr("value", dpt.value)
|
|
235
|
+
.text(dpt.text))
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
$("#node-input-dptLightColor").val(this.dptLightColor)
|
|
240
|
+
$("#node-input-dptLightColorState").val(this.dptLightColorState)
|
|
241
|
+
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// Autocomplete suggestion with ETS csv File
|
|
245
|
+
$("#node-input-GALightColor").autocomplete({
|
|
246
|
+
minLength: 1,
|
|
247
|
+
source: function (request, response) {
|
|
248
|
+
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
249
|
+
response($.map(data, function (value, key) {
|
|
250
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
251
|
+
if (fullSearch(sSearch, request.term + " 232.600")) {
|
|
252
|
+
return {
|
|
253
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
254
|
+
value: value.ga // Value
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}));
|
|
260
|
+
});
|
|
261
|
+
}, select: function (event, ui) {
|
|
262
|
+
// Sets Datapoint and device name automatically
|
|
263
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
264
|
+
try {
|
|
265
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
266
|
+
} catch (error) {
|
|
267
|
+
}
|
|
268
|
+
$('#node-input-nameLightColor').val(sDevName);
|
|
269
|
+
var optVal = $("#node-input-dptLightColor option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
270
|
+
// Select the option value
|
|
271
|
+
$("#node-input-dptLightColor").val(optVal);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Autocomplete suggestion with ETS csv File
|
|
276
|
+
$("#node-input-GALightColorState").autocomplete({
|
|
277
|
+
minLength: 1,
|
|
278
|
+
source: function (request, response) {
|
|
279
|
+
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
280
|
+
response($.map(data, function (value, key) {
|
|
281
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
282
|
+
if (fullSearch(sSearch, request.term + " 232.600")) {
|
|
283
|
+
return {
|
|
284
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
285
|
+
value: value.ga // Value
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}));
|
|
291
|
+
});
|
|
292
|
+
}, select: function (event, ui) {
|
|
293
|
+
// Sets Datapoint and device name automatically
|
|
294
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
295
|
+
try {
|
|
296
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
}
|
|
299
|
+
$('#node-input-nameLightColorState').val(sDevName);
|
|
300
|
+
var optVal = $("#node-input-dptLightColorState option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
301
|
+
// Select the option value
|
|
302
|
+
$("#node-input-dptLightColorState").val(optVal);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
// DPT dptLightBrightness and dptLightBrightnessState
|
|
309
|
+
// ########################
|
|
310
|
+
$.getJSON('knxUltimateDpts', (data) => {
|
|
311
|
+
data.forEach(dpt => {
|
|
312
|
+
if (dpt.value === "5.001") {
|
|
313
|
+
$("#node-input-dptLightBrightness").append($("<option></option>")
|
|
314
|
+
.attr("value", dpt.value)
|
|
315
|
+
.text(dpt.text))
|
|
316
|
+
$("#node-input-dptLightBrightnessState").append($("<option></option>")
|
|
317
|
+
.attr("value", dpt.value)
|
|
318
|
+
.text(dpt.text))
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
$("#node-input-dptLightBrightness").val(this.dptLightBrightness)
|
|
323
|
+
$("#node-input-dptLightBrightnessState").val(this.dptLightBrightnessState)
|
|
324
|
+
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
// Autocomplete suggestion with ETS csv File
|
|
328
|
+
$("#node-input-GALightBrightness").autocomplete({
|
|
329
|
+
minLength: 1,
|
|
330
|
+
source: function (request, response) {
|
|
331
|
+
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
332
|
+
response($.map(data, function (value, key) {
|
|
333
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
334
|
+
if (fullSearch(sSearch, request.term + " 5.001")) {
|
|
335
|
+
return {
|
|
336
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
337
|
+
value: value.ga // Value
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}));
|
|
343
|
+
});
|
|
344
|
+
}, select: function (event, ui) {
|
|
345
|
+
// Sets Datapoint and device name automatically
|
|
346
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
347
|
+
try {
|
|
348
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
349
|
+
} catch (error) {
|
|
350
|
+
}
|
|
351
|
+
$('#node-input-nameLightBrightness').val(sDevName);
|
|
352
|
+
var optVal = $("#node-input-dptLightBrightness option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
353
|
+
// Select the option value
|
|
354
|
+
$("#node-input-dptLightBrightness").val(optVal);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Autocomplete suggestion with ETS csv File
|
|
359
|
+
$("#node-input-GALightBrightnessState").autocomplete({
|
|
360
|
+
minLength: 1,
|
|
361
|
+
source: function (request, response) {
|
|
362
|
+
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
363
|
+
response($.map(data, function (value, key) {
|
|
364
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
365
|
+
if (fullSearch(sSearch, request.term + " 5.001")) {
|
|
366
|
+
return {
|
|
367
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
368
|
+
value: value.ga // Value
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
}));
|
|
374
|
+
});
|
|
375
|
+
}, select: function (event, ui) {
|
|
376
|
+
// Sets Datapoint and device name automatically
|
|
377
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
378
|
+
try {
|
|
379
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
380
|
+
} catch (error) {
|
|
381
|
+
}
|
|
382
|
+
$('#node-input-nameLightBrightnessState').val(sDevName);
|
|
383
|
+
var optVal = $("#node-input-dptLightBrightnessState option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
384
|
+
// Select the option value
|
|
385
|
+
$("#node-input-dptLightBrightnessState").val(optVal);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
201
393
|
// Autocomplete suggestion with HUE Lights
|
|
202
394
|
$("#node-input-hueLight").autocomplete({
|
|
203
395
|
minLength: 1,
|
|
@@ -220,6 +412,7 @@
|
|
|
220
412
|
// Sets the field
|
|
221
413
|
var sDevName = ui.item.label;
|
|
222
414
|
$('#node-input-hueLight').val(sDevName);
|
|
415
|
+
$('#node-input-name').val(sDevName.split("#")[0]);
|
|
223
416
|
}
|
|
224
417
|
});
|
|
225
418
|
|
|
@@ -282,11 +475,12 @@
|
|
|
282
475
|
</div>
|
|
283
476
|
|
|
284
477
|
<br/>
|
|
478
|
+
|
|
479
|
+
<!-- <p> <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/knx.png' width='32px'> -> <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/hue.png' width='32px'></p> -->
|
|
480
|
+
|
|
285
481
|
<p>
|
|
286
|
-
<b>
|
|
482
|
+
<b>KNX COMMANDS -> TO PHILIPS HUE</b>
|
|
287
483
|
</p>
|
|
288
|
-
|
|
289
|
-
<div id="GAandDPT">
|
|
290
484
|
<div class="form-row">
|
|
291
485
|
<label for="node-input-nameLightSwitch" style="width:100px;"><i class="fa fa-play-circle-o"></i> <span data-i18n="knxUltimateHueLight.node-input-nameLightSwitch"></span></label>
|
|
292
486
|
|
|
@@ -300,6 +494,55 @@
|
|
|
300
494
|
<label for="node-input-nameLightSwitch" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
301
495
|
<input type="text" id="node-input-nameLightSwitch" style="width:200px;margin-left: 5px; text-align: left;">
|
|
302
496
|
</div>
|
|
497
|
+
</div>
|
|
498
|
+
<div class="form-row">
|
|
499
|
+
<label for="node-input-nameLightDIM" style="width:100px;"><i class="fa fa-play-circle-o"></i>Dimming</label>
|
|
500
|
+
|
|
501
|
+
<label for="node-input-GALightDIM" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
|
|
502
|
+
<input type="text" id="node-input-GALightDIM" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
503
|
+
|
|
504
|
+
<label for="node-input-dptLightDIM" style="width:40px; margin-left: 0px; text-align: right;">
|
|
505
|
+
<span data-i18n="knxUltimateHueLight.node-input-dptLightState"></span> </label>
|
|
506
|
+
<select id="node-input-dptLightDIM" style="width:140px;"></select>
|
|
507
|
+
|
|
508
|
+
<label for="node-input-nameLightDIM" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
509
|
+
<input type="text" id="node-input-nameLightDIM" style="width:200px;margin-left: 5px; text-align: left;">
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="form-row">
|
|
513
|
+
<label for="node-input-nameLightColor" style="width:100px;"><i class="fa fa-play-circle-o"></i> Color</label>
|
|
514
|
+
|
|
515
|
+
<label for="node-input-GALightColor" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
|
|
516
|
+
<input type="text" id="node-input-GALightColor" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
517
|
+
|
|
518
|
+
<label for="node-input-dptLightColor" style="width:40px; margin-left: 0px; text-align: right;">
|
|
519
|
+
<span data-i18n="knxUltimateHueLight.node-input-dptLightState"></span> </label>
|
|
520
|
+
<select id="node-input-dptLightColor" style="width:140px;"></select>
|
|
521
|
+
|
|
522
|
+
<label for="node-input-nameLightColor" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
523
|
+
<input type="text" id="node-input-nameLightColor" style="width:200px;margin-left: 5px; text-align: left;">
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
<div class="form-row">
|
|
528
|
+
<label for="node-input-nameLightBrightness" style="width:100px;"><i class="fa fa-play-circle-o"></i> Brightness</label>
|
|
529
|
+
|
|
530
|
+
<label for="node-input-GALightBrightness" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
|
|
531
|
+
<input type="text" id="node-input-GALightBrightness" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
532
|
+
|
|
533
|
+
<label for="node-input-dptLightBrightness" style="width:40px; margin-left: 0px; text-align: right;">
|
|
534
|
+
<span data-i18n="knxUltimateHueLight.node-input-dptLightState"></span> </label>
|
|
535
|
+
<select id="node-input-dptLightBrightness" style="width:140px;"></select>
|
|
536
|
+
|
|
537
|
+
<label for="node-input-nameLightBrightness" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
538
|
+
<input type="text" id="node-input-nameLightBrightness" style="width:200px;margin-left: 5px; text-align: left;">
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<br/>
|
|
542
|
+
<p>
|
|
543
|
+
<b>PHILIPS HUE STATES -> TO KNX</b>
|
|
544
|
+
</p>
|
|
545
|
+
|
|
303
546
|
<div class="form-row">
|
|
304
547
|
<label for="node-input-nameLightState" style="width:100px;"><i class="fa fa-play-circle-o"></i> <span data-i18n="knxUltimateHueLight.node-input-nameLightState"></span></label>
|
|
305
548
|
|
|
@@ -314,19 +557,41 @@
|
|
|
314
557
|
<input type="text" id="node-input-nameLightState" style="width:200px;margin-left: 5px; text-align: left;">
|
|
315
558
|
</div>
|
|
316
559
|
<div class="form-row">
|
|
317
|
-
<label for="node-input-
|
|
560
|
+
<label for="node-input-nameLightColorState" style="width:100px;"><i class="fa fa-play-circle-o"></i> Color State</label>
|
|
318
561
|
|
|
319
|
-
<label for="node-input-
|
|
320
|
-
<input type="text" id="node-input-
|
|
562
|
+
<label for="node-input-GALightColorState" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
|
|
563
|
+
<input type="text" id="node-input-GALightColorState" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
321
564
|
|
|
322
|
-
<label for="node-input-
|
|
323
|
-
<span data-i18n="knxUltimateHueLight.node-input-
|
|
324
|
-
<select id="node-input-
|
|
565
|
+
<label for="node-input-dptLightColorState" style="width:40px; margin-left: 0px; text-align: right;">
|
|
566
|
+
<span data-i18n="knxUltimateHueLight.node-input-dptLightState"></span> </label>
|
|
567
|
+
<select id="node-input-dptLightColorState" style="width:140px;"></select>
|
|
325
568
|
|
|
326
|
-
<label for="node-input-
|
|
327
|
-
<input type="text" id="node-input-
|
|
569
|
+
<label for="node-input-nameLightColorState" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
570
|
+
<input type="text" id="node-input-nameLightColorState" style="width:200px;margin-left: 5px; text-align: left;">
|
|
328
571
|
</div>
|
|
329
|
-
</div>
|
|
572
|
+
</div>
|
|
573
|
+
<div class="form-row">
|
|
574
|
+
<label for="node-input-nameLightBrightnessState" style="width:100px;"><i class="fa fa-play-circle-o"></i> Brightness State</label>
|
|
575
|
+
|
|
576
|
+
<label for="node-input-GALightBrightnessState" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
|
|
577
|
+
<input type="text" id="node-input-GALightBrightnessState" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
578
|
+
|
|
579
|
+
<label for="node-input-dptLightBrightnessState" style="width:40px; margin-left: 0px; text-align: right;">
|
|
580
|
+
<span data-i18n="knxUltimateHueLight.node-input-dptLightState"></span> </label>
|
|
581
|
+
<select id="node-input-dptLightBrightnessState" style="width:140px;"></select>
|
|
582
|
+
|
|
583
|
+
<label for="node-input-nameLightBrightnessState" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
584
|
+
<input type="text" id="node-input-nameLightBrightnessState" style="width:200px;margin-left: 5px; text-align: left;">
|
|
585
|
+
</div>
|
|
586
|
+
|
|
587
|
+
<br/>
|
|
588
|
+
<br/>
|
|
589
|
+
<br/>
|
|
590
|
+
<br/>
|
|
591
|
+
<br/>
|
|
592
|
+
<br/>
|
|
593
|
+
<br/>
|
|
594
|
+
<br/>
|
|
330
595
|
|
|
331
596
|
|
|
332
597
|
</script>
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
module.exports = function (RED) {
|
|
2
2
|
const dptlib = require('./../KNXEngine/dptlib')
|
|
3
|
+
const hueColorConverter = require('./utils/hueColorConverter')
|
|
3
4
|
|
|
4
5
|
|
|
6
|
+
async function getLightState(node, config) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
try {
|
|
9
|
+
node.serverHue.hueManager.getLight(config.hueLight.split('#')[1]).then(ret => {
|
|
10
|
+
node.currentHUELight = ret[0]
|
|
11
|
+
resolve(ret)
|
|
12
|
+
})
|
|
13
|
+
} catch (error) {
|
|
14
|
+
reject(error)
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
function knxUltimateHueLight(config) {
|
|
6
20
|
RED.nodes.createNode(this, config)
|
|
7
21
|
const node = this
|
|
@@ -27,6 +41,16 @@ module.exports = function (RED) {
|
|
|
27
41
|
node.formatnegativevalue = 'leave'
|
|
28
42
|
node.formatdecimalsvalue = 2
|
|
29
43
|
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
// Read the state of the light and store it in the holding object
|
|
47
|
+
try {
|
|
48
|
+
getLightState(node, config)
|
|
49
|
+
} catch (error) {
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
30
54
|
// Used to call the status update from the config node.
|
|
31
55
|
node.setNodeStatus = ({ fill, shape, text, payload }) => {
|
|
32
56
|
|
|
@@ -34,38 +58,69 @@ module.exports = function (RED) {
|
|
|
34
58
|
|
|
35
59
|
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
36
60
|
node.handleSend = msg => {
|
|
37
|
-
let state
|
|
61
|
+
let state = {}
|
|
38
62
|
try {
|
|
39
63
|
switch (msg.knx.destination) {
|
|
40
64
|
case config.GALightSwitch:
|
|
41
65
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch))
|
|
42
66
|
state = msg.payload === true ? { on: { on: true } } : { on: { on: false } }
|
|
43
|
-
node.serverHue.setLightState(config.hueLight.split('#')[1], state)
|
|
67
|
+
node.serverHue.hueManager.setLightState(config.hueLight.split('#')[1], state)
|
|
44
68
|
break
|
|
45
69
|
case config.GALightDIM:
|
|
46
70
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightDIM))
|
|
47
71
|
state = msg.payload.decr_incr === 1 ? { dimming_delta: { action: 'up', brightness_delta: 20 } } : { dimming_delta: { action: 'down', brightness_delta: 20 } }
|
|
48
|
-
node.serverHue.setLightState(config.hueLight.split('#')[1], state)
|
|
72
|
+
node.serverHue.hueManager.setLightState(config.hueLight.split('#')[1], state)
|
|
73
|
+
break
|
|
74
|
+
case config.GALightBrightness:
|
|
75
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBrightness))
|
|
76
|
+
state = { dimming: { brightness: msg.payload } }
|
|
77
|
+
node.serverHue.hueManager.setLightState(config.hueLight.split('#')[1], state)
|
|
78
|
+
break
|
|
79
|
+
case config.GALightColor:
|
|
80
|
+
// Behavior like ISE HUE CONNECT, by setting the brightness and on/off as well
|
|
81
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightColor))
|
|
82
|
+
const gamut = node.currentHUELight.color.gamut_type || null
|
|
83
|
+
const retXY = hueColorConverter.ColorConverter.rgbToXy(msg.payload.red, msg.payload.green, msg.payload.blue, gamut)
|
|
84
|
+
const bright = hueColorConverter.ColorConverter.getBrightnessFromRGB(msg.payload.red, msg.payload.green, msg.payload.blue)
|
|
85
|
+
bright > 0 ? state = { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : state = { on: { on: false } }
|
|
86
|
+
node.serverHue.hueManager.setLightState(config.hueLight.split('#')[1], state)
|
|
49
87
|
break
|
|
50
88
|
default:
|
|
51
89
|
break
|
|
52
90
|
}
|
|
53
91
|
} catch (error) {
|
|
54
|
-
|
|
92
|
+
node.status({ fill: 'red', shape: 'dot', text: 'KNX->HUE error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
55
93
|
}
|
|
56
|
-
//node.exposedGAs.push({ address: msg.knx.destination, addressRAW: sAddressRAW, dpt: msg.knx.dpt, payload: msg.payload, devicename: sDeviceName, lastupdate: new Date(), rawPayload: 'HEX Raw: ' + msg.knx.rawValue.toString('hex') || '?', payloadmeasureunit: (msg.payloadmeasureunit !== 'unknown' ? ' ' + msg.payloadmeasureunit : '') })
|
|
94
|
+
// node.exposedGAs.push({ address: msg.knx.destination, addressRAW: sAddressRAW, dpt: msg.knx.dpt, payload: msg.payload, devicename: sDeviceName, lastupdate: new Date(), rawPayload: 'HEX Raw: ' + msg.knx.rawValue.toString('hex') || '?', payloadmeasureunit: (msg.payloadmeasureunit !== 'unknown' ? ' ' + msg.payloadmeasureunit : '') })
|
|
57
95
|
}
|
|
58
96
|
|
|
59
|
-
|
|
60
97
|
node.handleSendHUE = _event => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
98
|
+
try {
|
|
99
|
+
if (_event.id === config.hueLight.split('#')[1]) {
|
|
100
|
+
let knxMsgPayload = {}
|
|
101
|
+
if (_event.hasOwnProperty('on')) {
|
|
102
|
+
knxMsgPayload.ga = config.GALightState
|
|
103
|
+
knxMsgPayload.dpt = config.dptLightState
|
|
104
|
+
knxMsgPayload.payload = _event.on.on
|
|
105
|
+
}
|
|
106
|
+
if (_event.hasOwnProperty('color')) {
|
|
107
|
+
knxMsgPayload.ga = config.GALightColorState
|
|
108
|
+
knxMsgPayload.dpt = config.dptLightColorState
|
|
109
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBriToRgb(_event.color.xy.x, _event.color.xy.y, node.currentHUELight.dimming.brightness)
|
|
110
|
+
}
|
|
111
|
+
if (_event.hasOwnProperty('dimming')) {
|
|
112
|
+
knxMsgPayload.ga = config.GALightBrightnessState
|
|
113
|
+
knxMsgPayload.dpt = config.dptLightBrightnessState
|
|
114
|
+
knxMsgPayload.payload = _event.dimming.brightness
|
|
115
|
+
}
|
|
116
|
+
// Send to KNX bus
|
|
117
|
+
if (knxMsgPayload.ga !== undefined) {
|
|
118
|
+
node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX State ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
119
|
+
if (config.GALightState !== '') node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
120
|
+
}
|
|
68
121
|
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
69
124
|
}
|
|
70
125
|
}
|
|
71
126
|
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"helplink": " <i class=\"fa fa-question-circle\"></i> <a target=\"_blank\" href=\"https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/en-hue-configuration\"><u>Configuration</u></a>",
|
|
4
4
|
"title": "HUE node",
|
|
5
5
|
"node-input-name": "Name",
|
|
6
|
-
"node-input-nameLightSwitch": "Switch
|
|
6
|
+
"node-input-nameLightSwitch": "Switch",
|
|
7
7
|
"node-input-GALightSwitch": "GA",
|
|
8
8
|
"node-input-dptLightSwitch": "dpt",
|
|
9
|
-
"node-input-nameLightState": "State
|
|
9
|
+
"node-input-nameLightState": "Switch State",
|
|
10
10
|
"node-input-GALightState": "GA",
|
|
11
11
|
"node-input-dptLightState": "dpt",
|
|
12
|
-
"node-input-hueLight": "HUE
|
|
12
|
+
"node-input-hueLight": "HUE Light"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
const convert = require('color-convert')
|
|
2
|
+
class ColorConverter {
|
|
3
|
+
static getGamutRanges() {
|
|
4
|
+
const gamutA = {
|
|
5
|
+
red: [0.704, 0.296],
|
|
6
|
+
green: [0.2151, 0.7106],
|
|
7
|
+
blue: [0.138, 0.08]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const gamutB = {
|
|
11
|
+
red: [0.675, 0.322],
|
|
12
|
+
green: [0.409, 0.518],
|
|
13
|
+
blue: [0.167, 0.04]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const gamutC = {
|
|
17
|
+
red: [0.692, 0.308],
|
|
18
|
+
green: [0.17, 0.7],
|
|
19
|
+
blue: [0.153, 0.048]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaultGamut = {
|
|
23
|
+
red: [1.0, 0],
|
|
24
|
+
green: [0.0, 1.0],
|
|
25
|
+
blue: [0.0, 0.0]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { gamutA, gamutB, gamutC, default: defaultGamut }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static getLightColorGamutRange(gamutTypeABC = null) {
|
|
32
|
+
const ranges = ColorConverter.getGamutRanges()
|
|
33
|
+
const gamutA = ranges.gamutA
|
|
34
|
+
const gamutB = ranges.gamutB
|
|
35
|
+
const gamutC = ranges.gamutC
|
|
36
|
+
|
|
37
|
+
const philipsModels = {
|
|
38
|
+
A: gamutA,
|
|
39
|
+
B: gamutB,
|
|
40
|
+
C: gamutC
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (philipsModels[gamutTypeABC]) {
|
|
44
|
+
return philipsModels[gamutTypeABC]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return ranges.default
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static rgbToXy(red, green, blue, gamutTypeABC = null) {
|
|
51
|
+
function getGammaCorrectedValue(value) {
|
|
52
|
+
return (value > 0.04045) ? Math.pow((value + 0.055) / (1.0 + 0.055), 2.4) : (value / 12.92)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const colorGamut = ColorConverter.getLightColorGamutRange(gamutTypeABC)
|
|
56
|
+
|
|
57
|
+
red = parseFloat(red / 255)
|
|
58
|
+
green = parseFloat(green / 255)
|
|
59
|
+
blue = parseFloat(blue / 255)
|
|
60
|
+
|
|
61
|
+
red = getGammaCorrectedValue(red)
|
|
62
|
+
green = getGammaCorrectedValue(green)
|
|
63
|
+
blue = getGammaCorrectedValue(blue)
|
|
64
|
+
|
|
65
|
+
const x = red * 0.649926 + green * 0.103455 + blue * 0.197109
|
|
66
|
+
const y = red * 0.234327 + green * 0.743075 + blue * 0.022598
|
|
67
|
+
const z = red * 0.0000000 + green * 0.053077 + blue * 1.035763
|
|
68
|
+
|
|
69
|
+
let xy = {
|
|
70
|
+
x: x / (x + y + z),
|
|
71
|
+
y: y / (x + y + z)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!ColorConverter.xyIsInGamutRange(xy, colorGamut)) {
|
|
75
|
+
xy = ColorConverter.getClosestColor(xy, colorGamut)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return xy
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static getBrightnessFromRGB(red, green, blue) {
|
|
82
|
+
const hsv = convert.rgb.hsv(red, green, blue)
|
|
83
|
+
const brightness = hsv[2]
|
|
84
|
+
return brightness
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static convert_1_255_ToPercentage(number) {
|
|
88
|
+
const percentage = (number / 255) * 100
|
|
89
|
+
return percentage
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static xyIsInGamutRange(xy, gamut) {
|
|
93
|
+
gamut = gamut || ColorConverter.getGamutRanges().gamutC
|
|
94
|
+
if (Array.isArray(xy)) {
|
|
95
|
+
xy = {
|
|
96
|
+
x: xy[0],
|
|
97
|
+
y: xy[1]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const v0 = [gamut.blue[0] - gamut.red[0], gamut.blue[1] - gamut.red[1]]
|
|
102
|
+
const v1 = [gamut.green[0] - gamut.red[0], gamut.green[1] - gamut.red[1]]
|
|
103
|
+
const v2 = [xy.x - gamut.red[0], xy.y - gamut.red[1]]
|
|
104
|
+
|
|
105
|
+
const dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1])
|
|
106
|
+
const dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1])
|
|
107
|
+
const dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1])
|
|
108
|
+
const dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1])
|
|
109
|
+
const dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1])
|
|
110
|
+
|
|
111
|
+
const invDenom = 1 / (dot00 * dot11 - dot01 * dot01)
|
|
112
|
+
|
|
113
|
+
const u = (dot11 * dot02 - dot01 * dot12) * invDenom
|
|
114
|
+
const v = (dot00 * dot12 - dot01 * dot02) * invDenom
|
|
115
|
+
|
|
116
|
+
return ((u >= 0) && (v >= 0) && (u + v < 1))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static getClosestColor(xy, gamut) {
|
|
120
|
+
function getLineDistance(pointA, pointB) {
|
|
121
|
+
return Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getClosestPoint(xy, pointA, pointB) {
|
|
125
|
+
const xy2a = [xy.x - pointA.x, xy.y - pointA.y]
|
|
126
|
+
const a2b = [pointB.x - pointA.x, pointB.y - pointA.y]
|
|
127
|
+
const a2bSqr = Math.pow(a2b[0], 2) + Math.pow(a2b[1], 2)
|
|
128
|
+
const xy2a_dot_a2b = xy2a[0] * a2b[0] + xy2a[1] * a2b[1]
|
|
129
|
+
const t = xy2a_dot_a2b / a2bSqr
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
x: pointA.x + a2b[0] * t,
|
|
133
|
+
y: pointA.y + a2b[1] * t
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const greenBlue = {
|
|
138
|
+
a: {
|
|
139
|
+
x: gamut.green[0],
|
|
140
|
+
y: gamut.green[1]
|
|
141
|
+
},
|
|
142
|
+
b: {
|
|
143
|
+
x: gamut.blue[0],
|
|
144
|
+
y: gamut.blue[1]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const greenRed = {
|
|
149
|
+
a: {
|
|
150
|
+
x: gamut.green[0],
|
|
151
|
+
y: gamut.green[1]
|
|
152
|
+
},
|
|
153
|
+
b: {
|
|
154
|
+
x: gamut.red[0],
|
|
155
|
+
y: gamut.red[1]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const blueRed = {
|
|
160
|
+
a: {
|
|
161
|
+
x: gamut.red[0],
|
|
162
|
+
y: gamut.red[1]
|
|
163
|
+
},
|
|
164
|
+
b: {
|
|
165
|
+
x: gamut.blue[0],
|
|
166
|
+
y: gamut.blue[1]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const closestColorPoints = {
|
|
171
|
+
greenBlue: getClosestPoint(xy, greenBlue.a, greenBlue.b),
|
|
172
|
+
greenRed: getClosestPoint(xy, greenRed.a, greenRed.b),
|
|
173
|
+
blueRed: getClosestPoint(xy, blueRed.a, blueRed.b)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const distance = {
|
|
177
|
+
greenBlue: getLineDistance(xy, closestColorPoints.greenBlue),
|
|
178
|
+
greenRed: getLineDistance(xy, closestColorPoints.greenRed),
|
|
179
|
+
blueRed: getLineDistance(xy, closestColorPoints.blueRed)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let closestDistance
|
|
183
|
+
let closestColor
|
|
184
|
+
for (const i in distance) {
|
|
185
|
+
if (distance.hasOwnProperty(i)) {
|
|
186
|
+
if (!closestDistance) {
|
|
187
|
+
closestDistance = distance[i]
|
|
188
|
+
closestColor = i
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (closestDistance > distance[i]) {
|
|
192
|
+
closestDistance = distance[i]
|
|
193
|
+
closestColor = i
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return closestColorPoints[closestColor]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static xyBriToRgb(x, y, bri) {
|
|
201
|
+
function getReversedGammaCorrectedValue(value) {
|
|
202
|
+
return value <= 0.0031308 ? 12.92 * value : (1.0 + 0.055) * Math.pow(value, (1.0 / 2.4)) - 0.055
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const z = 1.0 - x - y
|
|
206
|
+
const Y = bri / 255
|
|
207
|
+
const X = (Y / y) * x
|
|
208
|
+
const Z = (Y / y) * z
|
|
209
|
+
let r = X * 1.612 - Y * 0.203 - Z * 0.302
|
|
210
|
+
let g = -X * 0.509 + Y * 1.412 + Z * 0.066
|
|
211
|
+
let b = X * 0.026 - Y * 0.072 + Z * 0.962
|
|
212
|
+
|
|
213
|
+
r = getReversedGammaCorrectedValue(r)
|
|
214
|
+
g = getReversedGammaCorrectedValue(g)
|
|
215
|
+
b = getReversedGammaCorrectedValue(b)
|
|
216
|
+
|
|
217
|
+
// Bring all negative components to zero
|
|
218
|
+
r = Math.max(r, 0)
|
|
219
|
+
g = Math.max(g, 0)
|
|
220
|
+
b = Math.max(b, 0)
|
|
221
|
+
|
|
222
|
+
// If one component is greater than 1, weight components by that value
|
|
223
|
+
const max = Math.max(r, g, b)
|
|
224
|
+
if (max > 1) {
|
|
225
|
+
r = r / max
|
|
226
|
+
g = g / max
|
|
227
|
+
b = b / max
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
red: Math.floor(r * 255),
|
|
232
|
+
geen: Math.floor(g * 255),
|
|
233
|
+
blue: Math.floor(b * 255)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
exports.ColorConverter = ColorConverter
|
package/nodes/utils/hueUtils.js
CHANGED
|
@@ -13,6 +13,7 @@ class classHUE extends EventEmitter {
|
|
|
13
13
|
this.bridgeid = _bridgeid
|
|
14
14
|
this.startPushEvents()
|
|
15
15
|
this.timerWatchDog = undefined
|
|
16
|
+
this.hue = undefined
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
getAllLights = async () => {
|
|
@@ -54,6 +55,16 @@ class classHUE extends EventEmitter {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
// Get light state
|
|
59
|
+
getLight = async (_LightID) => {
|
|
60
|
+
try {
|
|
61
|
+
const hue = hueApiV2.connect({ host: this.HUEBridgeIP, key: this.username })
|
|
62
|
+
return await hue.getLight(_LightID)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return ({ error: error.message })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
// Check the bridge for disconnections
|
|
58
69
|
handleTheDog = async () => {
|
|
59
70
|
this.timerWatchDog = setInterval(async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-knx-ultimate",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Control your KNX intallation via Node-Red! Single Node KNX IN/OUT with optional ETS group address importer. Easy to use and highly configurable.",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"mkdirp": "1.0.4",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"xml2js": "0.5.0",
|
|
15
15
|
"dns-sync": "0.2.1",
|
|
16
16
|
"node-hue-api": "5.0.0-beta.16",
|
|
17
|
-
"node-hue": "1.0.3"
|
|
17
|
+
"node-hue": "1.0.3",
|
|
18
|
+
"color-convert": "2.0.1"
|
|
18
19
|
},
|
|
19
20
|
"node-red": {
|
|
20
21
|
"nodes": {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|