node-red-contrib-knx-ultimate 3.2.0 → 3.2.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 (33) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/nodes/commonFunctions.js +0 -1
  3. package/nodes/hue-config.js +12 -6
  4. package/nodes/knxUltimate-config copy.html +456 -0
  5. package/nodes/knxUltimate-config copy.js +1939 -0
  6. package/nodes/knxUltimate-config.html +4 -9
  7. package/nodes/knxUltimate-config.js +128 -213
  8. package/nodes/knxUltimate.js +41 -32
  9. package/nodes/knxUltimateAlerter.js +11 -15
  10. package/nodes/knxUltimateAutoResponder.js +21 -13
  11. package/nodes/knxUltimateGarageDoorBarrierOpener.js +16 -8
  12. package/nodes/knxUltimateGlobalContext.js +10 -10
  13. package/nodes/knxUltimateHueBattery.js +8 -8
  14. package/nodes/knxUltimateHueButton.js +9 -9
  15. package/nodes/knxUltimateHueContactSensor.js +7 -7
  16. package/nodes/knxUltimateHueLight.js +26 -26
  17. package/nodes/knxUltimateHueLightSensor.js +8 -8
  18. package/nodes/knxUltimateHueMotion.js +7 -7
  19. package/nodes/knxUltimateHueScene.js +17 -9
  20. package/nodes/knxUltimateHueTapDial.js +13 -13
  21. package/nodes/knxUltimateHueTemperatureSensor.js +8 -8
  22. package/nodes/knxUltimateHueZigbeeConnectivity.js +8 -8
  23. package/nodes/knxUltimateHuedevice_software_update.js +8 -8
  24. package/nodes/knxUltimateLoadControl.js +24 -21
  25. package/nodes/knxUltimateLogger.js +8 -8
  26. package/nodes/knxUltimateSceneController.js +20 -13
  27. package/nodes/knxUltimateViewer.js +8 -8
  28. package/nodes/knxUltimateWatchDog.js +14 -14
  29. package/nodes/utils/http.js +0 -1
  30. package/nodes/utils/hueEngine.js +13 -6
  31. package/nodes/utils/payloadManipulation.js +2 -2
  32. package/nodes/utils/sysLogger.js +23 -60
  33. package/package.json +3 -3
