node-red-contrib-knx-ultimate 3.2.16 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,13 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 3.3.0** - November 2024<br/>
10
+ - KNX Engine: explicitly set the local port of the unicast socket on 3671 and the local IP of the multicast to 0.0.0.0.<br/>
11
+ - KNX Engine: many engine adaptation for the upcoming KNX-Secure implementation.<br/>
12
+
13
+ **Version 3.2.17** - October 2024<br/>
14
+ - Hue devices: globally check for boundary limits, when calculating brightness and colorYX from an RGB or HEX KNX input.<br/>
15
+
9
16
  **Version 3.2.16** - October 2024<br/>
10
17
  - Hue devices: fixed an issue with the ETS CSV file, when a HUE device's GA isn't contained in the CSV.<br/>
11
18
  - Hue Light: fixed an issue in setting RGB to 255,255,255, caused by an out of boundary of the calculater brightness value.<br/>
package/README.md CHANGED
@@ -77,7 +77,7 @@ Please subscribe to the [Youtube channel](https://www.youtube.com/playlist?list=
77
77
  | KNX Secure Tunnelling | ![](https://placehold.co/200x20/orange/white?text=UNDER+DEVELOPMENT) |
78
78
  | KNX Secure Routing | ![](https://placehold.co/200x20/red/white?text=NO) |
79
79
  | KNX 3rd PARTY IOT API client | ![](https://placehold.co/200x20/red/white?text=DELAYED) |
80
- | Matter | ![](https://placehold.co/200x20/blue/white?text=UNDER+BRAINSTORMING) |
80
+ | Matter | ![](https://placehold.co/200x20/red/white?text=NO+TOO+IMMATURE) |
81
81
 
82
82
  <br/>
83
83
 
@@ -6,7 +6,7 @@ const path = require("path");
6
6
  const yaml = require('js-yaml');
7
7
  const dptlib = require('knxultimate').dptlib;
8
8
  const customHTTP = require('./utils/http');
9
-
9
+ const KNXClient = require('knxultimate').KNXClient;
10
10
 
11
11
  // DATAPONT MANIPULATION HELPERS
12
12
  // ####################
@@ -47,6 +47,7 @@ const toConcattedSubtypes = (acc, baseType) => {
47
47
  // ####################
48
48
 
49
49
  module.exports = (RED) => {
50
+
50
51
  RED.plugins.registerPlugin("commonFunctions", {
51
52
  type: "foo",
52
53
  onadd: function () {
@@ -308,17 +309,32 @@ module.exports = (RED) => {
308
309
  });
309
310
 
310
311
  // 14/08/2019 Endpoint for retrieving the ethernet interfaces
311
- RED.httpAdmin.get("/KNUltimateGetLogFile", (req, res) => {
312
+ RED.httpAdmin.get("/knxUltimateDiscoverKNXGateways", RED.auth.needsPermission("knxUltimate-config.read"), async function (req, res) {
312
313
  try {
313
- const log = fs.readFileSync('./KNXUltimateDebugLog.txt')
314
- res.json(log.toString());
314
+ async function search() {
315
+ let jRet = [];
316
+ try {
317
+ const knxGateways = await KNXClient.discover();
318
+ // For each discovered gateway, get all possible device descriptions (usually only one)
319
+ // A description is a JSON object containing all details of the device and also what type of connection (Multicast, unicast, etc), it suppports
320
+ for (let index = 0; index < knxGateways.length; index++) {
321
+ const element = knxGateways[index];
322
+ const [ip, port] = element.split(':')
323
+ const descriptionsJSON = await KNXClient.getGatewayDescription(ip, port, 5000)
324
+ for (let index = 0; index < descriptionsJSON.length; index++) {
325
+ const element = descriptionsJSON[index];
326
+ jRet.push({ ip: ip, port: port, name: element.deviceInfo.name, physAddr: element.deviceInfo.formattedAddress });
327
+ }
328
+ }
329
+ res.json(jRet);
330
+ } catch (error) { }
331
+ }
332
+ search();
315
333
  } catch (error) {
316
- res.json(error.message);
334
+ res.json({ error: error.stack });
317
335
  }
318
-
319
336
  });
320
337
 
321
-
322
338
  // 12/08/2021 Endpoint for deleting the GA persistent file for the current gateway
323
339
  RED.httpAdmin.get("/deletePersistGAFile", RED.auth.needsPermission("knxUltimate-config.read"), (req, res) => {
324
340
  try {
@@ -43,6 +43,31 @@
43
43
  }
44
44
  });
45
45
 
46
+ // Autocomplete with KNX IP Interfaces
47
+ // let aKNXInterfaces = [];
48
+ // $.getJSON("knxUltimateDiscoverKNXGateways?" + { _: new Date().getTime() }, (data) => {
49
+ // for (let index = 0; index < data.length; index++) {
50
+ // const element = data[index];
51
+ // aKNXInterfaces.push({
52
+ // name: value.ip + " # " + value.name + " " + value.physAddr,
53
+ // value: value.ip,
54
+ // });
55
+ // }
56
+ // alert("IK")
57
+ // alert(aKNXInterfaces[0].name)
58
+ // $("#node-config-input-host").autocomplete({
59
+ // minLength: 0,
60
+ // source: aKNXInterfaces,
61
+ // select: function (event, ui) {
62
+ // $("#node-config-input-host").val(ui.item.value);
63
+ // },
64
+ // focus: function (event, ui) {
65
+ // $(this).autocomplete('search', $(this).val() + 'exactmatch');
66
+ // }
67
+ // })
68
+ // });
69
+
70
+
46
71
  $("#node-config-input-KNXEthInterface").append($("<option></option>")
47
72
  .attr("value", "Auto")
48
73
  .text("Auto")
@@ -96,13 +121,7 @@
96
121
  for (const [key, value] of Object.entries(node)) {
97
122
  sRetDebugText += (`-> ${key}: ${value}\r`);
98
123
  }
99
- // Add log file
100
- $.getJSON("KNUltimateGetLogFile", (data) => {
101
- sRetDebugText += "\r\r" + data;
102
- sRetDebugText = 'INSTRUCTIONS: COPY THE LINK BELOW AND PASTE IT INTO THE ADDRESS BAR OF YOUR BROWSER.\rhttps://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/new?assignees=Supergiovane&labels=&template=bug_report.md&title=KNXDebugText\r\r THEN PASTE THIS TEXT INTO THE ISSUE\'S BODY, USING THE <> BRACKETS.\r\r' + sRetDebugText;
103
- $("#debugText").val(sRetDebugText); // Store the config-node);
104
- });
105
-
124
+ $("#debugText").val(sRetDebugText); // Store the config-node);
106
125
  });
107
126
  $("#getallgaused").click(function () {
108
127
  sRetDebugText = "";
@@ -427,7 +446,7 @@
427
446
  |Property|Description|
428
447
  |--|--|
429
448
  | IP Port | The port. Default is 3671. |
430
- | IP Protocol | *Tunnel UDP* is for KNX/IP interfaces, *Multicast UDP* is for KNX/IP Routers. Leave **Auto** for auto detect. Default is "Auto". |
449
+ | IP Protocol | *Tunnel UDP* is for KNX/IP interfaces, *Multicast UDP* is for KNX/IP Routers. Leave **Auto** for auto detect. CAUTION: **Auto** won't work with hostnames (for example myknx.home.com), but only with IP addresses. Default is "Auto". |
431
450
  | KNX Physical Address | The physical KNX address, example 1.1.200. Default is "15.15.22".|
432
451
  | Bind to local interface | The Node will use this local interface for communications. Leave "Auto" for automatic selection. If you have more than one lan connection, for example Ethernet and Wifi, it's strongly recommended to manually select the interface, otherwise not all UDP telegram will reach your computer, thus the Node may not work as expected. Default is "Auto". |
433
452
  | Automatically connect to KNX BUS at start | Auto connect to the bus at start. Default is "Yes". |
@@ -440,7 +459,7 @@
440
459
  | Echo sent message to all node with same Group Address | Send the msg input coming from the flow, to all nodes having the same group address. The nodes will receive the new msg as if it's coming from the KNX bus. This is useful in case of using the KNX emulation and in case the connection to the KNX bus is not established. **This option will be deprecated in the next version and defaulted to checked.** Default is checked. |
441
460
  | Suppress repeated (R-Flag) telegrams fom BUS | Ignore repeated KNX telegrams coming from the bus. Default is unchecked. |
442
461
  | Suppress ACK request in tunneling mode | Enable it if you have a very old KNX/IP gateway. It ignores the ACK procedure and accepts all telegrams. Default is unchecked.|
443
- | Delay between each telegram (in milliseconds) | KNX specs states, that the maximum telegram sending speed is 50 telegrams per second. A speed between 25 and 50ms should be fine, unless you're connecting to a remote KNX Gateway via a slow internet connection (in this case, you should increase the value by, for example, 200 to 500ms or more). |
462
+ | Delay between each telegram (in milliseconds) | KNX specs states, that the maximum telegram sending speed is 50 telegrams per second. A speed between 25 and 50ms should be fine, unless you're connecting to a remote KNX Gateway via a slow internet connection (in this case, you should increase the value by, for example, 100 to 200ms). Don't exeed 200ms. |
444
463
  | Loglevel | Log level, in case you need to debug something with the dev. Default is "Error", |
445
464
 
446
465
  <br/>
@@ -96,8 +96,9 @@ module.exports = (RED) => {
96
96
  node.allowLauch_initKNXConnection = true; // See the node.timerKNXUltimateCheckState function
97
97
  node.hostProtocol = config.hostProtocol === undefined ? "Auto" : config.hostProtocol; // 20/03/2022 Default
98
98
  node.knxConnection = null; // 20/03/2022 Default
99
- node.delaybetweentelegrams = (config.delaybetweentelegrams === undefined || config.delaybetweentelegrams === null) ? 25 : Number(config.delaybetweentelegrams);
100
- if (node.delaybetweentelegrams < 20) node.delaybetweentelegrams = 20; // Protection avoiding handleKNXQueue hangs
99
+ node.delaybetweentelegrams = (config.delaybetweentelegrams === undefined || config.delaybetweentelegrams === null || config.delaybetweentelegrams === '') ? 25 : Number(config.delaybetweentelegrams);
100
+ if (node.delaybetweentelegrams < 25) node.delaybetweentelegrams = 25; // Protection avoiding handleKNXQueue hangs
101
+ if (node.delaybetweentelegrams > 100) node.delaybetweentelegrams = 100; // Protection avoiding handleKNXQueue hangs
101
102
 
102
103
  // 05/12/2021 Set the protocol (this is undefined if coming from ild versions
103
104
  if (node.hostProtocol === "Auto") {
@@ -209,7 +210,7 @@ module.exports = (RED) => {
209
210
  node.exposedGAs = JSON.parse(fs.readFileSync(sFile, "utf8"));
210
211
  } catch (err) {
211
212
  node.exposedGAs = [];
212
- if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("unable to read peristent file " + sFile + " " + err.message);
213
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("unable to read peristent file " + sFile + " " + err.message);
213
214
  }
214
215
  }
215
216
 
@@ -1165,16 +1166,25 @@ module.exports = (RED) => {
1165
1166
  // 26/12/2021 The KNXEngine is busy waiting for telegram's ACK. Strange.
1166
1167
  if (!node.knxConnection.clearToSend) {
1167
1168
  if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn(
1168
- "handleTelegramQueue: the KNXEngine is busy or is waiting for a telegram ACK with seqNumner " +
1169
+ "sendKNXTelegramToKNXEngine: the KNXEngine is busy or is waiting for a telegram ACK with seqNumner " +
1169
1170
  node.knxConnection.getSeqNumber() +
1170
- ". Delay handling queue. Len: 3",
1171
+ ". Delay handling queue.",
1171
1172
  );
1172
1173
  }
1173
1174
 
1174
1175
 
1175
1176
  // 19/01/2023 FORMATTING THE OUTPUT PAYLOAD (ROUND, ETC) BASED ON THE NODE CONFIG
1176
1177
  //* ********************************************************
1177
- _oKNXMessage.payload = payloadRounder.Manipulate(RED.nodes.getNode(_oKNXMessage.nodecallerid), _oKNXMessage.payload);
1178
+ if (_oKNXMessage.outputtype === "read") {
1179
+ try {
1180
+ _oKNXMessage.payload = payloadRounder.Manipulate(RED.nodes.getNode(_oKNXMessage.nodecallerid), _oKNXMessage.payload);
1181
+ } catch (error) {
1182
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(
1183
+ "sendKNXTelegramToKNXEngine: Sacripante Manipulate payload: " + error.message
1184
+ );
1185
+ }
1186
+
1187
+ }
1178
1188
  //* ********************************************************
1179
1189
 
1180
1190
  if (_oKNXMessage.outputtype === "response") {
@@ -1800,12 +1810,12 @@ module.exports = (RED) => {
1800
1810
  // 08/10/2021 Every xx seconds, i check if the connection is up and running
1801
1811
  if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("Autoconnection: " + (node.autoReconnect === false ? "no." : "yes") + " Node " + node.name);
1802
1812
  if (node.timerKNXUltimateCheckState !== null) clearInterval(node.timerKNXUltimateCheckState);
1803
- node.timerKNXUltimateCheckState = setInterval(() => {
1813
+ node.timerKNXUltimateCheckState = setInterval(async () => {
1804
1814
  // If the node is disconnected, wait another cycle, then reconnects
1805
1815
  if (node.allowLauch_initKNXConnection && node.autoReconnect) {
1806
1816
  node.allowLauch_initKNXConnection = false;
1807
1817
  const t = setTimeout(() => {
1808
- // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
1818
+ // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "const t = ".
1809
1819
  node.setAllClientsStatus("Auto reconnect in progress...", "grey", "");
1810
1820
  }, 100);
1811
1821
  if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug(
@@ -1814,7 +1824,7 @@ module.exports = (RED) => {
1814
1824
  ", node.autoReconnect:" +
1815
1825
  node.autoReconnect,
1816
1826
  );
1817
- node.initKNXConnection();
1827
+ await node.initKNXConnection();
1818
1828
  return;
1819
1829
  }
1820
1830
  if (node.linkStatus === "disconnected" && node.autoReconnect) {
@@ -1828,7 +1838,7 @@ module.exports = (RED) => {
1828
1838
  );
1829
1839
  // node.initKNXConnection();
1830
1840
  }
1831
- }, 4000);
1841
+ }, 5000);
1832
1842
 
1833
1843
  node.Disconnect = async (_sNodeStatus = "", _sColor = "grey") => {
1834
1844
  if (node.linkStatus === "disconnected") {
@@ -432,9 +432,7 @@ module.exports = function (RED) {
432
432
  gamut = node.currentHUEDevice.color.gamut;
433
433
  }
434
434
  const retXY = hueColorConverter.ColorConverter.calculateXYFromRGB(msg.payload.red, msg.payload.green, msg.payload.blue, gamut);
435
- let bright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(msg.payload.red, msg.payload.green, msg.payload.blue);
436
- bright = bright > 100 ? 100 : bright; // Otherwise it hangs and the light doesn't react anymore.
437
- // state = bright > 0 ? { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : { on: { on: false } }
435
+ const bright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(msg.payload.red, msg.payload.green, msg.payload.blue);
438
436
  state = { dimming: { brightness: bright }, color: { xy: retXY } };
439
437
  if (node.currentHUEDevice === undefined) {
440
438
  // Grouped light
@@ -53,6 +53,13 @@ module.exports = {
53
53
  }
54
54
  xy[0] = precision(xy[0]);
55
55
  xy[1] = precision(xy[1]);
56
+
57
+ // Check boundary (min 0, max 1)
58
+ xy[0] = xy[0] < 0 ? 0 : xy[0];
59
+ xy[0] = xy[0] > 1 ? 1 : xy[0];
60
+ xy[1] = xy[1] < 0 ? 0 : xy[1];
61
+ xy[1] = xy[1] > 1 ? 1 : xy[1];
62
+
56
63
  return { x: xy[0], y: xy[1] };
57
64
  },
58
65
  };
@@ -15,7 +15,13 @@ class ColorConverter {
15
15
 
16
16
  const Ylum = Rlin * 0.2126 + Glin * 0.7156 + Blin * 0.0722; // convert to Luminance Y
17
17
 
18
- return Ylum ** 0.43 * 100; // Convert to lightness (0 to 100)
18
+ let ret = Ylum ** 0.43 * 100;// Convert to lightness (0 to 100)
19
+
20
+ // Boundary Check (min 0, max 100)
21
+ ret = ret < 0 ? 0 : ret;
22
+ ret = ret > 100 ? 100 : ret;
23
+
24
+ return ret;
19
25
  }
20
26
 
21
27
  static convert_1_255_ToPercentage(number) {
@@ -2,11 +2,10 @@
2
2
  const { EventEmitter } = require("events");
3
3
  const EventSource = require("eventsource");
4
4
  const http = require("./http");
5
- // const pleaseWait = t => new Promise((resolve, reject) => setTimeout(resolve, t))
6
- const { RateLimiter } = require('limiter');
5
+ const pleaseWait = t => new Promise((resolve, reject) => setTimeout(resolve, t))
7
6
  //const { forEach } = require("lodash");
8
7
  // Configura il rate limiter
9
- const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 150 }); // HUE telegram interval
8
+ //const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 150 }); // HUE telegram interval
10
9
 
11
10
  class classHUE extends EventEmitter {
12
11
 
@@ -116,7 +115,7 @@ class classHUE extends EventEmitter {
116
115
  const ok = await this.hueApiV2.put(`/resource/light/${jRet._lightID}`, jRet._state);
117
116
  } catch (error) {
118
117
  if (this.sysLogger !== undefined && this.sysLogger !== null) {
119
- this.sysLogger.error(`KNXUltimatehueEngine: classHUE: handleQueue: setLight light: ${error.message}.`);
118
+ this.sysLogger.error(`KNXUltimatehueEngine: classHUE: processQueueItem: setLight light: ${error.message}.`);
120
119
  }
121
120
  }
122
121
  break;
@@ -125,7 +124,7 @@ class classHUE extends EventEmitter {
125
124
  await this.hueApiV2.put(`/resource/grouped_light/${jRet._lightID}`, jRet._state);
126
125
  } catch (error) {
127
126
  if (this.sysLogger !== undefined && this.sysLogger !== null)
128
- this.sysLogger.info(`KNXUltimatehueEngine: classHUE: handleQueue: setLight grouped_light: ${error.message}`);
127
+ this.sysLogger.info(`KNXUltimatehueEngine: classHUE: processQueueItem: setLight grouped_light: ${error.message}`);
129
128
  }
130
129
  break;
131
130
  case "setScene":
@@ -134,7 +133,7 @@ class classHUE extends EventEmitter {
134
133
  await this.hueApiV2.put(`/resource/scene/${sceneID}`, jRet._state);
135
134
  } catch (error) {
136
135
  if (this.sysLogger !== undefined && this.sysLogger !== null)
137
- this.sysLogger.info(`KNXUltimatehueEngine: classHUE: handleQueue: setScene: ${error.message}`);
136
+ this.sysLogger.info(`KNXUltimatehueEngine: classHUE: processQueueItem: setScene: ${error.message}`);
138
137
  }
139
138
  break;
140
139
  case "stopScene":
@@ -147,14 +146,14 @@ class classHUE extends EventEmitter {
147
146
  this.writeHueQueueAdd(light.rid, jRet._state, "setLight");
148
147
  });
149
148
  } catch (error) {
150
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: handleQueue: stopScene: ${error.message}`);
149
+ if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: processQueueItem: stopScene: ${error.message}`);
151
150
  }
152
151
  break;
153
152
  case "Ping":
154
153
  try {
155
154
  const jReturn = await this.hueApiV2.get('/resource/bridge');
156
155
  } catch (error) {
157
- if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: handleQueue: Ping: ${error.message}`);
156
+ if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error(`KNXUltimatehueEngine: classHUE: processQueueItem: Ping: ${error.message}`);
158
157
  if (this.timerCheckConnected !== null) clearInterval(this.timerCheckConnected);
159
158
  this.commandQueue = [];
160
159
  this.emit("disconnected");
@@ -174,10 +173,6 @@ class classHUE extends EventEmitter {
174
173
  handleQueue = async () => {
175
174
  // Verifica se è possibile eseguire una nuova richiesta
176
175
  do {
177
- try {
178
- const remainingRequests = await limiter.removeTokens(1);
179
- } catch (error) {
180
- }
181
176
  if (this.commandQueue !== undefined && this.commandQueue.length > 0) {
182
177
  //if (remainingRequests >= 0) {
183
178
  // OK, i can send
@@ -191,6 +186,7 @@ class classHUE extends EventEmitter {
191
186
  //console.log("\x1b[41m HO DETTO SPETA. remainingRequests=" + remainingRequests + "\x1b[0m " + new Date().toTimeString(), this.commandQueue.length, "remainingRequests " + remainingRequests);
192
187
  //}
193
188
  }
189
+ await pleaseWait(150);
194
190
  } while (this.closePushEventStream === false);
195
191
  //console.log("\x1b[42m End processing commandQueue \x1b[0m " + new Date().toTimeString(), this.commandQueue.length);
196
192
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=16.0.0"
5
5
  },
6
- "version": "3.2.16",
6
+ "version": "3.3.0",
7
7
  "description": "Control your KNX intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.",
8
8
  "dependencies": {
9
9
  "binary-parser": "2.2.1",
@@ -11,8 +11,7 @@
11
11
  "dns-sync": "0.2.1",
12
12
  "eventsource": "2.0.2",
13
13
  "js-yaml": "4.1.0",
14
- "knxultimate": "3.0.4",
15
- "limiter": "^2.1.0",
14
+ "knxultimate": "4.0.0-beta.4",
16
15
  "lodash": "4.17.21",
17
16
  "node-color-log": "12.0.1",
18
17
  "mkdirp": "3.0.1",