node-red-contrib-knx-ultimate 2.1.10 → 2.1.12

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 CHANGED
@@ -6,6 +6,14 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ <p>
10
+ <b>Version 2.1.12</b> - June 2023<br/>
11
+ - Hue Light node: added tunable white.<br/>
12
+ </p>
13
+ <p>
14
+ <b>Version 2.1.11</b> - June 2023<br/>
15
+ - KNX Global Context node: added the optional datastore to choose from.<br/>
16
+ </p>
9
17
  <p>
10
18
  <b>Version 2.1.10</b> - June 2023<br/>
11
19
  - KNX Gateway Node: Migrated documentation to the standard node-red documentation box.<br/>
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "knxultimate",
3
- "version": "1.0.22",
3
+ "version": "1.0.36",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "knxultimate",
9
- "version": "1.0.22",
9
+ "version": "1.0.36",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@types/node": "^6.14.4",
@@ -14,16 +14,16 @@
14
14
  "binary-parser": "1.1.5",
15
15
  "binary-protocol": "0.0.0",
16
16
  "caller-id": "^0.1.0",
17
- "crypto-js": ">=4.1.1",
17
+ "crypto-js": "4.1.1",
18
18
  "fs": "0.0.1-security",
19
- "ipaddr.js": ">=2.0.0",
20
- "lodash": ">=4.17.21",
19
+ "ipaddr.js": "2.0.0",
20
+ "lodash": "4.17.21",
21
21
  "log-driver": "1.2.7",
22
22
  "mkdirp": "^0.5.1",
23
- "os": ">=0.1.1",
24
- "path": ">=0.12.7",
25
- "ping": ">=0.4.1",
26
- "xml2js": ">=0.4.23"
23
+ "os": "0.1.1",
24
+ "path": "0.12.7",
25
+ "ping": "0.4.1",
26
+ "xml2js": "0.5.0"
27
27
  },
28
28
  "devDependencies": {},
29
29
  "engines": {
@@ -85,9 +85,9 @@
85
85
  "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
86
86
  },
87
87
  "node_modules/ipaddr.js": {
88
- "version": "2.0.1",
89
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz",
90
- "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==",
88
+ "version": "2.0.0",
89
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz",
90
+ "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w==",
91
91
  "engines": {
92
92
  "node": ">= 10"
93
93
  }
@@ -106,9 +106,12 @@
106
106
  }
107
107
  },
108
108
  "node_modules/minimist": {
109
- "version": "1.2.5",
110
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
111
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
109
+ "version": "1.2.8",
110
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
111
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
112
+ "funding": {
113
+ "url": "https://github.com/sponsors/ljharb"
114
+ }
112
115
  },
113
116
  "node_modules/mkdirp": {
114
117
  "version": "0.5.5",
@@ -122,9 +125,9 @@
122
125
  }
123
126
  },
124
127
  "node_modules/os": {
125
- "version": "0.1.2",
126
- "resolved": "https://registry.npmjs.org/os/-/os-0.1.2.tgz",
127
- "integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ=="
128
+ "version": "0.1.1",
129
+ "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz",
130
+ "integrity": "sha512-jg06S2xr5De63mLjZVJDf3/k37tpjppr2LR7MUOsxv8XuUCVpCnvbCksXCBcB5gQqQf/K0+87WGTRlAj5q7r1A=="
128
131
  },
129
132
  "node_modules/path": {
130
133
  "version": "0.12.7",
@@ -191,9 +194,9 @@
191
194
  }
192
195
  },