@@ -0,0 +1,1939 @@
1
+ /* eslint-disable prefer-template */
2
+ /* eslint-disable no-inner-declarations */
3
+ /* eslint-disable curly */
4
+ /* eslint-disable max-len */
5
+ /* eslint-disable prefer-arrow-callback */
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const net = require("net");
9
+ const _ = require("lodash");
10
+ const knx = require("knxultimate");
11
+ //const dptlib = require('knxultimate').dptlib;
12
+ const dptlib = require('knxultimate').dptlib;
13
+
14
+ // const { Server } = require('http')
15
+ const payloadRounder = require("./utils/payloadManipulation");
16
+ const loggerEngine = require("./utils/sysLogger.js");
17
+
18
+
19
+ // DATAPONT MANIPULATION HELPERS
20
+ // ####################
21
+ const sortBy = (field) => (a, b) => {
22
+ if (a[field] > b[field]) {
23
+ return 1;
24
+ } else {
25
+ return -1;
26
+ }
27
+ };
28
+
29
+ const onlyDptKeys = (kv) => {
30
+ return kv[0].startsWith("DPT");
31
+ };
32
+
33
+ const extractBaseNo = (kv) => {
34
+ return {
35
+ subtypes: kv[1].subtypes,
36
+ base: parseInt(kv[1].id.replace("DPT", "")),
37
+ };
38
+ };
39
+
40
+ const convertSubtype = (baseType) => (kv) => {
41
+ const value = `${baseType.base}.${kv[0]}`;
42
+ // let sRet = value + " " + kv[1].name + (kv[1].unit === undefined ? "" : " (" + kv[1].unit + ")");
43
+ const sRet = value + " " + kv[1].name;
44
+ return {
45
+ value,
46
+ text: sRet,
47
+ };
48
+ };
49
+
50
+ const toConcattedSubtypes = (acc, baseType) => {
51
+ const subtypes = Object.entries(baseType.subtypes).sort(sortBy(0)).map(convertSubtype(baseType));
52
+
53
+ return acc.concat(subtypes);
54
+ };
55
+ // ####################
56
+
57
+
58
+
59
+ module.exports = (RED) => {
60
+
61
+
62
+ function knxUltimateConfigNode(config) {
63
+ RED.nodes.createNode(this, config);
64
+ const node = this;
65
+ node.host = config.host;
66
+ node.port = parseInt(config.port);
67
+ node.physAddr = config.physAddr || "15.15.22"; // the KNX physical address we'd like to use
68
+ node.suppressACKRequest = typeof config.suppressACKRequest === "undefined" ? true : config.suppressACKRequest; // enable this option to suppress the acknowledge flag with outgoing L_Data.req requests. LoxOne needs this
69
+ node.linkStatus = "disconnected"; // Can be: connected or disconnected
70
+ node.nodeClients = []; // Stores the registered clients
71
+ node.KNXEthInterface = typeof config.KNXEthInterface === "undefined" ? "Auto" : config.KNXEthInterface;
72
+ node.KNXEthInterfaceManuallyInput = typeof config.KNXEthInterfaceManuallyInput === "undefined" ? "" : config.KNXEthInterfaceManuallyInput; // If you manually set the interface name, it will be wrote here
73
+ node.telegramsQueue = []; // 02/01/2020 Queue containing telegrams
74
+ node.timerSendTelegramFromQueue = null;
75
+ node.delaybetweentelegramsfurtherdelayREAD = typeof config.delaybetweentelegramsfurtherdelayREAD === "undefined" || Number(config.delaybetweentelegramsfurtherdelayREAD < 1) ? 1 : Number(config.delaybetweentelegramsfurtherdelayREAD); // 18/05/2020 delay multiplicator only for "read" telegrams.
76
+ node.delaybetweentelegramsREADCount = 0; // 18/05/2020 delay multiplicator only for "read" telegrams.
77
+ node.timerDoInitialRead = null; // 17/02/2020 Timer (timeout) to do initial read of all nodes requesting initial read, after all nodes have been registered to the sercer
78
+ node.stopETSImportIfNoDatapoint = typeof config.stopETSImportIfNoDatapoint === "undefined" ? "stop" : config.stopETSImportIfNoDatapoint; // 09/01/2020 Stop, Import Fake or Skip the import if a group address has unset datapoint
79
+ node.csv = readCSV(config.csv); // Array from ETS CSV Group Addresses {ga:group address, dpt: datapoint, devicename: full device name with main and subgroups}
80
+ node.localEchoInTunneling = typeof config.localEchoInTunneling !== "undefined" ? config.localEchoInTunneling : true;
81
+ node.userDir = path.join(RED.settings.userDir, "knxultimatestorage"); // 04/04/2021 Supergiovane: Storage for service files
82
+ node.exposedGAs = [];
83
+ node.loglevel = config.loglevel !== undefined ? config.loglevel : "error"; // 18/02/2020 Loglevel default error
84
+ try {
85
+ node.sysLogger = loggerEngine.get({ loglevel: node.loglevel }); // 08/04/2021 new logger to adhere to the loglevel selected in the config-window
86
+ } catch (error) { node.sysLogger = null }
87
+ // 12/11/2021 Connect at start delay
88
+ node.autoReconnect = true; // 20/03/2022 Default
89
+ if (config.autoReconnect === "no" || config.autoReconnect === false) {
90
+ node.autoReconnect = false;
91
+ } else {
92
+ node.autoReconnect = true;
93
+ }
94
+ node.ignoreTelegramsWithRepeatedFlag = config.ignoreTelegramsWithRepeatedFlag === undefined ? false : config.ignoreTelegramsWithRepeatedFlag;
95
+ // 24/07/2021 KNX Secure checks...
96
+ node.keyringFileXML = typeof config.keyringFileXML === "undefined" || config.keyringFileXML.trim() === "" ? "" : config.keyringFileXML;
97
+ node.knxSecureSelected = typeof config.knxSecureSelected === "undefined" ? false : config.knxSecureSelected;
98
+ node.name = config.name === undefined || config.name === "" ? node.host : config.name; // 12/08/2021
99
+ node.timerKNXUltimateCheckState = null; // 08/10/2021 Check the state. If not connected and autoreconnect is true, retrig the connetion attempt.
100
+ node.lockHandleTelegramQueue = false; // 12/11/2021 Lock sending telegrams if node disconnected or if already handling the queue
101
+ node.knxConnectionProperties = null; // Retains the connection properties
102
+ node.allowLauch_initKNXConnection = true; // See the node.timerKNXUltimateCheckState function
103
+ node.timerClearTelegramQueue = null; // Timer to clear the telegram's queue after long disconnection
104
+ node.hostProtocol = config.hostProtocol === undefined ? "Auto" : config.hostProtocol; // 20/03/2022 Default
105
+ node.knxConnection = null; // 20/03/2022 Default
106
+ // 15/12/2021
107
+
108
+ // 05/12/2021 Set the protocol (this is undefined if coming from ild versions
109
+ if (node.hostProtocol === "Auto") {
110
+ // Auto set protocol based on IP
111
+ if (
112
+ node.host.startsWith("224.") ||
113
+ node.host.startsWith("225.") ||
114
+ node.host.startsWith("232.") ||
115
+ node.host.startsWith("233.") ||
116
+ node.host.startsWith("234.") ||
117
+ node.host.startsWith("235.") ||
118
+ node.host.startsWith("239.")
119
+ ) {
120
+ node.hostProtocol = "Multicast";
121
+ } else {
122
+ node.hostProtocol = "TunnelUDP";
123
+ }
124
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("IP Protocol AUTO SET to " + node.hostProtocol + ", based on IP " + node.host);
125
+ }
126
+
127
+ node.setAllClientsStatus = (_status, _color, _text) => {
128
+ node.nodeClients.forEach((oClient) => {
129
+ try {
130
+ if (oClient.setNodeStatus !== undefined) oClient.setNodeStatus({
131
+ fill: _color,
132
+ shape: "dot",
133
+ text: _status + " " + _text,
134
+ payload: "",
135
+ GA: oClient.topic,
136
+ dpt: "",
137
+ devicename: "",
138
+ });
139
+ } catch (error) {
140
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn("Wow setAllClientsStatus error " + error.message);
141
+ }
142
+ });
143
+ };
144
+
145
+ // 21/01/2022 TTL Timer for clearung the node.telegramsQueue if the connection stays down for long time
146
+ node.startTimerClearTelegramQueue = () => {
147
+ if (node.timerClearTelegramQueue === null) {
148
+ node.timerClearTelegramQueue = setTimeout(() => {
149
+ const t = setTimeout(() => {
150
+ // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
151
+ node.setAllClientsStatus("Queue", "grey", "Deleted TX");
152
+ }, 200);
153
+ node.telegramsQueue = [];
154
+ node.timerClearTelegramQueue = null;
155
+ }, 30000);
156
+ }
157
+ };
158
+
159
+ //
160
+ // KNX-SECURE
161
+ // 15/11/2021 Function to load the keyring file exported from ETS
162
+ //
163
+ //
164
+ node.jKNXSecureKeyring = null;
165
+ try {
166
+ (async () => {
167
+ if (node.knxSecureSelected) {
168
+ node.jKNXSecureKeyring = await knx.KNXSecureKeyring.keyring.load(node.keyringFileXML, node.credentials.keyringFilePassword);
169
+ RED.log.info(
170
+ "KNX-Secure: Keyring for ETS proj " +
171
+ node.jKNXSecureKeyring.ETSProjectName +
172
+ ", created by " +
173
+ node.jKNXSecureKeyring.ETSCreatedBy +
174
+ " on " +
175
+ node.jKNXSecureKeyring.ETSCreated +
176
+ " succesfully validated with provided password, using node " +
177
+ node.name || node.id,
178
+ );
179
+ } else {
180
+ RED.log.info("KNX-Unsecure: connection to insecure interface/router using node " + node.name || node.id);
181
+ }
182
+ })();
183
+ } catch (error) {
184
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("KNXUltimate-config: KNX Secure: error parsing the keyring XML: " + error.message);
185
+ node.jKNXSecureKeyring = null;
186
+ node.knxSecureSelected = false;
187
+ const t = setTimeout(() => node.setAllClientsStatus("Error", "red", "KNX Secure " + error.message), 2000); // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
188
+ }
189
+
190
+ // 04/04/2021 Supergiovane, creates the service paths where the persistent files are created.
191
+ // The values file is stored only upon disconnection/close
192
+ // ************************
193
+ function setupDirectory(_aPath) {
194
+ if (!fs.existsSync(_aPath)) {
195
+ // Create the path
196
+ try {
197
+ fs.mkdirSync(_aPath);
198
+ return true;
199
+ } catch (error) {
200
+ return false;
201
+ }
202
+ } else {
203
+ return true;
204
+ }
205
+ }
206
+ if (!setupDirectory(node.userDir)) {
207
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("KNXUltimate-config: Unable to set up MAIN directory: " + node.userDir);
208
+ }
209
+ if (!setupDirectory(path.join(node.userDir, "knxpersistvalues"))) {
210
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("KNXUltimate-config: Unable to set up cache directory: " + path.join(node.userDir, "knxpersistvalues"));
211
+ } else {
212
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: payload cache set to " + path.join(node.userDir, "knxpersistvalues"));
213
+ }
214
+
215
+ function saveExposedGAs() {
216
+ const sFile = path.join(node.userDir, "knxpersistvalues", "knxpersist" + node.id + ".json");
217
+ try {
218
+ if (node.exposedGAs.length > 0) {
219
+ fs.writeFileSync(sFile, JSON.stringify(node.exposedGAs));
220
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: wrote peristent values to the file " + sFile);
221
+ }
222
+ } catch (err) {
223
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("KNXUltimate-config: unable to write peristent values to the file " + sFile + " " + err.message);
224
+ }
225
+ }
226
+ function loadExposedGAs() {
227
+ const sFile = path.join(node.userDir, "knxpersistvalues", "knxpersist" + node.id + ".json");
228
+ try {
229
+ node.exposedGAs = JSON.parse(fs.readFileSync(sFile, "utf8"));
230
+ } catch (err) {
231
+ node.exposedGAs = [];
232
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn("KNXUltimate-config: unable to read peristent file " + sFile + " " + err.message);
233
+ }
234
+ }
235
+
236
+ // ************************
237
+
238
+
239
+
240
+ // 16/02/2020 KNX-Ultimate nodes calls this function, then this funcion calls the same function on the Watchdog
241
+ node.reportToWatchdogCalledByKNXUltimateNode = (_oError) => {
242
+ // _oError is = { nodeid: node.id, topic: node.outputtopic, devicename: devicename, GA: GA, text: text };
243
+ const readHistory = [];
244
+ const delay = 0;
245
+ node.nodeClients
246
+ .filter((_oClient) => _oClient.isWatchDog !== undefined && _oClient.isWatchDog === true)
247
+ .forEach((_oClient) => {
248
+ _oClient.signalNodeErrorCalledByConfigNode(_oError);
249
+ });
250
+ };
251
+
252
+ node.addClient = (_Node) => {
253
+ // Check if node already exists
254
+ if (node.nodeClients.filter((x) => x.id === _Node.id).length === 0) {
255
+ // Add _Node to the clients array
256
+ if (node.autoReconnect) {
257
+ _Node.setNodeStatus({
258
+ fill: "grey",
259
+ shape: "ring",
260
+ text: "Node initialized.",
261
+ payload: "",
262
+ GA: "",
263
+ dpt: "",
264
+ devicename: "",
265
+ });
266
+ } else {
267
+ _Node.setNodeStatus({
268
+ fill: "red",
269
+ shape: "ring",
270
+ text: "Autoconnect disabled. Please manually connect.",
271
+ payload: "",
272
+ GA: "",
273
+ dpt: "",
274
+ devicename: "",
275
+ });
276
+ }
277
+ node.nodeClients.push(_Node);
278
+ }
279
+ };
280
+
281
+ node.removeClient = async (_Node) => {
282
+ // Remove the client node from the clients array
283
+ try {
284
+ node.nodeClients = node.nodeClients.filter((x) => x.id !== _Node.id);
285
+ } catch (error) { /* empty */ }
286
+
287
+ // If no clien nodes, disconnect from bus.
288
+ if (node.nodeClients.length === 0) {
289
+ try {
290
+ await node.Disconnect();
291
+ } catch (error) { /* empty */ }
292
+ }
293
+ };
294
+
295
+ // 17/02/2020 Do initial read (called by node.timerDoInitialRead timer)
296
+ function DoInitialReadFromKNXBusOrFile() {
297
+ if (node.linkStatus !== "connected") return; // 29/08/2019 If not connected, exit
298
+ loadExposedGAs(); // 04/04/2021 load the current values of GA payload
299
+ try {
300
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: Loaded saved GA values", node.exposedGAs.length);
301
+ } catch (error) { }
302
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: Do DoInitialReadFromKNXBusOrFile");
303
+ try {
304
+ const readHistory = [];
305
+
306
+ // First, read from file. This allow all virtual devices to get their values from file.
307
+ node.nodeClients
308
+ .filter((_oClient) => _oClient.initialread === 2 || _oClient.initialread === 3)
309
+ .filter((_oClient) => _oClient.hasOwnProperty("isWatchDog") === false)
310
+ .forEach((_oClient) => {
311
+
312
+ if (node.linkStatus !== "connected") return; // 16/08/2021 If not connected, exit
313
+
314
+ // 04/04/2020 selected READ FROM FILE 2 or from file then from bus 3
315
+ if (_oClient.listenallga === true) {
316
+ // 13/12/2021 DA FARE
317
+ } else {
318
+ try {
319
+ if (node.exposedGAs.length > 0) {
320
+ const oExposedGA = node.exposedGAs.find((a) => a.ga === _oClient.topic);
321
+ if (oExposedGA !== undefined) {
322
+ // Retrieve the value from exposedGAs
323
+ const msg = buildInputMessage({
324
+ _srcGA: "",
325
+ _destGA: _oClient.topic,
326
+ _event: "GroupValue_Response",
327
+ _Rawvalue: Buffer.from(oExposedGA.rawValue.data),
328
+ _inputDpt: _oClient.dpt,
329
+ _devicename: _oClient.name ? _oClient.name : "",
330
+ _outputtopic: _oClient.outputtopic,
331
+ _oNode: _oClient,
332
+ });
333
+ _oClient.previouspayload = ""; // 05/04/2021 Added previous payload
334
+ _oClient.currentPayload = msg.payload;
335
+ _oClient.setNodeStatus({
336
+ fill: "grey",
337
+ shape: "dot",
338
+ text: "Update value from persist file",
339
+ payload: _oClient.currentPayload,
340
+ GA: _oClient.topic,
341
+ dpt: _oClient.dpt,
342
+ devicename: _oClient.name || "",
343
+ });
344
+ // 06/05/2021 If, after the rawdata has been savad to file, the user changes the datapoint, the buildInputMessage returns payload null, because it's unable to convert the value
345
+ if (msg.payload === null) {
346
+ // Delete the exposedGA
347
+ node.exposedGAs = node.exposedGAs.filter((item) => item.ga !== _oClient.topic);
348
+ _oClient.setNodeStatus({
349
+ fill: "yellow",
350
+ shape: "dot",
351
+ text: "Datapoint has been changed, remove the value from persist file",
352
+ payload: _oClient.currentPayload,
353
+ GA: _oClient.topic,
354
+ dpt: _oClient.dpt,
355
+ devicename: _oClient.devicename || "",
356
+ });
357
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("knxUltimate-config: DoInitialReadFromKNXBusOrFile: Datapoint may have been changed, remove the value from persist file of " + _oClient.topic + " Devicename " + _oClient.name + " Currend DPT " + _oClient.dpt + " Node.id " + _oClient.id);
358
+ } else {
359
+ if (_oClient.notifyresponse) _oClient.handleSend(msg);
360
+ }
361
+ } else {
362
+ if (_oClient.initialread === 3) {
363
+ // Not found, issue a READ to the bus
364
+ if (!readHistory.includes(_oClient.topic)) {
365
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug("KNXUltimate-config: DoInitialReadFromKNXBusOrFile 3: sent read request to GA " + _oClient.topic);
366
+ _oClient.setNodeStatus({
367
+ fill: "grey",
368
+ shape: "dot",
369
+ text: "Persist value not found, issuing READ request to BUS",
370
+ payload: _oClient.currentPayload,
371
+ GA: _oClient.topic,
372
+ dpt: _oClient.dpt,
373
+ devicename: _oClient.devicename || "",
374
+ });
375
+ node.writeQueueAdd({
376
+ grpaddr: _oClient.topic,
377
+ payload: "",
378
+ dpt: "",
379
+ outputtype: "read",
380
+ nodecallerid: _oClient.id,
381
+ });
382
+ readHistory.push(_oClient.topic);
383
+ }
384
+ }
385
+ }
386
+ }
387
+ } catch (error) { }
388
+ }
389
+ });
390
+
391
+ // Then, after all values have been read from file, read from BUS
392
+ // This allow the virtual devices to get their values before this will be readed from bus
393
+ node.nodeClients
394
+ .filter((_oClient) => _oClient.initialread === 1)
395
+ .filter((_oClient) => _oClient.hasOwnProperty("isWatchDog") === false)
396
+ .forEach((_oClient) => {
397
+
398
+ if (node.linkStatus !== "connected") return; // 16/08/2021 If not connected, exit
399
+
400
+ // 04/04/2020 selected READ FROM BUS 1
401
+ if (_oClient.hasOwnProperty("isalertnode") && _oClient.isalertnode) {
402
+ _oClient.initialReadAllDevicesInRules();
403
+ } else if (_oClient.hasOwnProperty("isLoadControlNode") && _oClient.isLoadControlNode) {
404
+ _oClient.initialReadAllDevicesInRules();
405
+ } else if (_oClient.listenallga === true) {
406
+ for (let index = 0; index < node.csv.length; index++) {
407
+ const element = node.csv[index];
408
+ if (!readHistory.includes(element.ga)) {
409
+ node.writeQueueAdd({
410
+ grpaddr: element.ga,
411
+ payload: "",
412
+ dpt: "",
413
+ outputtype: "read",
414
+ nodecallerid: element.id,
415
+ });
416
+ readHistory.push(element.ga);
417
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug("KNXUltimate-config: DoInitialReadFromKNXBusOrFile from Universal Node: sent read request to GA " + element.ga);
418
+ }
419
+ }
420
+ } else {
421
+ if (!readHistory.includes(_oClient.topic)) {
422
+ node.writeQueueAdd({
423
+ grpaddr: _oClient.topic,
424
+ payload: "",
425
+ dpt: "",
426
+ outputtype: "read",
427
+ nodecallerid: _oClient.id,
428
+ });
429
+ readHistory.push(_oClient.topic);
430
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug("KNXUltimate-config: DoInitialReadFromKNXBusOrFile: sent read request to GA " + _oClient.topic);
431
+ }
432
+ }
433
+ });
434
+ } catch (error) { }
435
+ }
436
+
437
+ // 01/02/2020 Dinamic change of the KNX Gateway IP, Port and Physical Address
438
+ // This new thing has been requested by proServ RealKNX staff.
439
+ node.setGatewayConfig = async (
440
+ /** @type {string} */ _sIP,
441
+ /** @type {number} */ _iPort,
442
+ /** @type {string} */ _sPhysicalAddress,
443
+ /** @type {string} */ _sBindToEthernetInterface,
444
+ /** @type {string} */ _Protocol,
445
+ /** @type {string} */ _CSV,
446
+ ) => {
447
+ if (typeof _sIP !== "undefined" && _sIP !== "") node.host = _sIP;
448
+ if (typeof _iPort !== "undefined" && _iPort !== 0) node.port = _iPort;
449
+ if (typeof _sPhysicalAddress !== "undefined" && _sPhysicalAddress !== "") node.physAddr = _sPhysicalAddress;
450
+ if (typeof _sBindToEthernetInterface !== "undefined") node.KNXEthInterface = _sBindToEthernetInterface;
451
+ if (typeof _Protocol !== "undefined") node.hostProtocol = _Protocol;
452
+ if (typeof _CSV !== "undefined" && _CSV !== "") {
453
+ try {
454
+ const sTemp = readCSV(_CSV); // 27/09/2022 Set the new CSV
455
+ node.csv = sTemp;
456
+ } catch (error) {
457
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("Node's main config setting error. " + error.message || "");
458
+ }
459
+ }
460
+
461
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(
462
+ "Node's main config setting has been changed. New config: IP " +
463
+ node.host +
464
+ " Port " +
465
+ node.port +
466
+ " PhysicalAddress " +
467
+ node.physAddr +
468
+ " BindToInterface " +
469
+ node.KNXEthInterface +
470
+ (typeof _CSV !== "undefined" && _CSV !== "" ? ". A new group address CSV has been imported." : ""),
471
+ );
472
+
473
+ try {
474
+ await node.Disconnect();
475
+ // node.setKnxConnectionProperties(); // 28/12/2021 Commented
476
+ node.setAllClientsStatus("CONFIG", "yellow", "KNXUltimage-config:setGatewayConfig: disconnected by new setting...");
477
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug("KNXUltimage-config:setGatewayConfig: disconnected by setGatewayConfig.");
478
+ } catch (error) { }
479
+ };
480
+
481
+ // 05/05/2021 force connection or disconnection from the KNX BUS and disable the autoreconenctions attempts.
482
+ // This new thing has been requested by proServ RealKNX staff.
483
+ node.connectGateway = async (_bConnection) => {
484
+ if (_bConnection === undefined) return;
485
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(
486
+ (_bConnection === true ? "Forced connection from watchdog" : "Forced disconnection from watchdog") +
487
+ node.host +
488
+ " Port " +
489
+ node.port +
490
+ " PhysicalAddress " +
491
+ node.physAddr +
492
+ " BindToInterface " +
493
+ node.KNXEthInterface,
494
+ );
495
+ if (_bConnection === true) {
496
+ // CONNECT AND ENABLE RECONNECTION ATTEMPTS
497
+ try {
498
+ await node.Disconnect();
499
+ node.setAllClientsStatus("CONFIG", "yellow", "Forced GW connection from watchdog.");
500
+ node.autoReconnect = true;
501
+ } catch (error) { }
502
+ } else {
503
+ // DISCONNECT AND DISABLE RECONNECTION ATTEMPTS
504
+ try {
505
+ node.autoReconnect = false;
506
+ await node.Disconnect();
507
+ const t = setTimeout(() => {
508
+ // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
509
+ node.setAllClientsStatus("CONFIG", "yellow", "Forced GW disconnection and stop reconnection attempts, from watchdog.");
510
+ }, 2000);
511
+ } catch (error) { }
512
+ }
513
+ };
514
+
515
+ // 08/10/2021
516
+ // node.knxConnectionProperties must be:
517
+ // const optionsDefaults = {
518
+ // physAddr: '15.15.200',
519
+ // connectionKeepAliveTimeout: KNXConstants.KNX_CONSTANTS.CONNECTION_ALIVE_TIME,
520
+ // ipAddr: "224.0.23.12",
521
+ // ipPort: 3671,
522
+ // hostProtocol: "TunnelUDP", // TunnelUDP, TunnelTCP, Multicast
523
+ // isSecureKNXEnabled: false,
524
+ // suppress_ack_ldatareq: false,
525
+ // loglevel: "info",
526
+ // localEchoInTunneling: true,
527
+ // localIPAddress: "",
528
+ // jKNXSecureKeyring: node.jKNXSecureKeyring
529
+ // interface: ""
530
+ // };
531
+
532
+ node.setKnxConnectionProperties = () => {
533
+ // 25/08/2021 Moved out of node.initKNXConnection
534
+ node.knxConnectionProperties = {
535
+ ipAddr: node.host,
536
+ ipPort: node.port,
537
+ physAddr: node.physAddr, // the KNX physical address we'd like to use
538
+ suppress_ack_ldatareq: node.suppressACKRequest,
539
+ loglevel: node.loglevel,
540
+ localEchoInTunneling: node.localEchoInTunneling, // 14/03/2020 local echo in tunneling mode (see API Supergiovane)
541
+ hostProtocol: node.hostProtocol,
542
+ isSecureKNXEnabled: node.knxSecureSelected,
543
+ jKNXSecureKeyring: node.jKNXSecureKeyring,
544
+ localIPAddress: "", // Riempito da KNXEngine
545
+ };
546
+ // 11/07/2022 Test if the IP is a valid one or is a DNS Name
547
+ switch (net.isIP(node.host)) {
548
+ case 0:
549
+ // Invalid IP, resolve the DNS name.
550
+ const dns = require("dns-sync");
551
+ let resolvedIP = null;
552
+ try {
553
+ resolvedIP = dns.resolve(node.host);
554
+ } catch (error) {
555
+ throw new Error("net.isIP: INVALID IP OR DNS NAME. Error checking the Gateway Host in Config node. " + error.message);
556
+ }
557
+ if (resolvedIP === null || net.isIP(resolvedIP) === 0) {
558
+ // Error in resolving DNS Name
559
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(
560
+ "knxUltimate-config: net.isIP: INVALID IP OR DNS NAME. Check the Gateway Host in Config node " + node.name + " " + node.host,
561
+ );
562
+ throw new Error("net.isIP: INVALID IP OR DNS NAME. Check the Gateway Host in Config node.");
563
+ }
564
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(
565
+ "knxUltimate-config: net.isIP: The gateway is not specified as IP. The DNS resolver pointed me to the IP " +
566
+ node.host +
567
+ ", in Config node " +
568
+ node.name,
569
+ );
570
+ node.knxConnectionProperties.ipAddr = resolvedIP;
571
+ case 4:
572
+ // It's an IPv4
573
+ break;
574
+ case 6:
575
+ // It's an IPv6
576
+ break;
577
+ default:
578
+ break;
579
+ }
580
+
581
+ if (node.KNXEthInterface !== "Auto") {
582
+ let sIfaceName = "";
583
+ if (node.KNXEthInterface === "Manual") {
584
+ sIfaceName = node.KNXEthInterfaceManuallyInput;
585
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: Bind KNX Bus to interface : " + sIfaceName + " (Interface's name entered by hand). Node " + node.name);
586
+ } else {
587
+ sIfaceName = node.KNXEthInterface;
588
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(
589
+ "KNXUltimate-config: Bind KNX Bus to interface : " + sIfaceName + " (Interface's name selected from dropdown list). Node " + node.name,
590
+ );
591
+ }
592
+ node.knxConnectionProperties.interface = sIfaceName;
593
+ } else {
594
+ // 08/10/2021 Delete the interface
595
+ try {
596
+ delete node.knxConnectionProperties.interface;
597
+ } catch (error) { }
598
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: Bind KNX Bus to interface (Auto). Node " + node.name);
599
+ }
600
+ };
601
+ // node.setKnxConnectionProperties(); 28/12/2021 Commented
602
+
603
+ node.initKNXConnection = async () => {
604
+ try {
605
+ node.setKnxConnectionProperties(); // 28/12/2021 Added
606
+ } catch (error) {
607
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("knxUltimate-config: setKnxConnectionProperties: " + error.message);
608
+ if (node.linkStatus !== "disconnected") await node.Disconnect();
609
+ return;
610
+ }
611
+
612
+ // 12/08/2021 Avoid start connection if there are no knx-ultimate nodes linked to this gateway
613
+ // At start, initKNXConnection is already called only if the gateway has clients, but in the successive calls from the error handler, this check is not done.
614
+ if (node.nodeClients.length === 0) {
615
+ try {
616
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("knxUltimate-config: No nodes linked to this gateway " + node.name);
617
+ try {
618
+ if (node.linkStatus !== "disconnected") await node.Disconnect();
619
+ } catch (error) { }
620
+ return;
621
+ } catch (error) { }
622
+ }
623
+
624
+ try {
625
+ // 02/01/2022 This is important to free the tunnel in case of hard disconnection.
626
+ await node.Disconnect();
627
+ } catch (error) {
628
+ // if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(error)
629
+ }
630
+
631
+ try {
632
+ // Unsetting handlers if node.knxConnection was existing
633
+ try {
634
+ if (node.knxConnection !== null && node.knxConnection !== undefined) {
635
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug("knxUltimate-config: removing old handlers. Node " + node.name);
636
+ node.knxConnection.removeAllListeners();
637
+ }
638
+ } catch (error) {
639
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("BANANA ERRORINO", error);
640
+ }
641
+
642
+ //node.knxConnectionProperties.localSocketAddress = { address: '192.168.2.2', port: 59000 }
643
+ node.knxConnection = new knx.KNXClient(node.knxConnectionProperties);
644
+
645
+ // Setting handlers
646
+ // ######################################
647
+ node.knxConnection.on(knx.KNXClientEvents.indication, handleBusEvents);
648
+ node.knxConnection.on(knx.KNXClientEvents.error, (err) => {
649
+ try {
650
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("knxUltimate-config: received KNXClientEvents.error: " + (err.message === undefined ? err : err.message));
651
+ } catch (error) {
652
+ }
653
+ // 31/03/2022 Don't care about some errors
654
+ if (err.message !== undefined && (err.message === "ROUTING_LOST_MESSAGE" || err.message === "ROUTING_BUSY")) {
655
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(
656
+ "knxUltimate-config: KNXClientEvents.error: " +
657
+ (err.message === undefined ? err : err.message) +
658
+ " consider DECREASING the transmission speed, by increasing the telegram's DELAY in the gateway configuration node!",
659
+ );
660
+ return;
661
+ }
662
+ node.Disconnect("Disconnected by error " + (err.message === undefined ? err : err.message), "red");
663
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("knxUltimate-config: Disconnected by: " + (err.message === undefined ? err : err.message));
664
+ });
665
+ // Call discoverCB when a knx gateway has been discovered.
666
+ // node.knxConnection.on(knx.KNXClientEvents.discover, info => {
667
+ // const [ip, port] = info.split(":");
668
+ // discoverCB(ip, port);
669
+ // });
670
+ node.knxConnection.on(knx.KNXClientEvents.disconnected, (info) => {
671
+ node.startTimerClearTelegramQueue(); // 21/01/2022 Clear the telegram queue after a while
672
+ if (node.linkStatus !== "disconnected") {
673
+ node.linkStatus = "disconnected";
674
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn("knxUltimate-config: Disconnected event %s", info);
675
+ node.Disconnect("Disconnected by event: " + info || "", "red"); // 11/03/2022
676
+ }
677
+ });
678
+ node.knxConnection.on(knx.KNXClientEvents.close, (info) => {
679
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug("knxUltimate-config: KNXClient socket closed.");
680
+ });
681
+ node.knxConnection.on(knx.KNXClientEvents.connected, (info) => {
682
+ if (node.timerClearTelegramQueue !== null) {
683
+ clearTimeout(node.timerClearTelegramQueue); // Connected. Stop the timer that clears the telegrams queue.
684
+ node.timerClearTelegramQueue = null;
685
+ }
686
+ // 12/11/2021 Starts the telegram out queue handler
687
+ if (node.timerSendTelegramFromQueue !== null) clearInterval(node.timerSendTelegramFromQueue);
688
+ node.timerSendTelegramFromQueue = setInterval(handleTelegramQueue, config.delaybetweentelegrams === undefined || Number(config.delaybetweentelegrams) < 20 ? 20 : Number(config.delaybetweentelegrams)); // 02/01/2020 Start the timer that handles the queue of telegrams
689
+ node.linkStatus = "connected";
690
+
691
+ // Start the timer to do initial read.
692
+ if (node.timerDoInitialRead !== null) clearTimeout(node.timerDoInitialRead);
693
+ node.timerDoInitialRead = setTimeout(() => {
694
+ try {
695
+ DoInitialReadFromKNXBusOrFile();
696
+ } catch (error) {
697
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("knxUltimate-config: DoInitialReadFromKNXBusOrFile " + error.stack);
698
+ }
699
+ }, 6000); // 17/02/2020 Do initial read of all nodes requesting initial read
700
+ const t = setTimeout(() => {
701
+ // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
702
+ node.setAllClientsStatus("Connected.", "green", "On duty.");
703
+ }, 500);
704
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("knxUltimate-config: Connected to %o", info);
705
+ });
706
+ node.knxConnection.on(knx.KNXClientEvents.connecting, (info) => {
707
+ node.linkStatus = "connecting";
708
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug("knxUltimate-config: Connecting to" + info.ipAddr || "");
709
+ node.setAllClientsStatus(info.ipAddr || "", "grey", "Connecting...");
710
+ });
711
+ // ######################################
712
+
713
+ node.setAllClientsStatus("Connecting... ", "grey", "");
714
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("knxUltimate-config: perform websocket connection on " + node.name);
715
+ try {
716
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: Connecting... " + node.name);
717
+ node.knxConnection.Connect();
718
+ } catch (error) {
719
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("KNXUltimate-config: node.knxConnection.Connect() " + node.name + ": " + error.message);
720
+ node.linkStatus = "disconnected";
721
+ throw error;
722
+ }
723
+ } catch (error) {
724
+ if (node.sysLogger !== undefined && node.sysLogger !== null) {
725
+ node.sysLogger.error("KNXUltimate-config: Error in instantiating knxConnection " + error.stack + " Node " + node.name);
726
+ node.error("KNXUltimate-config: Error in instantiating knxConnection " + error.message + " Node " + node.name);
727
+ }
728
+ node.linkStatus = "disconnected";
729
+ // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
730
+ const t = setTimeout(() => node.setAllClientsStatus("Error in instantiating knxConnection " + error.message, "red", "Error"), 200);
731
+ }
732
+ };
733
+
734
+ // Handle BUS events
735
+ // ---------------------------------------------------------------------------------------
736
+ function handleBusEvents(_datagram, _echoed) {
737
+ // console.time('handleBusEvents');
738
+
739
+ let _rawValue = null;
740
+ try {
741
+ _rawValue = _datagram.cEMIMessage.npdu.dataValue;
742
+ } catch (error) {
743
+ return;
744
+ }
745
+
746
+ let _evt = null;
747
+ if (_datagram.cEMIMessage.npdu.isGroupRead) _evt = "GroupValue_Read";
748
+ if (_datagram.cEMIMessage.npdu.isGroupResponse) _evt = "GroupValue_Response";
749
+ if (_datagram.cEMIMessage.npdu.isGroupWrite) _evt = "GroupValue_Write";
750
+
751
+ let _src = null;
752
+ _src = _datagram.cEMIMessage.srcAddress.toString();
753
+
754
+ let _dest = null;
755
+ _dest = _datagram.cEMIMessage.dstAddress.toString();
756
+
757
+ const isRepeated = _datagram.cEMIMessage.control.repeat !== 1;
758
+ // 06/06/2021 Supergiovane: check if i can handle the telegrams with "Repeated" flag
759
+ if (node.ignoreTelegramsWithRepeatedFlag === true && isRepeated) {
760
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn("KNXUltimate-config: Ignored telegram with Repeated Flag " + _evt + " Src:" + _src + " Dest:" + _dest);
761
+ return;
762
+ }
763
+
764
+ // 23/03/2021 Supergiovane: Added the CEMI telegram for ETS Diagnostic
765
+ // #####################################################################
766
+ let _cemiETS = "";
767
+ if (_echoed) {
768
+ // I'm sending a telegram to the BUS in Tunneling mode, with echo enabled.
769
+ // Tunnel: TX to BUS: OK
770
+ try {
771
+ const sCemiFromDatagram = _datagram.cEMIMessage.toBuffer().toString("hex");
772
+ _cemiETS = "2900BCD0" + sCemiFromDatagram.substr(8);
773
+ } catch (error) {
774
+ _cemiETS = "";
775
+ }
776
+ } else {
777
+ try {
778
+ // Multicast: RX from BUS: OK
779
+ // Multicast TX to BUS: OK
780
+ // Tunnel: RX from BUS: OK
781
+ // Tunnel: TX to BUS: see the _echoed above
782
+ _cemiETS = _datagram.cEMIMessage.toBuffer().toString("hex");
783
+ } catch (error) {
784
+ _cemiETS = "";
785
+ }
786
+ }
787
+ // #####################################################################
788
+
789
+ // 04/04/2021 Supergiovane: save value to node.exposedGAs
790
+ if (typeof _dest === "string" && _rawValue !== undefined && (_evt === "GroupValue_Write" || _evt === "GroupValue_Response")) {
791
+ try {
792
+ const ret = { ga: _dest, rawValue: _rawValue, dpt: undefined, devicename: undefined };
793
+ node.exposedGAs = node.exposedGAs.filter((item) => item.ga !== _dest); // Remove previous
794
+ if (node.csv !== undefined && node.csv !== '' && node.csv.length !== 0) {
795
+ // Add the dpt
796
+ const found = node.csv.find(a => a.ga === _dest);
797
+ if (found !== undefined) {
798
+ ret.dpt = found.dpt;
799
+ ret.devicename = found.devicename;
800
+ }
801
+ }
802
+ node.exposedGAs.push(ret); // add the new
803
+ } catch (error) { }
804
+ }
805
+
806
+ switch (_evt) {
807
+ case "GroupValue_Write":
808
+ // console.time('GroupValue_Write'); // 05/04/2022 Fatto test velocità tra for..loop e forEach. E' risultato sempre comunque più veloce il forEach!
809
+ node.nodeClients
810
+ .filter((_input) => _input.notifywrite === true)
811
+ .forEach((_input) => {
812
+
813
+ // 19/03/2020 in the middle of coronavirus. Whole italy is red zone, closed down. Scene Controller implementation
814
+ if (_input.hasOwnProperty("isSceneController")) {
815
+ // 12/08/2020 Check wether is a learn (save) command or a activate (play) command.
816
+ if (_dest === _input.topic || _dest === _input.topicSave) {
817
+ // Prepare the two messages to be evaluated directly into the Scene Controller node.
818
+ new Promise((resolve) => {
819
+ if (_dest === _input.topic) {
820
+ try {
821
+ const msgRecall = buildInputMessage({
822
+ _srcGA: _src,
823
+ _destGA: _dest,
824
+ _event: _evt,
825
+ _Rawvalue: _rawValue,
826
+ _inputDpt: _input.dpt,
827
+ _devicename: _input.name ? _input.name : "",
828
+ _outputtopic: _input.outputtopic,
829
+ _oNode: null,
830
+ });
831
+ _input.RecallScene(msgRecall.payload, false);
832
+ } catch (error) { }
833
+ } // 12/08/2020 Do NOT use "else", because both topics must be evaluated in case both recall and save have same group address.
834
+ if (_dest === _input.topicSave) {
835
+ try {
836
+ const msgSave = buildInputMessage({
837
+ _srcGA: _src,
838
+ _destGA: _dest,
839
+ _event: _evt,
840
+ _Rawvalue: _rawValue,
841
+ _inputDpt: _input.dptSave,
842
+ _devicename: _input.name || "",
843
+ _outputtopic: _dest,
844
+ _oNode: null,
845
+ });
846
+ _input.SaveScene(msgSave.payload, false);
847
+ } catch (error) { }
848
+ }
849
+ resolve(true); // fulfilled
850
+ // reject("error"); // rejected
851
+ })
852
+ .then(function () { })
853
+ .catch(function () { });
854
+ } else {
855
+ // 19/03/2020 Check and Update value if the input is part of a scene controller
856
+ new Promise((resolve) => {
857
+ // Check and update the values of each device in the scene and update the rule array accordingly.
858
+ for (let i = 0; i < _input.rules.length; i++) {
859
+ // rule is { topic: rowRuleTopic, devicename: rowRuleDeviceName, dpt:rowRuleDPT, send: rowRuleSend}
860
+ const oDevice = _input.rules[i];
861
+ if (typeof oDevice !== "undefined" && oDevice.topic == _dest) {
862
+ const msg = buildInputMessage({
863
+ _srcGA: _src,
864
+ _destGA: _dest,
865
+ _event: _evt,
866
+ _Rawvalue: _rawValue,
867
+ _inputDpt: oDevice.dpt,
868
+ _devicename: oDevice.name || "",
869
+ _outputtopic: oDevice.outputtopic,
870
+ _oNode: null,
871
+ });
872
+ oDevice.currentPayload = msg.payload;
873
+ _input.setNodeStatus({
874
+ fill: "grey",
875
+ shape: "dot",
876
+ text: "Update dev in scene",
877
+ payload: oDevice.currentPayload,
878
+ GA: oDevice.topic,
879
+ dpt: oDevice.dpt,
880
+ devicename: oDevice.devicename || "",
881
+ });
882
+ break;
883
+ }
884
+ }
885
+ resolve(true); // fulfilled
886
+ // reject("error"); // rejected
887
+ })
888
+ .then(function () { })
889
+ .catch(function () { });
890
+ }
891
+ } else if (_input.hasOwnProperty("isLogger")) {
892
+ // 26/03/2020 Coronavirus is slightly decreasing the affected numer of people. Logger Node
893
+ // 24/03/2021 Logger Node, i'll pass cemiETS
894
+ if (_cemiETS !== undefined) {
895
+ // new Promise((resolve, reject) => {
896
+ _input.handleSend(_cemiETS);
897
+ // resolve(true); // fulfilled
898
+ // reject("error"); // rejected
899
+ // }).then(function () { }).catch(function () { });
900
+ }
901
+ } else if (_input.listenallga === true) {
902
+
903
+ // 25/10/2019 TRY TO AUTO DECODE IF Group address not found in the CSV
904
+ const msg = buildInputMessage({
905
+ _srcGA: _src,
906
+ _destGA: _dest,
907
+ _event: _evt,
908
+ _Rawvalue: _rawValue,
909
+ _outputtopic: _dest,
910
+ _oNode: _input
911
+ });
912
+ _input.setNodeStatus({
913
+ fill: "green",
914
+ shape: "dot",
915
+ text: "",
916
+ payload: msg.payload,
917
+ GA: msg.knx.destination,
918
+ dpt: msg.knx.dpt,
919
+ devicename: msg.devicename,
920
+ });
921
+ _input.handleSend(msg);
922
+ } else if (_input.topic == _dest) {
923
+ if (_input.hasOwnProperty("isWatchDog")) {
924
+ // 04/02/2020 Watchdog implementation
925
+ // Is a watchdog node
926
+ } else {
927
+ const msg = buildInputMessage({
928
+ _srcGA: _src,
929
+ _destGA: _dest,
930
+ _event: _evt,
931
+ _Rawvalue: _rawValue,
932
+ _inputDpt: _input.dpt,
933
+ _devicename: _input.name ? _input.name : "",
934
+ _outputtopic: _input.outputtopic,
935
+ _oNode: _input,
936
+ });
937
+ // Check RBE INPUT from KNX Bus, to avoid send the payload to the flow, if it's equal to the current payload
938
+ if (!checkRBEInputFromKNXBusAllowSend(_input, msg.payload)) {
939
+ _input.setNodeStatus({
940
+ fill: "grey",
941
+ shape: "ring",
942
+ text: "rbe block (" + msg.payload + ") from KNX",
943
+ payload: "",
944
+ GA: "",
945
+ dpt: "",
946
+ devicename: "",
947
+ });
948
+ return;
949
+ }
950
+ msg.previouspayload = typeof _input.currentPayload !== "undefined" ? _input.currentPayload : ""; // 24/01/2020 Added previous payload
951
+ _input.currentPayload = msg.payload; // Set the current value for the RBE input
952
+ _input.setNodeStatus({
953
+ fill: "green",
954
+ shape: "dot",
955
+ text: "",
956
+ payload: msg.payload,
957
+ GA: _input.topic,
958
+ dpt: _input.dpt,
959
+ devicename: "",
960
+ });
961
+ _input.handleSend(msg);
962
+ }
963
+ }
964
+ });
965
+ // console.timeEnd('GroupValue_Write');
966
+ break;
967
+
968
+ case "GroupValue_Response":
969
+ node.nodeClients
970
+ .filter((_input) => _input.notifyresponse === true)
971
+ .forEach((_input) => {
972
+
973
+ if (_input.hasOwnProperty("isLogger")) {
974
+ // 26/03/2020 Coronavirus is slightly decreasing the affected numer of people. Logger Node
975
+ // 24/03/2021 Logger Node, i'll pass cemiETS
976
+ if (_cemiETS !== undefined) {
977
+ // new Promise((resolve, reject) => {
978
+ _input.handleSend(_cemiETS);
979
+ // resolve(true); // fulfilled
980
+ // reject("error"); // rejected
981
+ // }).then(function () { }).catch(function () { });
982
+ }
983
+ } else if (_input.listenallga === true) {
984
+
985
+ const msg = buildInputMessage({
986
+ _srcGA: _src,
987
+ _destGA: _dest,
988
+ _event: _evt,
989
+ _Rawvalue: _rawValue,
990
+ _outputtopic: _dest,
991
+ _oNode: _input
992
+ });
993
+ _input.setNodeStatus({
994
+ fill: "blue",
995
+ shape: "dot",
996
+ text: "",
997
+ payload: msg.payload,
998
+ GA: msg.knx.destination,
999
+ dpt: msg.knx.dpt,
1000
+ devicename: msg.devicename,
1001
+ });
1002
+ _input.handleSend(msg);
1003
+ } else if (_input.topic === _dest) {
1004
+ // 04/02/2020 Watchdog implementation
1005
+ if (_input.hasOwnProperty("isWatchDog")) {
1006
+ // Is a watchdog node
1007
+ _input.watchDogTimerReset();
1008
+ } else {
1009
+ const msg = buildInputMessage({
1010
+ _srcGA: _src,
1011
+ _destGA: _dest,
1012
+ _event: _evt,
1013
+ _Rawvalue: _rawValue,
1014
+ _inputDpt: _input.dpt,
1015
+ _devicename: _input.name ? _input.name : "",
1016
+ _outputtopic: _input.outputtopic,
1017
+ _oNode: _input,
1018
+ });
1019
+ // Check RBE INPUT from KNX Bus, to avoid send the payload to the flow, if it's equal to the current payload
1020
+ if (!checkRBEInputFromKNXBusAllowSend(_input, msg.payload)) {
1021
+ _input.setNodeStatus({
1022
+ fill: "grey",
1023
+ shape: "ring",
1024
+ text: "rbe INPUT filter applied on " + msg.payload,
1025
+ payload: msg.payload,
1026
+ GA: _dest,
1027
+ });
1028
+ return;
1029
+ }
1030
+ msg.previouspayload = typeof _input.currentPayload !== "undefined" ? _input.currentPayload : ""; // 24/01/2020 Added previous payload
1031
+ _input.currentPayload = msg.payload; // Set the current value for the RBE input
1032
+ _input.setNodeStatus({
1033
+ fill: "blue",
1034
+ shape: "dot",
1035
+ text: "",
1036
+ payload: msg.payload,
1037
+ GA: _input.topic,
1038
+ dpt: msg.knx.dpt,
1039
+ devicename: msg.devicename,
1040
+ });
1041
+ _input.handleSend(msg);
1042
+ }
1043
+ }
1044
+ });
1045
+ break;
1046
+
1047
+ case "GroupValue_Read":
1048
+ node.nodeClients
1049
+ .filter((_input) => _input.notifyreadrequest === true)
1050
+ .forEach((_input) => {
1051
+
1052
+ if (_input.hasOwnProperty("isLogger")) {
1053
+ // 26/03/2020 Coronavirus is slightly decreasing the affected numer of people. Logger Node
1054
+ // if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("BANANA isLogger", _evt, _src, _dest, _rawValue, _cemiETS);
1055
+ // 24/03/2021 Logger Node, i'll pass cemiETS
1056
+ if (_cemiETS !== undefined) {
1057
+ // new Promise((resolve, reject) => {
1058
+ _input.handleSend(_cemiETS);
1059
+ // resolve(true); // fulfilled
1060
+ // reject("error"); // rejected
1061
+ // }).then(function () { }).catch(function () { });
1062
+ }
1063
+ } else if (_input.listenallga === true) {
1064
+
1065
+ // Read Request
1066
+ const msg = buildInputMessage({
1067
+ _srcGA: _src,
1068
+ _destGA: _dest,
1069
+ _event: _evt,
1070
+ _Rawvalue: null,
1071
+ _outputtopic: _dest,
1072
+ _oNode: _input
1073
+ });
1074
+ _input.setNodeStatus({
1075
+ fill: "grey",
1076
+ shape: "dot",
1077
+ text: "Read",
1078
+ payload: "",
1079
+ GA: msg.knx.destination,
1080
+ dpt: msg.knx.dpt,
1081
+ devicename: msg.devicename,
1082
+ });
1083
+ _input.handleSend(msg);
1084
+ } else if (_input.topic === _dest) {
1085
+ // 04/02/2020 Watchdog implementation
1086
+ if (_input.hasOwnProperty("isWatchDog")) {
1087
+ // Is a watchdog node
1088
+ } else {
1089
+ // Read Request
1090
+ const msg = buildInputMessage({
1091
+ _srcGA: _src,
1092
+ _destGA: _dest,
1093
+ _event: _evt,
1094
+ _Rawvalue: null,
1095
+ _inputDpt: _input.dpt,
1096
+ _devicename: _input.name || "",
1097
+ _outputtopic: _input.outputtopic,
1098
+ _oNode: _input,
1099
+ });
1100
+ msg.previouspayload = typeof _input.currentPayload !== "undefined" ? _input.currentPayload : ""; // 24/01/2020 Reset previous payload
1101
+ // 24/09/2019 Autorespond to BUS
1102
+ if (_input.hasOwnProperty("notifyreadrequestalsorespondtobus") && _input.notifyreadrequestalsorespondtobus === true) {
1103
+ if (typeof _input.currentPayload === "undefined" || _input.currentPayload === "" || _input.currentPayload === null) {
1104
+ // 14/08/2021 Added || input.currentPayload === null
1105
+ node.writeQueueAdd({
1106
+ grpaddr: _dest,
1107
+ payload: _input.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized,
1108
+ dpt: _input.dpt,
1109
+ outputtype: "response",
1110
+ nodecallerid: _input.id,
1111
+ });
1112
+ _input.setNodeStatus({
1113
+ fill: "blue",
1114
+ shape: "ring",
1115
+ text: "Read & Autorespond with default",
1116
+ payload: _input.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized,
1117
+ GA: _input.topic,
1118
+ dpt: msg.knx.dpt,
1119
+ devicename: "",
1120
+ });
1121
+ } else {
1122
+ node.writeQueueAdd({
1123
+ grpaddr: _dest,
1124
+ payload: _input.currentPayload,
1125
+ dpt: _input.dpt,
1126
+ outputtype: "response",
1127
+ nodecallerid: _input.id,
1128
+ });
1129
+ _input.setNodeStatus({
1130
+ fill: "blue",
1131
+ shape: "ring",
1132
+ text: "Read & Autorespond with default",
1133
+ payload: _input.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized,
1134
+ GA: _input.topic,
1135
+ dpt: msg.knx.dpt,
1136
+ devicename: "",
1137
+ });
1138
+ }
1139
+ } else {
1140
+ _input.setNodeStatus({
1141
+ fill: "grey",
1142
+ shape: "dot",
1143
+ text: "Read",
1144
+ payload: msg.payload,
1145
+ GA: _input.topic,
1146
+ dpt: msg.knx.dpt,
1147
+ devicename: "",
1148
+ });
1149
+ }
1150
+ _input.handleSend(msg);
1151
+ }
1152
+ }
1153
+ });
1154
+ break;
1155
+
1156
+ default:
1157
+ return;
1158
+ }
1159
+ // console.timeEnd('handleBusEvents');
1160
+ }
1161
+ // END Handle BUS events---------------------------------------------------------------------------------------
1162
+
1163
+ // 02/01/2020 All sent messages are queued, to allow at least 50 milliseconds between each telegram sent to the bus
1164
+ node.writeQueueAdd = (_oKNXMessage) => {
1165
+ const _clonedMessage = RED.util.cloneMessage(_oKNXMessage);
1166
+ // if (node.linkStatus !== "connected") {
1167
+ // try {
1168
+ // if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("knxUltimate-config: writeQueueAdd Discarded " + JSON.stringify(_clonedMessage));
1169
+ // } catch (error) {
1170
+
1171
+ // }
1172
+ // return;
1173
+ // }
1174
+ // _clonedMessage is { grpaddr, payload,dpt,outputtype (write or response),nodecallerid (id of the node sending adding the telegram to the queue)}
1175
+ node.telegramsQueue.unshift(_clonedMessage); // Add _clonedMessage as first in the queue pile
1176
+ };
1177
+
1178
+ function handleTelegramQueue() {
1179
+ if (node.knxConnection !== null) {
1180
+ if (node.lockHandleTelegramQueue === true) return; // Exits if the funtion is busy
1181
+ node.lockHandleTelegramQueue = true; // Lock the function. It cannot be called again until finished.
1182
+
1183
+ // 16/08/2021 If not connected, exit
1184
+ if (node.linkStatus !== "connected" || node.telegramsQueue.length === 0) {
1185
+ node.lockHandleTelegramQueue = false; // Unlock the function
1186
+ return;
1187
+ }
1188
+
1189
+ // 26/12/2021 If the KNXEngine is busy waiting for telegram's ACK, exit
1190
+ if (!node.knxConnection.clearToSend) {
1191
+ node.lockHandleTelegramQueue = false; // Unlock the function
1192
+ if (node.telegramsQueue.length > 0) {
1193
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn(
1194
+ "knxUltimate-config: handleTelegramQueue: the KNXEngine is busy or is waiting for a telegram ACK with seqNumner " +
1195
+ node.knxConnection.getSeqNumber() +
1196
+ ". Delay handling queue. YOUR COMPUTER COULD BE TOO SLOW OR BUSY TO KEEP UP WITH THE STRICT TIMING, REQUIRED FOR KNX TO FUNCTION PROPERLY.",
1197
+ );
1198
+ }
1199
+ return;
1200
+ }
1201
+
1202
+ // Retrieving oKNXMessage { grpaddr, payload,dpt,outputtype (write or response),nodecallerid (node caller)}. 06/03/2020 "Read" request does have the lower priority in the queue, so firstly, i search for "read" telegrams and i move it on the top of the queue pile.
1203
+ let aTelegramsFiltered = [];
1204
+ aTelegramsFiltered = node.telegramsQueue.filter((a) => a.outputtype !== "read");
1205
+
1206
+ if (aTelegramsFiltered.length == 0) {
1207
+ // There are no write nor response telegrams, handle the remaining "read", if any
1208
+ if (node.delaybetweentelegramsREADCount >= node.delaybetweentelegramsfurtherdelayREAD) {
1209
+ // 18/05/2020 delay multiplicator only for "read" telegrams.
1210
+ node.delaybetweentelegramsREADCount = 0;
1211
+ aTelegramsFiltered = node.telegramsQueue;
1212
+ } else {
1213
+ node.delaybetweentelegramsREADCount += 1;
1214
+ }
1215
+ }
1216
+ if (aTelegramsFiltered.length == 0) {
1217
+ node.lockHandleTelegramQueue = false; // Unlock the function
1218
+ return;
1219
+ }
1220
+
1221
+ const oKNXMessage = aTelegramsFiltered[aTelegramsFiltered.length - 1]; // Get the last message in the queue
1222
+
1223
+ // 19/01/2023 FORMATTING THE OUTPUT PAYLOAD (ROUND, ETC) BASED ON THE NODE CONFIG
1224
+ //* ********************************************************
1225
+ oKNXMessage.payload = payloadRounder.Manipulate(RED.nodes.getNode(oKNXMessage.nodecallerid), oKNXMessage.payload);
1226
+ //* ********************************************************
1227
+
1228
+ if (oKNXMessage.outputtype === "response") {
1229
+ try {
1230
+ node.knxConnection.respond(oKNXMessage.grpaddr, oKNXMessage.payload, oKNXMessage.dpt);
1231
+ } catch (error) {
1232
+ try {
1233
+ const oNode = RED.nodes.getNode(oKNXMessage.nodecallerid); // 05/04/2022 Get the real node
1234
+ oNode.setNodeStatus({
1235
+ fill: "red",
1236
+ shape: "dot",
1237
+ text: "Send response " + error,
1238
+ payload: oKNXMessage.payload,
1239
+ GA: oKNXMessage.grpaddr,
1240
+ dpt: oKNXMessage.dpt,
1241
+ devicename: "",
1242
+ });
1243
+ } catch (error) { }
1244
+ }
1245
+ } else if (oKNXMessage.outputtype === "read") {
1246
+ try {
1247
+ node.knxConnection.read(oKNXMessage.grpaddr);
1248
+ } catch (error) { }
1249
+ } else if (oKNXMessage.outputtype === "update") {
1250
+ // 05/01/2021 Update don't send anything to the bus, but instead updates the values of all nodes belonging to the group address passed
1251
+ // oKNXMessage = {
1252
+ // grpaddr: '5/0/1',
1253
+ // payload: true,
1254
+ // dpt: '1.001',
1255
+ // outputtype: 'update',
1256
+ // nodecallerid: 'd104af91.31da18'
1257
+ // }
1258
+ try {
1259
+ node.nodeClients.forEach((_input) => {
1260
+
1261
+ // 16/08/2021 If not connected, exit
1262
+ if (node.linkStatus !== "connected") {
1263
+ node.lockHandleTelegramQueue = false; // Unlock the function
1264
+ return;
1265
+ }
1266
+
1267
+ // 19/03/2020 in the middle of coronavirus. Whole italy is red zone, closed down. Scene Controller implementation
1268
+ if (_input.hasOwnProperty("isSceneController")) {
1269
+ } else if (_input.hasOwnProperty("isLogger")) {
1270
+ // 26/03/2020 Coronavirus is slightly decreasing the affected numer of people. Logger Node
1271
+ } else if (_input.listenallga === true) {
1272
+ } else if (_input.topic == oKNXMessage.grpaddr) {
1273
+ if (_input.hasOwnProperty("isWatchDog")) {
1274
+ // 04/02/2020 Watchdog implementation
1275
+ // Is a watchdog node
1276
+ } else {
1277
+ const msg = {
1278
+ topic: _input.outputtopic,
1279
+ payload: oKNXMessage.payload,
1280
+ devicename: _input.name ? _input.name : "",
1281
+ event: "Update_NoWrite",
1282
+ eventdesc: "The value has been updated from another node and hasn't been received from KNX BUS",
1283
+ };
1284
+ // Check RBE INPUT from KNX Bus, to avoid send the payload to the flow, if it's equal to the current payload
1285
+ if (!checkRBEInputFromKNXBusAllowSend(_input, msg.payload)) {
1286
+ _input.setNodeStatus({
1287
+ fill: "grey",
1288
+ shape: "ring",
1289
+ text: "rbe block (" + msg.payload + ") from KNX",
1290
+ payload: "",
1291
+ GA: "",
1292
+ dpt: "",
1293
+ devicename: "",
1294
+ });
1295
+ node.lockHandleTelegramQueue = false; // Unlock the function
1296
+ return;
1297
+ }
1298
+ msg.previouspayload = typeof _input.currentPayload !== "undefined" ? _input.currentPayload : ""; // 24/01/2020 Added previous payload
1299
+ _input.currentPayload = msg.payload; // Set the current value for the RBE input
1300
+ _input.setNodeStatus({
1301
+ fill: "green",
1302
+ shape: "dot",
1303
+ text: "",
1304
+ payload: msg.payload,
1305
+ GA: _input.topic,
1306
+ dpt: _input.dpt,
1307
+ devicename: "",
1308
+ });
1309
+ _input.handleSend(msg);
1310
+ }
1311
+ }
1312
+ });
1313
+ } catch (error) { }
1314
+ } else {
1315
+ // Write
1316
+ try {
1317
+ node.knxConnection.write(oKNXMessage.grpaddr, oKNXMessage.payload, oKNXMessage.dpt);
1318
+ } catch (error) {
1319
+ try {
1320
+ const oNode = RED.nodes.getNode(oKNXMessage.nodecallerid); // 05/04/2022 Get the real node
1321
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(
1322
+ "knxUltimate-config: node.knxConnection.write: Payload: " + oKNXMessage.payload + " GA:" + oKNXMessage.grpaddr + " DPT:" + oKNXMessage.dpt + " " + error.stack
1323
+ );
1324
+ oNode.setNodeStatus({
1325
+ fill: "red",
1326
+ shape: "dot",
1327
+ text: "Send write " + error,
1328
+ payload: oKNXMessage.payload,
1329
+ GA: oKNXMessage.grpaddr,
1330
+ dpt: oKNXMessage.dpt,
1331
+ devicename: "",
1332
+ });
1333
+ } catch (error) { }
1334
+ }
1335
+ }
1336
+ // Remove current item in the main node.telegramsQueue array
1337
+ try {
1338
+ node.telegramsQueue = node.telegramsQueue.filter((item) => {
1339
+ if (item !== oKNXMessage) {
1340
+ return item;
1341
+ }
1342
+ });
1343
+ } catch (error) { }
1344
+ node.lockHandleTelegramQueue = false; // Unlock the function
1345
+ }
1346
+ }
1347
+
1348
+ // 14/08/2019 If the node has payload same as the received telegram, return false
1349
+ function checkRBEInputFromKNXBusAllowSend(_node, _KNXTelegramPayload) {
1350
+ if (_node.inputRBE !== "true") return true;
1351
+
1352
+ return !_.isEqual(_node.currentPayload, _KNXTelegramPayload);
1353
+ }
1354
+
1355
+ // 26/10/2019 Try to figure out the datapoint type from raw value
1356
+ function tryToFigureOutDataPointFromRawValue(_rawValue) {
1357
+ // 25/10/2019 Try some Datapoints
1358
+ if (_rawValue === null) return "1.001";
1359
+ if (_rawValue.length === 1) {
1360
+ if (_rawValue[0].toString() == "0" || _rawValue[0].toString() == "1") {
1361
+ return "1.001"; // True/False?
1362
+ } else {
1363
+ return "5.001"; // Absolute Brightness ?
1364
+ }
1365
+ } else if (_rawValue.length == 4) {
1366
+ return "14.056"; // Watt ?
1367
+ } else if (_rawValue.length == 2) {
1368
+ return "9.001";
1369
+ } else if (_rawValue.length == 3) {
1370
+ return "11.001";
1371
+ } else if (_rawValue.length == 14) {
1372
+ return "16.001"; // Text ?
1373
+ } else {
1374
+ // Dont' know, try until no errors
1375
+ const dpts = Object.entries(dptlib).filter(onlyDptKeys).map(extractBaseNo).sort(sortBy("base")).reduce(toConcattedSubtypes, []);
1376
+ for (let index = 0; index < dpts.length; index++) {
1377
+ const element = dpts[index];
1378
+ try {
1379
+ // dpt.value)
1380
+ // dpt.text))
1381
+ const dpt = dptlib.resolve(element.value);
1382
+ if (typeof dpt !== "undefined") {
1383
+ const jsValue = dptlib.fromBuffer(_rawValue, dpt);
1384
+ if (typeof jsValue !== "undefined") {
1385
+ // if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("Trying for " + dest + ". FOUND " + element.value);
1386
+ return element.value;
1387
+ }
1388
+ }
1389
+ } catch (error) { }
1390
+ }
1391
+ throw new Error("tryToFigureOutDataPointFromRawValue: no suitable datapoint found"); // 24/08/2021 Return error if no DPT
1392
+ }
1393
+ }
1394
+
1395
+
1396
+ function buildInputMessage({ _srcGA, _destGA, _event, _Rawvalue, _inputDpt, _devicename, _outputtopic, _oNode }) {
1397
+ let sPayloadmeasureunit = "unknown";
1398
+ let sDptdesc = "unknown";
1399
+ let sPayloadsubtypevalue = "unknown";
1400
+ let jsValue = null;
1401
+ let sInputDpt = "unknown";
1402
+ let gainfo = "unknown";
1403
+ let oGA;
1404
+
1405
+ // Construct the gainfo from _devicename ("(Ingressi logici->Sensori) Camera armadi lux")
1406
+ if (node.csv !== undefined) {
1407
+ try {
1408
+ oGA = node.csv.filter((sga) => sga.ga == _destGA)[0];
1409
+ const regexGA = /^(.*)\/(.*)\/(.*)$/;
1410
+ const regexName = /^\((.*)->(.*)\) (.*)$/;
1411
+ const matchGA = oGA.ga.match(regexGA);
1412
+ const matchName = oGA.devicename.match(regexName);
1413
+ gainfo = {
1414
+ maingroupname: matchName[1],
1415
+ middlegroupname: matchName[2],
1416
+ ganame: matchName[3],
1417
+ maingroupnumber: matchGA[1],
1418
+ middlegroupnumber: matchGA[2],
1419
+ ganumber: matchGA[3]
1420
+ }
1421
+ } catch (error) {
1422
+ // Dont' care
1423
+ }
1424
+ }
1425
+
1426
+ // 20/06/2024 set both if undefined
1427
+ if (_inputDpt === undefined) {
1428
+ try {
1429
+ _inputDpt = oGA === undefined ? null : oGA.dpt;
1430
+ } catch (error) {
1431
+ _inputDpt = null;
1432
+ }
1433
+
1434
+ }
1435
+ if (_devicename === undefined) {
1436
+ try {
1437
+ _devicename = oGA === undefined ? _oNode.name || "" : oGA.devicename;
1438
+ } catch (error) {
1439
+ _devicename = undefined;
1440
+ }
1441
+ }
1442
+
1443
+ const errorMessage = {
1444
+ topic: _outputtopic,
1445
+ payload: "UNKNOWN, PLEASE IMPORT THE ETS FILE!",
1446
+ devicename: typeof _devicename !== "undefined" ? _devicename : "",
1447
+ payloadmeasureunit: "",
1448
+ payloadsubtypevalue: "",
1449
+ knx: {
1450
+ event: _event,
1451
+ dpt: "unknown",
1452
+ dptdesc: "",
1453
+ source: _srcGA,
1454
+ destination: _destGA,
1455
+ rawValue: _Rawvalue,
1456
+ },
1457
+ };
1458
+
1459
+ // Resolve DPT and convert value if available
1460
+ if (_Rawvalue !== null) {
1461
+ try {
1462
+ sInputDpt = _inputDpt === null ? tryToFigureOutDataPointFromRawValue(_Rawvalue) : _inputDpt;
1463
+ } catch (error) {
1464
+ // Here comes if no datapoint has beeen found
1465
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(
1466
+ "knxUltimate-config: buildInputMessage: Error returning from tryToFigureOutDataPointFromRawValue. Device " +
1467
+ _srcGA +
1468
+ " Destination " +
1469
+ _destGA +
1470
+ " Event " +
1471
+ _event +
1472
+ " GA's Datapoint " +
1473
+ (_inputDpt === null
1474
+ ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
1475
+ : _inputDpt) +
1476
+ " Devicename " +
1477
+ _devicename +
1478
+ " Topic " +
1479
+ _outputtopic +
1480
+ " " +
1481
+ error.message,
1482
+ );
1483
+ errorMessage.payload = "UNKNOWN: ERROR tryToFigureOutDataPointFromRawValue:" + error.message;
1484
+ return errorMessage;
1485
+ }
1486
+
1487
+ try {
1488
+ var dpt = dptlib.resolve(sInputDpt);
1489
+ } catch (error) {
1490
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(
1491
+ "knxUltimate-config: buildInputMessage: Error returning from dptlib.resolve(sInputDpt). Device " +
1492
+ _srcGA +
1493
+ " Destination " +
1494
+ _destGA +
1495
+ " Event " +
1496
+ _event +
1497
+ " GA's Datapoint " +
1498
+ (_inputDpt === null
1499
+ ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
1500
+ : _inputDpt) +
1501
+ " Devicename " +
1502
+ _devicename +
1503
+ " Topic " +
1504
+ _outputtopic +
1505
+ " " +
1506
+ error.message,
1507
+ );
1508
+ errorMessage.payload = "UNKNOWN: ERROR dptlib.resolve:" + error.messages;
1509
+ return errorMessage;
1510
+ }
1511
+
1512
+ if (dpt !== null && _Rawvalue !== null) {
1513
+ try {
1514
+ jsValue = dptlib.fromBuffer(_Rawvalue, dpt);
1515
+ if (jsValue === null) {
1516
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(
1517
+ "knxUltimate-config: buildInputMessage: received a wrong datagram form KNX BUS, from device " +
1518
+ _srcGA +
1519
+ " Destination " +
1520
+ _destGA +
1521
+ " Event " +
1522
+ _event +
1523
+ " GA's Datapoint " +
1524
+ (_inputDpt === null
1525
+ ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
1526
+ : _inputDpt) +
1527
+ " Devicename " +
1528
+ _devicename +
1529
+ " Topic " +
1530
+ _outputtopic +
1531
+ " NodeID " +
1532
+ _oNode.id || "",
1533
+ );
1534
+ }
1535
+ } catch (error) {
1536
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(
1537
+ "knxUltimate-config: buildInputMessage: Error returning from DPT decoding. Device " +
1538
+ _srcGA +
1539
+ " Destination " +
1540
+ _destGA +
1541
+ " Event " +
1542
+ _event +
1543
+ " GA's Datapoint " +
1544
+ (_inputDpt === null
1545
+ ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
1546
+ : _inputDpt) +
1547
+ " Devicename " +
1548
+ _devicename +
1549
+ " Topic " +
1550
+ _outputtopic +
1551
+ " " +
1552
+ error.message +
1553
+ " NodeID " +
1554
+ _oNode.id || "",
1555
+ );
1556
+ errorMessage.payload = "UNKNOWN: ERROR dptlib.fromBuffer:" + error.stack;
1557
+ return errorMessage;
1558
+ }
1559
+ }
1560
+
1561
+ // 19/01/2023 FORMATTING THE OUTPUT PAYLOAD (ROUND, ETC) BASED ON THE NODE CONFIG
1562
+ //* ********************************************************
1563
+ jsValue = payloadRounder.Manipulate(_oNode, jsValue);
1564
+ //* ********************************************************
1565
+
1566
+ if (dpt.subtype !== undefined) {
1567
+ sPayloadmeasureunit = dpt.subtype.unit !== undefined ? dpt.subtype.unit : "unknown";
1568
+ sDptdesc = dpt.subtype.desc !== undefined ? dpt.subtype.desc.charAt(0).toUpperCase() + dpt.subtype.desc.slice(1) : "unknown";
1569
+ if (dpt.subtype.enc !== undefined) {
1570
+ try {
1571
+ if (!jsValue) sPayloadsubtypevalue = dpt.subtype.enc[0];
1572
+ if (jsValue) sPayloadsubtypevalue = dpt.subtype.enc[1];
1573
+ } catch (error) {
1574
+ // Don't care
1575
+ }
1576
+ }
1577
+ }
1578
+
1579
+ } else {
1580
+ // Don't care, it's a READ REQUEST
1581
+ }
1582
+
1583
+ try {
1584
+ // Build final input message object
1585
+ const finalMessage = {
1586
+ topic: _outputtopic,
1587
+ devicename: typeof _devicename !== "undefined" ? _devicename : "",
1588
+ payload: jsValue,
1589
+ payloadmeasureunit: sPayloadmeasureunit,
1590
+ payloadsubtypevalue: sPayloadsubtypevalue,
1591
+ gainfo: gainfo,
1592
+ knx: {
1593
+ event: _event,
1594
+ dpt: sInputDpt,
1595
+ dptdesc: sDptdesc,
1596
+ source: _srcGA,
1597
+ destination: _destGA,
1598
+ rawValue: _Rawvalue
1599
+ }
1600
+ };
1601
+ // 11/11/2021 jsValue is null, as well as _Rawvalue, in case of READ REQUEST message.
1602
+ // if (jsValue !== null) finalMessage.payload = jsValue;
1603
+
1604
+ return finalMessage;
1605
+ } catch (error) {
1606
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("knxUltimate-config: buildInputMessage error: " + error.message);
1607
+ return errorMessage;
1608
+ }
1609
+ }
1610
+
1611
+ function readCSV(_csvText) {
1612
+ // 26/05/2023 check if the text is a file path
1613
+ if (_csvText.toUpperCase().includes(".CSV") || _csvText.toUpperCase().includes(".ESF")) {
1614
+ // I'ts a file. Read it now and pass to the _csvText
1615
+ const sFileName = _csvText;
1616
+ try {
1617
+ _csvText = fs.readFileSync(sFileName, { encoding: "utf8" });
1618
+ } catch (error) {
1619
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("KNXUltimate-config: ERROR: reading ETS file " + error.message);
1620
+ node.error("KNXUltimate-config: ERROR: reading ETS file " + error.message);
1621
+ return;
1622
+ }
1623
+ }
1624
+
1625
+ // 24/02/2020, in the middle of Coronavirus emergency in Italy. Check if it a CSV ETS Export of group addresses, or if it's an EFS
1626
+ if (_csvText.split("\n")[0].toUpperCase().indexOf('"') == -1) return readESF(_csvText);
1627
+
1628
+ const ajsonOutput = new Array(); // Array: qui va l'output totale con i nodi per node-red
1629
+
1630
+ if (_csvText == "") {
1631
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: no csv ETS found");
1632
+ } else {
1633
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: csv ETS found !");
1634
+ // 23/08/2019 Delete inwanted CRLF in the GA description
1635
+ const sTemp = correctCRLFInCSV(_csvText);
1636
+
1637
+ // Read and decode the CSV in an Array containing: "group address", "DPT", "Device Name"
1638
+ const fileGA = sTemp.split("\n");
1639
+ // Controllo se le righe dei gruppi contengono il separatore di tabulazione
1640
+ if (fileGA[0].search("\t") == -1) {
1641
+ node.error("KNXUltimate-config: ERROR: the csv ETS file must have the tabulation as separator");
1642
+ return;
1643
+ }
1644
+
1645
+ let sFirstGroupName = "";
1646
+ let sSecondGroupName = "";
1647
+ let sFather = "";
1648
+ for (let index = 0; index < fileGA.length; index++) {
1649
+ let element = fileGA[index];
1650
+ element = element.replace(/\"/g, ""); // Rimuovo le virgolette
1651
+ element = element.replace(/\#/g, ""); // Rimuovo evetuali #
1652
+
1653
+ if (element !== "") {
1654
+ // Main and secondary group names
1655
+ if ((element.split("\t")[1].match(/-/g) || []).length == 2) {
1656
+ // Found main group family name (Example Light Actuators)
1657
+ sFirstGroupName = element.split("\t")[0] || "";
1658
+ sSecondGroupName = "";
1659
+ }
1660
+ if ((element.split("\t")[1].match(/-/g) || []).length == 1) {
1661
+ // Found second group family name (Example First Floor light)
1662
+ sSecondGroupName = element.split("\t")[0] || "";
1663
+ }
1664
+ if (sFirstGroupName !== "" && sSecondGroupName !== "") {
1665
+ sFather = "(" + sFirstGroupName + "->" + sSecondGroupName + ") ";
1666
+ }
1667
+
1668
+ if (element.split("\t")[1].search("-") == -1 && element.split("\t")[1].search("/") !== -1) {
1669
+ // Ho trovato una riga contenente un GA valido, cioè con 2 "/"
1670
+ if (element.split("\t")[5] == "") {
1671
+ if (node.stopETSImportIfNoDatapoint === "stop") {
1672
+ node.error(
1673
+ "KNXUltimate-config: ABORT IMPORT OF ETS CSV FILE. To skip the invalid datapoint and continue import, change the related setting, located in the config node in the ETS import section.",
1674
+ );
1675
+ return;
1676
+ }
1677
+ if (node.stopETSImportIfNoDatapoint === "fake") {
1678
+ // 02/03/2020 Whould you like to continue without datapoint? Good. Here a totally fake datapoint
1679
+ node.warn(
1680
+ "KNXUltimate-config: WARNING IMPORT OF ETS CSV FILE. Datapoint not set. You choosed to continue import with a fake datapoint 1.001. -> " +
1681
+ element.split("\t")[0] +
1682
+ " " +
1683
+ element.split("\t")[1],
1684
+ );
1685
+ ajsonOutput.push({
1686
+ ga: element.split("\t")[1],
1687
+ dpt: "1.001",
1688
+ devicename: sFather + element.split("\t")[0] + " (DPT NOT SET IN ETS - FAKE DPT USED)",
1689
+ });
1690
+ } else {
1691
+ // 31/03/2020 Skip import
1692
+ node.warn(
1693
+ "KNXUltimate-config: WARNING IMPORT OF ETS CSV FILE. Datapoint not set. You choosed to skip -> " +
1694
+ element.split("\t")[0] +
1695
+ " " +
1696
+ element.split("\t")[1],
1697
+ );
1698
+ }
1699
+ } else {
1700
+ const DPTa = element.split("\t")[5].split("-")[1];
1701
+ let DPTb = element.split("\t")[5].split("-")[2];
1702
+ if (typeof DPTb === "undefined") {
1703
+ node.warn(
1704
+ "KNXUltimate-config: WARNING: Datapoint not fully set (there is only the main type). I applied a default .001, but please check if i'ts ok ->" +
1705
+ element.split("\t")[0] +
1706
+ " " +
1707
+ element.split("\t")[1] +
1708
+ " Datapoint: " +
1709
+ element.split("\t")[5],
1710
+ );
1711
+ DPTb = "001"; // default
1712
+ }
1713
+ // Trailing zeroes
1714
+ if (DPTb.length == 1) {
1715
+ DPTb = "00" + DPTb;
1716
+ } else if (DPTb.length == 2) {
1717
+ DPTb = "0" + DPTb;
1718
+ }
1719
+ if (DPTb.length == 3) {
1720
+ DPTb = "" + DPTb; // stupid, but for readability
1721
+ }
1722
+ ajsonOutput.push({
1723
+ ga: element.split("\t")[1],
1724
+ dpt: DPTa + "." + DPTb,
1725
+ devicename: sFather + element.split("\t")[0],
1726
+ });
1727
+ }
1728
+ }
1729
+ }
1730
+ }
1731
+
1732
+ return ajsonOutput;
1733
+ }
1734
+ }
1735
+
1736
+ function readESF(_esfText) {
1737
+ // 24/02/2020 must do an EIS to DPT conversion.
1738
+ // https://www.loxone.com/dede/kb/eibknx-datentypen/
1739
+ // Format: Attuatori luci.Luci primo piano.0/0/1 Luce camera da letto EIS 1 'Switching' (1 Bit) Low
1740
+ const ajsonOutput = new Array(); // Array: qui va l'output totale con i nodi per node-red
1741
+
1742
+ if (_esfText === "") {
1743
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: no ESF found");
1744
+ return;
1745
+ } else {
1746
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: esf ETS found !");
1747
+ // Read and decode the CSV in an Array containing: "group address", "DPT", "Device Name"
1748
+ const fileGA = _esfText.split("\n");
1749
+ let sGA = "";
1750
+ let sFirstGroupName = "";
1751
+ let sSecondGroupName = ""; // Fake, because EIS datapoints are unprecise.
1752
+ let sDeviceName = "";
1753
+ let sEIS = "";
1754
+ let sDPT = "";
1755
+
1756
+ for (let index = 1; index < fileGA.length; index++) {
1757
+ let element = fileGA[index];
1758
+ element = element.replace(/\"/g, ""); // Rimuovo evetuali virgolette
1759
+ element = element.replace(/\#/g, ""); // Rimuovo evetuali #
1760
+ // element = element.replace(/[^\x00-\x7F]/g, '') // Remove non ascii chars
1761
+
1762
+ if (element !== "") {
1763
+ sFirstGroupName = element.split("\t")[0].split(".")[0] || "";
1764
+ sSecondGroupName = element.split("\t")[0].split(".")[1] || "";
1765
+ sGA = element.split("\t")[0].split(".")[2] || "";
1766
+ sDeviceName = element.split("\t")[1] || "";
1767
+ sEIS = element.split("\t")[2] || "";
1768
+ sDPT = "";
1769
+ // Transform EIS to DPT
1770
+ if (sEIS.toUpperCase().includes("EIS 1")) sDPT = "1.001";
1771
+ if (sEIS.toUpperCase().includes("EIS 2")) sDPT = "3.007";
1772
+ if (sEIS.toUpperCase().includes("EIS 3")) sDPT = "10.001";
1773
+ if (sEIS.toUpperCase().includes("EIS 4")) sDPT = "11.001";
1774
+ if (sEIS.toUpperCase().includes("EIS 5")) sDPT = "9.001";
1775
+ if (sEIS.toUpperCase().includes("EIS 6")) sDPT = "5.001";
1776
+ if (sEIS.toUpperCase().includes("EIS 7")) sDPT = "1.001";
1777
+ if (sEIS.toUpperCase().includes("EIS 8")) sDPT = "2.001";
1778
+ if (sEIS.toUpperCase().includes("EIS 9")) sDPT = "14.007";
1779
+ if (sEIS.toUpperCase().includes("EIS 10")) sDPT = "7.001";
1780
+ if (sEIS.toUpperCase().includes("EIS 11")) sDPT = "12.001";
1781
+ if (sEIS.toUpperCase().includes("EIS 12")) sDPT = "15.000";
1782
+ if (sEIS.toUpperCase().includes("EIS 13")) sDPT = "4.001";
1783
+ if (sEIS.toUpperCase().includes("EIS 14")) sDPT = "5.001";
1784
+ if (sEIS.toUpperCase().includes("EIS 15")) sDPT = "16.001";
1785
+
1786
+ if (sEIS.toUpperCase().includes("UNCERTAIN")) {
1787
+ if (sEIS.toUpperCase().includes("4 BYTE")) {
1788
+ sDPT = "14.056";
1789
+ } else if (sEIS.toUpperCase().includes("2 BYTE")) {
1790
+ sDPT = "9.001";
1791
+ } else if (sEIS.toUpperCase().includes("3 BYTE")) {
1792
+ sDPT = "10.001"; // Date
1793
+ } else if (sEIS.toUpperCase().includes("1 BYTE")) {
1794
+ sDPT = "20.102"; // RTC
1795
+ } else {
1796
+ sDPT = "5.004"; // Maybe.
1797
+ }
1798
+ }
1799
+ if (sDPT === "") {
1800
+ if (node.stopETSImportIfNoDatapoint === "stop") {
1801
+ node.error(
1802
+ "KNXUltimate-config: ABORT IMPORT OF ETS ESF FILE. To continue import, change the related setting, located in the config node in the ETS import section.",
1803
+ );
1804
+ return;
1805
+ } if (node.stopETSImportIfNoDatapoint === "fake") {
1806
+ sDPT = "5.004"; // Maybe.
1807
+ node.error(
1808
+ "KNXUltimate-config: ERROR: Found an UNCERTAIN datapoint in ESF ETS. You choosed to fake the datapoint -> " +
1809
+ sGA +
1810
+ ". An fake datapoint has been set: " +
1811
+ sDPT,
1812
+ );
1813
+ } else {
1814
+ sDPT = "SKIP";
1815
+ node.error("KNXUltimate-config: ERROR: Found an UNCERTAIN datapoint in ESF ETS. You choosed to skip -> " + sGA);
1816
+ }
1817
+ }
1818
+ if (sDPT !== "SKIP") ajsonOutput.push({
1819
+ ga: sGA,
1820
+ dpt: sDPT,
1821
+ devicename: "(" + sFirstGroupName + "->" + sSecondGroupName + ") " + sDeviceName,
1822
+ });
1823
+ }
1824
+ }
1825
+ }
1826
+
1827
+ return ajsonOutput;
1828
+ }
1829
+
1830
+ // 23/08/2019 Delete unwanted CRLF in the GA description
1831
+ function correctCRLFInCSV(_csv) {
1832
+ let sOut = ""; // fixed output text to return
1833
+ let sChar = "";
1834
+ let bStart = false;
1835
+ for (let index = 0; index < _csv.length; index++) {
1836
+ sChar = _csv.substr(index, 1);
1837
+ if (sChar == '"') {
1838
+ if (!bStart) {
1839
+ bStart = true;
1840
+ } else {
1841
+ bStart = false;
1842
+ }
1843
+ sOut += sChar;
1844
+ } else {
1845
+ if (bStart) {
1846
+ // i'm in the phrase, delimited by "". No CRLF should be there
1847
+ if (sChar !== "\n" && sChar !== "\r") {
1848
+ sOut += sChar;
1849
+ } else {
1850
+ sOut += " "; // Where it was a CRLF, i put a space
1851
+ }
1852
+ } else {
1853
+ sOut += sChar;
1854
+ }
1855
+ }
1856
+ }
1857
+
1858
+ // Replace all parenthesis with []
1859
+ sOut = sOut.replace(/\(/g, "[").replace(/\)/g, "]");
1860
+ return sOut;
1861
+ }
1862
+
1863
+ // 08/10/2021 Every xx seconds, i check if the connection is up and running
1864
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("KNXUltimate-config: Autoconnection: " + (node.autoReconnect === false ? "no." : "yes") + " Node " + node.name);
1865
+ if (node.timerKNXUltimateCheckState !== null) clearInterval(node.timerKNXUltimateCheckState);
1866
+ node.timerKNXUltimateCheckState = setInterval(() => {
1867
+ // If the node is disconnected, wait another cycle, then reconnects
1868
+ if (node.allowLauch_initKNXConnection && node.autoReconnect) {
1869
+ node.allowLauch_initKNXConnection = false;
1870
+ const t = setTimeout(() => {
1871
+ // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
1872
+ node.setAllClientsStatus("Auto reconnect in progress...", "grey", "");
1873
+ }, 100);
1874
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug(
1875
+ "knxUltimate-config: Auto Reconect by timerKNXUltimateCheckState in progress. node.LinkStatus: " +
1876
+ node.linkStatus +
1877
+ ", node.autoReconnect:" +
1878
+ node.autoReconnect,
1879
+ );
1880
+ node.initKNXConnection();
1881
+ return;
1882
+ }
1883
+ if (node.linkStatus === "disconnected" && node.autoReconnect) {
1884
+ node.allowLauch_initKNXConnection = true; // Next cycle, launch initKNXConnection, so it pauses more and leave more time
1885
+ const t = setTimeout(() => {
1886
+ // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
1887
+ node.setAllClientsStatus("Retry connection", "grey", "");
1888
+ }, 1000);
1889
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug(
1890
+ "knxUltimate-config: Waiting next cycle to reconect. node.LinkStatus: " + node.linkStatus + ", node.autoReconnect:" + node.autoReconnect,
1891
+ );
1892
+ // node.initKNXConnection();
1893
+ }
1894
+ }, 4000);
1895
+
1896
+ node.Disconnect = async (_sNodeStatus = "", _sColor = "grey") => {
1897
+ if (node.linkStatus === "disconnected") {
1898
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug("knxUltimate-config: Disconnect: already not connected:" + node.linkStatus + ", node.autoReconnect:" + node.autoReconnect);
1899
+ return;
1900
+ }
1901
+ if (node.timerSendTelegramFromQueue !== null) clearInterval(node.timerSendTelegramFromQueue); // 02/01/2020 Stop queue timer
1902
+ node.linkStatus = "disconnected"; // 29/08/2019 signal disconnection
1903
+ node.lockHandleTelegramQueue = false; // Unlock the telegram handling function
1904
+ if (node.timerDoInitialRead !== null) clearTimeout(node.timerDoInitialRead); // 17/02/2020 Stop the initial read timer
1905
+ try {
1906
+ if (node.knxConnection !== null) await node.knxConnection.Disconnect();
1907
+ } catch (error) {
1908
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug(
1909
+ "knxUltimate-config: Disconnected: node.knxConnection.Disconnect() " + (error.message || "") + " , node.autoReconnect:" + node.autoReconnect,
1910
+ );
1911
+ }
1912
+ node.startTimerClearTelegramQueue(); // 21/01/2022 Clear the telegram queue after a while
1913
+ node.setAllClientsStatus("Disconnected", _sColor, _sNodeStatus);
1914
+ saveExposedGAs(); // 04/04/2021 save the current values of GA payload
1915
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.debug("knxUltimate-config: Disconnected, node.autoReconnect:" + node.autoReconnect);
1916
+ };
1917
+
1918
+ node.on("close", async function (done) {
1919
+ try {
1920
+ await node.Disconnect();
1921
+ } catch (error) { /* empty */ }
1922
+ if (node.timerClearTelegramQueue !== null) clearTimeout(node.timerClearTelegramQueue);
1923
+ node.telegramsQueue = [];
1924
+ node.nodeClients = []; // 05/04/2022 Nullify
1925
+ try {
1926
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger = null;
1927
+ loggerEngine.destroy();
1928
+ } catch (error) { /* empty */ }
1929
+ done();
1930
+ });
1931
+ }
1932
+
1933
+ // RED.nodes.registerType("knxUltimate-config", knxUltimateConfigNode);
1934
+ RED.nodes.registerType("knxUltimate-config", knxUltimateConfigNode, {
1935
+ credentials: {
1936
+ keyringFilePassword: { type: "password" },
1937
+ },
1938
+ });
1939
+ };