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.
Files changed (34) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/KNXEngine/dptlib/index.js +1 -1
  3. package/KNXUltimate.code-workspace +11 -1
  4. package/img/hue.png +0 -0
  5. package/img/knx.png +0 -0
  6. package/nodes/hue-config.js +2 -6
  7. package/nodes/knxUltimateHueLight.html +290 -25
  8. package/nodes/knxUltimateHueLight.js +68 -13
  9. package/nodes/locales/en-US/knxUltimateHueLight.json +3 -3
  10. package/nodes/utils/hueColorConverter.js +237 -0
  11. package/nodes/utils/hueUtils.js +11 -0
  12. package/package.json +3 -2
  13. /package/nodes/locales/{de → de-disabled}/hue-config.html +0 -0
  14. /package/nodes/locales/{de → de-disabled}/hue-config.json +0 -0
  15. /package/nodes/locales/{de → de-disabled}/knxUltimate-config.html +0 -0
  16. /package/nodes/locales/{de → de-disabled}/knxUltimate-config.json +0 -0
  17. /package/nodes/locales/{de → de-disabled}/knxUltimate.html +0 -0
  18. /package/nodes/locales/{de → de-disabled}/knxUltimate.json +0 -0
  19. /package/nodes/locales/{de → de-disabled}/knxUltimateAlerter.html +0 -0
  20. /package/nodes/locales/{de → de-disabled}/knxUltimateAlerter.json +0 -0
  21. /package/nodes/locales/{de → de-disabled}/knxUltimateGlobalContext.html +0 -0
  22. /package/nodes/locales/{de → de-disabled}/knxUltimateGlobalContext.json +0 -0
  23. /package/nodes/locales/{de → de-disabled}/knxUltimateHueLight.html +0 -0
  24. /package/nodes/locales/{de → de-disabled}/knxUltimateHueLight.json +0 -0
  25. /package/nodes/locales/{de → de-disabled}/knxUltimateLoadControl.html +0 -0
  26. /package/nodes/locales/{de → de-disabled}/knxUltimateLoadControl.json +0 -0
  27. /package/nodes/locales/{de → de-disabled}/knxUltimateLogger.html +0 -0
  28. /package/nodes/locales/{de → de-disabled}/knxUltimateLogger.json +0 -0
  29. /package/nodes/locales/{de → de-disabled}/knxUltimateSceneController.html +0 -0
  30. /package/nodes/locales/{de → de-disabled}/knxUltimateSceneController.json +0 -0
  31. /package/nodes/locales/{de → de-disabled}/knxUltimateViewer.html +0 -0
  32. /package/nodes/locales/{de → de-disabled}/knxUltimateViewer.json +0 -0
  33. /package/nodes/locales/{de → de-disabled}/knxUltimateWatchDog.html +0 -0
  34. /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 == 'signed') {
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
@@ -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
- // Send to HUE
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
- $("#node-input-dptLightSwitch").append($("<option></option>")
73
- .attr("value", dpt.value)
74
- .text(dpt.text))
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
- $("#node-input-dptLightState").append($("<option></option>")
120
- .attr("value", dpt.value)
121
- .text(dpt.text))
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
- $("#node-input-dptLightDIM").append($("<option></option>")
163
- .attr("value", dpt.value)
164
- .text(dpt.text))
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>KONNEX</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>&nbspColor</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>&nbspBrightness</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-nameLightDIM" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbsp DIM (3.007)</label>
560
+ <label for="node-input-nameLightColorState" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspColor State</label>
318
561
 
319
- <label for="node-input-GALightDIM" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
320
- <input type="text" id="node-input-GALightDIM" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
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-dptLightDIM" style="width:40px; margin-left: 0px; text-align: right;">
323
- <span data-i18n="knxUltimateHueLight.node-input-dptLightDIM"></span> </label>
324
- <select id="node-input-dptLightDIM" style="width:140px;"></select>
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-nameLightDIM" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
327
- <input type="text" id="node-input-nameLightDIM" style="width:200px;margin-left: 5px; text-align: left;">
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>&nbspBrightness 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
- if (_event.id === config.hueLight.split('#')[1]) {
62
- let knxMsgPayload = undefined
63
- if (_event.hasOwnProperty('on')) knxMsgPayload = _event.on.on
64
- // Send to KNX bus
65
- if (knxMsgPayload !== undefined) {
66
- node.status({ fill: 'green', shape: 'dot', text: 'HUE Status ' + knxMsgPayload + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
67
- if (config.GALightState !== '') node.server.writeQueueAdd({ grpaddr: config.GALightState, payload: knxMsgPayload, dpt: config.dptLightState, outputtype: 'write', nodecallerid: node.id })
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": "&nbsp<i class=\"fa fa-question-circle\"></i>&nbsp<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 (bit)",
6
+ "node-input-nameLightSwitch": "Switch",
7
7
  "node-input-GALightSwitch": "GA",
8
8
  "node-input-dptLightSwitch": "dpt",
9
- "node-input-nameLightState": "State (bit)",
9
+ "node-input-nameLightState": "Switch State",
10
10
  "node-input-GALightState": "GA",
11
11
  "node-input-dptLightState": "dpt",
12
- "node-input-hueLight": "HUE light"
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
@@ -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.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": {