193
196
  "node_modules/xml2js": {
194
- "version": "0.4.23",
195
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
196
- "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
197
+ "version": "0.5.0",
198
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
199
+ "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
197
200
  "dependencies": {
198
201
  "sax": ">=0.6.0",
199
202
  "xmlbuilder": "~11.0.0"
@@ -264,9 +267,9 @@
264
267
  "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
265
268
  },
266
269
  "ipaddr.js": {
267
- "version": "2.0.1",
268
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz",
269
- "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng=="
270
+ "version": "2.0.0",
271
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz",
272
+ "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w=="
270
273
  },
271
274
  "lodash": {
272
275
  "version": "4.17.21",
@@ -279,9 +282,9 @@
279
282
  "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg=="
280
283
  },
281
284
  "minimist": {
282
- "version": "1.2.5",
283
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
284
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
285
+ "version": "1.2.8",
286
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
287
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
285
288
  },
286
289
  "mkdirp": {
287
290
  "version": "0.5.5",
@@ -292,9 +295,9 @@
292
295
  }
293
296
  },
294
297
  "os": {
295
- "version": "0.1.2",
296
- "resolved": "https://registry.npmjs.org/os/-/os-0.1.2.tgz",
297
- "integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ=="
298
+ "version": "0.1.1",
299
+ "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz",
300
+ "integrity": "sha512-jg06S2xr5De63mLjZVJDf3/k37tpjppr2LR7MUOsxv8XuUCVpCnvbCksXCBcB5gQqQf/K0+87WGTRlAj5q7r1A=="
298
301
  },
299
302
  "path": {
300
303
  "version": "0.12.7",
@@ -348,9 +351,9 @@
348
351
  }
349
352
  },
350
353
  "xml2js": {
351
- "version": "0.4.23",
352
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
353
- "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
354
+ "version": "0.5.0",
355
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
356
+ "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
354
357
  "requires": {
355
358
  "sax": ">=0.6.0",
356
359
  "xmlbuilder": "~11.0.0"
@@ -47,7 +47,7 @@
47
47
  "caller-id": "^0.1.0",
48
48
  "path": "0.12.7",
49
49
  "crypto-js": "4.1.1",
50
- "xml2js": "0.4.23",
50
+ "xml2js": "0.5.0",
51
51
  "aes-cbc-mac": "1.0.1"
52
52
  },
53
53
  "devDependencies": {}
@@ -7,7 +7,8 @@
7
7
  server: { type: "knxUltimate-config", required: true },
8
8
  name: { value: "KNXGlobalContext", validate: RED.validators.regex(/^[a-zA-Z]+$/) },
9
9
  exposeAsVariable: { value: "exposeAsVariableREADWRITE", required: false },
10
- writeExecutionInterval: { value: 1000 }
10
+ writeExecutionInterval: { value: 1000 },
11
+ contextStorage: { value: "" }
11
12
  },
12
13
  inputs: 0,
13
14
  outputs: 0,
@@ -98,5 +99,11 @@
98
99
  </select>
99
100
  </div>
100
101
 
102
+ <div class="form-row">
103
+ <label for="node-input-contextStorage" style="width:60%;">
104
+ <i class="fa fa-tag"></i> Context storage
105
+ </label>
106
+ <input style="width:35%;" type="text" id="node-input-contextStorage" placeholder="Optional context storage name" />
107
+ </div>
101
108
 
102
109
  </script>
@@ -26,7 +26,7 @@ module.exports = function (RED) {
26
26
  // payload
27
27
  // }
28
28
 
29
- function knxUltimateGlobalContext (config) {
29
+ function knxUltimateGlobalContext(config) {
30
30
  RED.nodes.createNode(this, config)
31
31
  const node = this
32
32
  node.server = RED.nodes.getNode(config.server)
@@ -50,7 +50,7 @@ module.exports = function (RED) {
50
50
  node.formatnegativevalue = 'leave'
51
51
  node.formatdecimalsvalue = 999
52
52
  node.writeExecutionInterval = config.writeExecutionInterval === undefined ? 1000 : config.writeExecutionInterval
53
-
53
+ node.contextStorage = config.contextStorage !== undefined ? config.contextStorage : ''
54
54
  node.exposeAsVariable = config.exposeAsVariable !== undefined ? config.exposeAsVariable : 'exposeAsVariableREADONLY' // Should expose the Group Addresses to the Global Context?
55
55
  node.exposedGAs = []
56
56
  node.timerExposedGAs = null
@@ -83,8 +83,8 @@ module.exports = function (RED) {
83
83
  node.goTimerGo = function () {
84
84
  if (node.timerExposedGAs !== null) clearTimeout(node.timerExposedGAs) // 21/03/2021
85
85
  node.timerExposedGAs = setTimeout(() => {
86
- let oContext = node.context().global.get(node.name + '_WRITE') || []
87
- node.context().global.set(node.name + '_WRITE', []) // Delete the var
86
+ let oContext = node.context().global.get(node.name + '_WRITE', node.contextStorage) || []
87
+ node.context().global.set(node.name + '_WRITE', [], node.contextStorage) // Delete the var
88
88
  for (let index = 0; index < oContext.length; index++) {
89
89
  const element = oContext[index]
90
90
  if (!element.hasOwnProperty('address')) {
@@ -129,7 +129,7 @@ module.exports = function (RED) {
129
129
  node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Start Writing', payload: '', GA: '', dpt: '', devicename: '' })
130
130
  } else {
131
131
  if (node.timerExposedGAs !== null) clearTimeout(node.timerExposedGAs)
132
- node.context().global.set(node.name + '_WRITE', []) // Delete the var
132
+ node.context().global.set(node.name + '_WRITE', [], node.contextStorage) // Delete the var
133
133
  }
134
134
  // #endregion
135
135
 
@@ -149,14 +149,14 @@ module.exports = function (RED) {
149
149
  }
150
150
  // Save into the global Context
151
151
  try {
152
- node.context().global.set(node.name + '_READ', node.exposedGAs)
152
+ node.context().global.set(node.name + '_READ', node.exposedGAs, node.contextStorage)
153
153
  } catch (error) {
154
154
  console.log(error)
155
155
  }
156
156
  oGa = null // 21/03/2022
157
157
  } else {
158
158
  node.exposedGAs = []
159
- node.context().global.set(node.name + '_READ', node.exposedGAs)
159
+ node.context().global.set(node.name + '_READ', node.exposedGAs, node.contextStorage)
160
160
  }
161
161
  }
162
162
 
@@ -28,6 +28,14 @@
28
28
  GALightColorState: { value: "" },
29
29
  dptLightColorState: { value: "" },
30
30
 
31
+ nameLightHSV: { value: "" },
32
+ GALightHSV: { value: "" },
33
+ dptLightHSV: { value: "" },
34
+
35
+ nameLightHSVState: { value: "" },
36
+ GALightHSVState: { value: "" },
37
+ dptLightHSVState: { value: "" },
38
+
31
39
  nameLightBrightness: { value: "" },
32
40
  GALightBrightness: { value: "" },
33
41
  dptLightBrightness: { value: "" },
@@ -237,7 +245,7 @@
237
245
  });
238
246
 
239
247
 
240
- // DPT dptLightColor and dptLightColorStateƒ
248
+ // DPT dptLightColor and dptLightColorState
241
249
  // ########################
242
250
  $.getJSON('knxUltimateDpts', (data) => {
243
251
  data.forEach(dpt => {
@@ -320,6 +328,105 @@
320
328
 
321
329
 
322
330
 
331
+
332
+
333
+
334
+ // DPT dptLightTunableWhite
335
+ // ########################
336
+ $.getJSON('knxUltimateDpts', (data) => {
337
+ data.forEach(dpt => {
338
+ if (dpt.value === "3.007") {
339
+ $("#node-input-dptLightHSV").append($("<option></option>")
340
+ .attr("value", dpt.value)
341
+ .text(dpt.text))
342
+ }
343
+ });
344
+
345
+ $("#node-input-dptLightHSV").val(this.dptLightHSV)
346
+ })
347
+
348
+ // DPT dptLightTunableWhiteState
349
+ $.getJSON('knxUltimateDpts', (data) => {
350
+ data.forEach(dpt => {
351
+ if (dpt.value === "5.001") {
352
+ $("#node-input-dptLightHSVState").append($("<option></option>")
353
+ .attr("value", dpt.value)
354
+ .text(dpt.text))
355
+ }
356
+ });
357
+ $("#node-input-dptLightHSVState").val(this.dptLightHSVState)
358
+ })
359
+
360
+ // Autocomplete suggestion with ETS csv File
361
+ $("#node-input-GALightHSV").autocomplete({
362
+ minLength: 1,
363
+ source: function (request, response) {
364
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
365
+ response($.map(data, function (value, key) {
366
+ if (value.dpt === "3.007") {
367
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
368
+ if (fullSearch(sSearch, request.term)) {
369
+ return {
370
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
371
+ value: value.ga // Value
372
+ }
373
+ } else {
374
+ return null;
375
+ }
376
+ }
377
+ }));
378
+ });
379
+ }, select: function (event, ui) {
380
+ // Sets Datapoint and device name automatically
381
+ var sDevName = ui.item.label.split("#")[1].trim();
382
+ try {
383
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
384
+ } catch (error) {
385
+ }
386
+ $('#node-input-nameLightHSV').val(sDevName);
387
+ var optVal = $("#node-input-dptLightHSV option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
388
+ // Select the option value
389
+ $("#node-input-dptLightHSV").val(optVal);
390
+ }
391
+ });
392
+
393
+ // Autocomplete suggestion with ETS csv File
394
+ $("#node-input-GALightHSVState").autocomplete({
395
+ minLength: 1,
396
+ source: function (request, response) {
397
+ $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
398
+ response($.map(data, function (value, key) {
399
+ if (value.dpt === "5.001") {
400
+ var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
401
+ if (fullSearch(sSearch, request.term)) {
402
+ return {
403
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
404
+ value: value.ga // Value
405
+ }
406
+ } else {
407
+ return null;
408
+ }
409
+ }
410
+ }));
411
+ });
412
+ }, select: function (event, ui) {
413
+ // Sets Datapoint and device name automatically
414
+ var sDevName = ui.item.label.split("#")[1].trim();
415
+ try {
416
+ sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
417
+ } catch (error) {
418
+ }
419
+ $('#node-input-nameLightHSVState').val(sDevName);
420
+ var optVal = $("#node-input-dptLightHSVState option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
421
+ // Select the option value
422
+ $("#node-input-dptLightHSVState").val(optVal);
423
+ }
424
+ });
425
+ // ########################
426
+
427
+
428
+
429
+
323
430
  // DPT dptLightBrightness and dptLightBrightnessState
324
431
  // ########################
325
432
  $.getJSON('knxUltimateDpts', (data) => {
@@ -583,8 +690,6 @@
583
690
 
584
691
  <br/>
585
692
 
586
- <!-- <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> -->
587
-
588
693
  <p>
589
694
  <b>KNX LINK</b>
590
695
  </p>
@@ -612,8 +717,8 @@
612
717
  <label for="node-input-nameLightState" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
613
718
  <input type="text" id="node-input-nameLightState" style="width:200px;margin-left: 5px; text-align: left;">
614
719
  </div>
615
- <br/>
616
- </div>
720
+
721
+
617
722
  <div class="form-row">
618
723
  <label for="node-input-nameLightDIM" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspDimming</label>
619
724
 
@@ -625,9 +730,8 @@
625
730
 
626
731
  <label for="node-input-nameLightDIM" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
627
732
  <input type="text" id="node-input-nameLightDIM" style="width:200px;margin-left: 5px; text-align: left;">
628
- </div>
629
- <br/>
630
- </div>
733
+ </div>
734
+
631
735
  <div class="form-row">
632
736
  <label for="node-input-nameLightColor" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspColor</label>
633
737
 
@@ -639,7 +743,7 @@
639
743
 
640
744
  <label for="node-input-nameLightColor" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
641
745
  <input type="text" id="node-input-nameLightColor" style="width:200px;margin-left: 5px; text-align: left;">
642
- </div>
746
+ </div>
643
747
  <div class="form-row">
644
748
  <label for="node-input-nameLightColorState" style="width:100px;"><i class="fa fa-play-circle-o"></i> Color Status</label>
645
749
 
@@ -651,10 +755,35 @@
651
755
 
652
756
  <label for="node-input-nameLightColorState" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
653
757
  <input type="text" id="node-input-nameLightColorState" style="width:200px;margin-left: 5px; text-align: left;">
758
+ </div>
759
+
760
+
761
+ <div class="form-row">
762
+ <label for="node-input-nameLightHSV" style="width:100px;"><i class="fa fa-play-circle-o"></i> Tunable white</label>
763
+
764
+ <label for="node-input-GALightHSV" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
765
+ <input type="text" id="node-input-GALightHSV" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
766
+
767
+ <label for="node-input-dptLightHSV" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
768
+ <select id="node-input-dptLightHSV" style="width:140px;"></select>
769
+
770
+ <label for="node-input-nameLightHSV" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
771
+ <input type="text" id="node-input-nameLightHSV" style="width:200px;margin-left: 5px; text-align: left;">
654
772
  </div>
655
- <br/>
773
+ <div class="form-row">
774
+ <label for="node-input-nameLightHSVState" style="width:100px;"><i class="fa fa-play-circle-o"></i> Tunable white Status</label>
775
+
776
+ <label for="node-input-GALightHSVState" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
777
+ <input type="text" id="node-input-GALightHSVState" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
778
+
779
+ <label for="node-input-dptLightHSVState" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
780
+ <select id="node-input-dptLightHSVState" style="width:140px;"></select>
781
+
782
+ <label for="node-input-nameLightHSVState" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
783
+ <input type="text" id="node-input-nameLightHSVState" style="width:200px;margin-left: 5px; text-align: left;">
656
784
  </div>
657
- </div>
785
+
786
+
658
787
  <div class="form-row">
659
788
  <label for="node-input-nameLightBrightness" style="width:100px;"><i class="fa fa-play-circle-o"></i>&nbspBrightness</label>
660
789
 
@@ -666,8 +795,8 @@
666
795
 
667
796
  <label for="node-input-nameLightBrightness" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
668
797
  <input type="text" id="node-input-nameLightBrightness" style="width:200px;margin-left: 5px; text-align: left;">
669
- </div>
670
- </div>
798
+ </div>
799
+
671
800
  <div class="form-row">
672
801
  <label for="node-input-nameLightBrightnessState" style="width:100px;"><i class="fa fa-play-circle-o"></i> Brightness Status</label>
673
802
 
@@ -679,10 +808,9 @@
679
808
 
680
809
  <label for="node-input-nameLightBrightnessState" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
681
810
  <input type="text" id="node-input-nameLightBrightnessState" style="width:200px;margin-left: 5px; text-align: left;">
682
- </div>
683
- <br/>
684
- <br/>
685
- </div>
811
+ </div>
812
+
813
+
686
814
  <div class="form-row">
687
815
  <label for="node-input-nameLightBlink" style="width:100px;"><i class="fa fa-play-circle-o"></i> Blink</label>
688
816
 
@@ -694,8 +822,8 @@
694
822
 
695
823
  <label for="node-input-nameLightBlink" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
696
824
  <input type="text" id="node-input-nameLightBlink" style="width:200px;margin-left: 5px; text-align: left;">
697
- </div>
698
- </div>
825
+ </div>
826
+
699
827
  <div class="form-row">
700
828
  <label for="node-input-nameLightColorCycle" style="width:100px;"><i class="fa fa-play-circle-o"></i> Color Cycle</label>
701
829
 
@@ -707,7 +835,7 @@
707
835
 
708
836
  <label for="node-input-nameLightColorCycle" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
709
837
  <input type="text" id="node-input-nameLightColorCycle" style="width:200px;margin-left: 5px; text-align: left;">
710
- </div>
838
+ </div>
711
839
  <br/>
712
840
  <br/>
713
841
  <br/>
@@ -738,6 +866,8 @@ Start typing in the GA field, the name or group address of your KNX device, the
738
866
  | Dimming | Relative DIM the HUE light |
739
867
  | Color | This command is used to change the HUE light's color. Accepted datapoint is RGB triplet (r,g,b). The node handles the gamut color correction. As soon as you send a color KNX telegran, the light turns on and sets color and brightness, derived from the brightness human perception. As soon as you send a KNX telegram with r,g,b set to zero, the light turns off |
740
868
  | Color Status | Link this to the light's color status group address. Accepted datapoint is RGB triplet (r,g,b)|
869
+ | Tunable white | This command is used to change the HUE light's white temperature. Datapoint is 3.007 dimming. |
870
+ | Tunable white Status | Link this to the light temperature status group address. Datapoint is 5.001 absolute value|
741
871
  | Brightness | This command is used to change the absolute HUE light's brightness |
742
872
  | Brightness Status| Link this to the light's brightness status group address |
743
873
  | Blink| *true* Blink the light, *false* Stop blinking. Blinks the light on and off. Useful for signalling. Works with all HUE lights. |
@@ -2,7 +2,7 @@ module.exports = function (RED) {
2
2
  const dptlib = require('./../KNXEngine/src/dptlib')
3
3
  const hueColorConverter = require('./utils/hueColorConverter')
4
4
 
5
- function knxUltimateHueLight (config) {
5
+ function knxUltimateHueLight(config) {
6
6
  RED.nodes.createNode(this, config)
7
7
  const node = this
8
8
  node.server = RED.nodes.getNode(config.server)
@@ -60,6 +60,21 @@ module.exports = function (RED) {
60
60
  node.startDimStopper('stop')
61
61
  }
62
62
  break
63
+ case config.GALightHSV:
64
+ if (config.dptLightHSV === '3.007') {
65
+ // MDT smartbutton will dim the color temperature
66
+ // { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
67
+ // { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
68
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightHSV))
69
+ if (msg.payload.data > 0) {
70
+ let dimDirectionTunableWhite = 'down'
71
+ dimDirectionTunableWhite = msg.payload.decr_incr === 1 ? 'up' : 'down'
72
+ node.startDimStopperTunableWhite(dimDirectionTunableWhite)
73
+ } else {
74
+ node.startDimStopperTunableWhite('stop')
75
+ }
76
+ }
77
+ break
63
78
  case config.GALightBrightness:
64
79
  msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBrightness))
65
80
  state = { dimming: { brightness: msg.payload } }
@@ -97,7 +112,7 @@ module.exports = function (RED) {
97
112
  node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: true } }, 'setLight')
98
113
  node.timerColorCycle = setInterval(() => {
99
114
  try {
100
- function getRandomIntInclusive (min, max) {
115
+ function getRandomIntInclusive(min, max) {
101
116
  min = Math.ceil(min)
102
117
  max = Math.floor(max)
103
118
  return Math.floor(Math.random() * (max - min + 1) + min) // The maximum is inclusive and the minimum is inclusive
@@ -150,6 +165,29 @@ module.exports = function (RED) {
150
165
  }, 300)
151
166
  }
152
167
 
168
+ // Start dimming tunable white
169
+ // mirek: required(integer – minimum: 153 – maximum: 500)
170
+ node.timerDimTunableWhite = undefined
171
+ node.dimDirectionTunableWhite = {}
172
+ node.startDimStopperTunableWhite = function (_direction) {
173
+ if (node.timerDimTunableWhite !== undefined) clearInterval(node.timerDimTunableWhite)
174
+ if (_direction === 'stop') return
175
+ switch (_direction) {
176
+ case 'up':
177
+ node.dimDirectionTunableWhite = { color_temperature_delta: { action: 'up', mirek_delta: 10 } }
178
+ break
179
+ case 'down':
180
+ node.dimDirectionTunableWhite = { color_temperature_delta: { action: 'down', mirek_delta: 10 } }
181
+ break
182
+ default:
183
+ break
184
+ }
185
+ node.timerDimTunableWhite = setInterval(() => {
186
+ node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, node.dimDirectionTunableWhite, 'setLight')
187
+ }, 300)
188
+
189
+ }
190
+
153
191
  node.handleSendHUE = _event => {
154
192
  try {
155
193
  if (_event.id === config.hueDevice) {
@@ -187,6 +225,17 @@ module.exports = function (RED) {
187
225
  // Send to KNX bus
188
226
  if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
189
227
  }
228
+ if (_event.hasOwnProperty('color_temperature')) {
229
+ knxMsgPayload.topic = config.GALightHSVState
230
+ knxMsgPayload.dpt = config.dptLightHSVState
231
+ if (config.dptLightHSVState === '5.001') {
232
+ //NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
233
+ let NewValue = (((_event.color_temperature.mirek - 153) * (100 - 0)) / (500 - 153)) + 0
234
+ knxMsgPayload.payload = NewValue
235
+ }
236
+ // Send to KNX bus
237
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
238
+ }
190
239
  node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX State ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
191
240
  }
192
241
  } catch (error) {
@@ -4,9 +4,10 @@
4
4
  const hueApiV2 = require('node-hue')
5
5
  const { EventEmitter } = require('events')
6
6
  const https = require('https')
7
+ const EventSource = require('eventsource');
7
8
 
8
9
  class classHUE extends EventEmitter {
9
- constructor (_hueBridgeIP, _username, _clientkey, _bridgeid) {
10
+ constructor(_hueBridgeIP, _username, _clientkey, _bridgeid) {
10
11
  super()
11
12
  this.setup(_hueBridgeIP, _username, _clientkey, _bridgeid)
12
13
  }
@@ -19,6 +20,8 @@ class classHUE extends EventEmitter {
19
20
  this.commandQueue = []
20
21
  this.closePushEventStream = false
21
22
  this.timerwriteQueueAdd = setTimeout(this.handleQueue, 3000) // First start
23
+ this.connect()
24
+
22
25
  // this.run()
23
26
  // start the SSE Stream Receiver
24
27
  // #############################################
@@ -80,28 +83,84 @@ class classHUE extends EventEmitter {
80
83
  // // Starts the connection for the first time
81
84
  // req();
82
85
 
86
+
83
87
  // Eventstream Reader using hueApiV2
84
- const runStreamReader = async () => {
88
+ // const runStreamReader = async () => {
89
+ // try {
90
+ // const listener = (event) => {
91
+ // // console.log(event)
92
+ // event.data.forEach(element => {
93
+ // if (event.type === 'update') this.emit('event', element)
94
+ // })
95
+ // }
96
+ // const hueEventStream = hueApiV2.connect({
97
+ // host: this.hueBridgeIP,
98
+ // key: this.username,
99
+ // eventListener: listener
100
+ // })
101
+ // } catch (error) {
102
+ // console.log('KNXUltimateHUEConfig: classHUE: const run = async: ' + error.message)
103
+ // }
104
+ // }
105
+ // runStreamReader()
106
+
107
+
108
+
109
+ // #############################################
110
+ }
111
+
112
+
113
+ connect() {
114
+ // #############################################
115
+ const options = {
116
+ headers: {
117
+ 'hue-application-key': this.username,
118
+ },
119
+ https: {
120
+ rejectUnauthorized: false,
121
+ },
122
+ };
123
+
124
+ this.es = new EventSource('https://' + this.hueBridgeIP + '/eventstream/clip/v2', options);
125
+
126
+ this.es.onmessage = (event) => {
85
127
  try {
86
- const listener = (event) => {
87
- // console.log(event)
88
- event.data.forEach(element => {
89
- if (event.type === 'update') this.emit('event', element)
128
+ if (event && event.type === 'message' && event.data) {
129
+ const data = JSON.parse(event.data);
130
+ data.forEach(element => {
131
+ if (element.type === 'update') {
132
+ element.data.forEach(ev => {
133
+ this.emit('event', ev)
134
+ })
135
+ }
90
136
  })
91
137
  }
92
- const hueEventStream = hueApiV2.connect({
93
- host: this.hueBridgeIP,
94
- key: this.username,
95
- eventListener: listener
96
- })
97
138
  } catch (error) {
98
- console.log('KNXUltimateHUEConfig: classHUE: const run = async: ' + error.message)
139
+ console.log('KNXUltimateHUEConfig: classHUE: this.es.onmessage: ' + error.message)
99
140
  }
141
+
142
+ };
143
+
144
+ this.es.onopen = () => {
145
+ //console.log('KNXUltimateHUEConfig: classHUE: SSE-Connected')
146
+ //this.emit('connected');
100
147
  }
101
- runStreamReader()
148
+ this.es.onerror = (error) => {
149
+ try {
150
+ this.es.close()
151
+ this.es = null
152
+ console.log('KNXUltimateHUEConfig: classHUE: request.on(error): ' + error.message)
153
+ setTimeout(() => {
154
+ this.connect()
155
+ }, 5000);
156
+ } catch (error) {
102
157
 
103
- // #############################################
158
+ }
159
+ //this.emit('error', err)
160
+ };
104
161
  }
162
+ // #############################################
163
+
105
164
 
106
165
  // Handle the send queue
107
166
  // ######################################
@@ -131,7 +190,7 @@ class classHUE extends EventEmitter {
131
190
  }
132
191
  }
133
192
  // The Hue bridge allows about 10 telegram per second, so i need to make a queue manager
134
- setTimeout(this.handleQueue, 100)
193
+ setTimeout(this.handleQueue, 150)
135
194
  }
136
195
 
137
196
  writeHueQueueAdd = async (_lightID, _state, _operation, _callback) => {
@@ -185,10 +244,22 @@ class classHUE extends EventEmitter {
185
244
  }
186
245
  }
187
246
 
247
+ // Get the light details
248
+ getLightStatus = async (_rid) => {
249
+ try {
250
+ const hue = hueApiV2.connect({ host: this.hueBridgeIP, key: this.username })
251
+ const oLight = await hue.getLight(_rid)
252
+ return oLight[0]
253
+ } catch (error) {
254
+ }
255
+ }
256
+
188
257
  close = async () => {
189
258
  return new Promise((resolve, reject) => {
190
259
  try {
191
260
  this.closePushEventStream = true
261
+ this.es.close();
262
+ this.es = null;
192
263
  setTimeout(() => {
193
264
  resolve(true)
194
265
  }, 1000)
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=16.0.0"
5
5
  },
6
- "version": "2.1.10",
6
+ "version": "2.1.12",
7
7
  "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. With integrated Philips HUE devices handling.",
8
8
  "dependencies": {
9
9
  "mkdirp": "1.0.4",
@@ -18,7 +18,8 @@
18
18
  "dns-sync": "0.2.1",
19
19
  "node-hue-api": "5.0.0-beta.16",
20
20
  "node-hue": "1.0.4",
21
- "color-convert": "2.0.1"
21
+ "color-convert": "2.0.1",
22
+ "eventsource": "2.0.2"
22
23
  },
23
24
  "node-red": {
24
25
  "version": ">=2.0.0",