node-red-contrib-knx-ultimate 1.3.37 → 1.3.38
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 +5 -0
- package/README.md +11 -7
- package/nodes/knxUltimate-config.js +76 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
<p>
|
|
10
|
+
<b>Version 1.3.38</b> - April 2022<br/>
|
|
11
|
+
- Memory footprint decreased.<br/>
|
|
12
|
+
- Better handling of KNX nodes objects array, for flows with more than 250 nodes.<br/>
|
|
13
|
+
</p>
|
|
9
14
|
<p>
|
|
10
15
|
<b>Version 1.3.37</b> - April 2022<br/>
|
|
11
16
|
- Changed: the KNX Gateway don't care anymore for ROUTING_LOST_MESSAGE and ROUTING_BUSY. Previously, it was disconnecting. Now it only advises in LOG.<br/>
|
package/README.md
CHANGED
|
@@ -46,17 +46,11 @@ msg.payload = {red:255, green:200, blue:30} // Put some colors in our life
|
|
|
46
46
|
## VOLUNTEER NEEDED FOR KNX SECURE
|
|
47
47
|
|
|
48
48
|
**************************************************
|
|
49
|
-
**************************************************
|
|
50
|
-
|
|
51
49
|
KNX-Secure is under development and **should** be ready by mid 2022.<br/>
|
|
52
|
-
Many users requested me to "extract" the baseline KNX API and make it accessible via npmjs. Here is it.<br/>
|
|
53
|
-
The API is named **KNXUltimate**. In the README page is well documented and there are also samples for unsecure and secure KNX connections.
|
|
54
|
-
* <a href="https://github.com/Supergiovane/KNXUltimate#readme">KNXUltimate API</a>
|
|
55
|
-
|
|
56
50
|
I need volunteer helping in development of KNX Secure.<br/>
|
|
57
51
|
High knowledge of cryptography and KNX is needed.
|
|
58
52
|
**************************************************
|
|
59
|
-
|
|
53
|
+
|
|
60
54
|
<br>
|
|
61
55
|
<br>
|
|
62
56
|
|
|
@@ -322,6 +316,16 @@ List of commercial companies, which have given us permission to be mentioned on
|
|
|
322
316
|
<br/>
|
|
323
317
|
<br/>
|
|
324
318
|
|
|
319
|
+
|
|
320
|
+
## ARE YOU A NODEJS DEV? DO YOU KNOW THERE IS AN API FOR NODEJS?
|
|
321
|
+
Many users requested me to "extract" the baseline KNX API and make it accessible via npmjs. Here is it.<br/>
|
|
322
|
+
The API is named **KNXUltimate**. In the README page is well documented and there are also samples for unsecure and secure KNX connections.
|
|
323
|
+
* <a href="https://github.com/Supergiovane/KNXUltimate#readme">KNXUltimate API</a>
|
|
324
|
+
|
|
325
|
+
<br/>
|
|
326
|
+
<br/>
|
|
327
|
+
|
|
328
|
+
|
|
325
329
|

|
|
326
330
|
|
|
327
331
|
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg
|
|
@@ -10,6 +10,7 @@ const net = require("net");
|
|
|
10
10
|
const _ = require("lodash");
|
|
11
11
|
const path = require("path");
|
|
12
12
|
var fs = require('fs');
|
|
13
|
+
const { Server } = require("http");
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
//Helpers
|
|
@@ -188,8 +189,10 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
188
189
|
|
|
189
190
|
|
|
190
191
|
node.setAllClientsStatus = (_status, _color, _text) => {
|
|
191
|
-
function nextStatus(
|
|
192
|
-
oClient
|
|
192
|
+
function nextStatus(_oClient) {
|
|
193
|
+
let oClient = RED.nodes.getNode(_oClient.id);
|
|
194
|
+
oClient.setNodeStatus({ fill: _color, shape: "dot", text: _status + " " + _text, payload: "", GA: oClient.topic, dpt: "", devicename: "" });
|
|
195
|
+
oClient = null;
|
|
193
196
|
}
|
|
194
197
|
node.nodeClients.map(nextStatus);
|
|
195
198
|
}
|
|
@@ -305,11 +308,11 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
305
308
|
Object.keys(oiFaces).forEach(ifname => {
|
|
306
309
|
// Interface with single IP
|
|
307
310
|
if (Object.keys(oiFaces[ifname]).length === 1) {
|
|
308
|
-
if (Object.keys(oiFaces[ifname])[0].internal
|
|
311
|
+
if (Object.keys(oiFaces[ifname])[0].internal === false) jListInterfaces.push({ name: ifname, address: Object.keys(oiFaces[ifname])[0].address });
|
|
309
312
|
} else {
|
|
310
313
|
var sAddresses = "";
|
|
311
314
|
oiFaces[ifname].forEach(function (iface) {
|
|
312
|
-
if (iface.internal
|
|
315
|
+
if (iface.internal === false) sAddresses += "+" + iface.address;
|
|
313
316
|
});
|
|
314
317
|
if (sAddresses !== "") jListInterfaces.push({ name: ifname, address: sAddresses });
|
|
315
318
|
}
|
|
@@ -346,11 +349,12 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
346
349
|
return date1.localeCompare(date2);
|
|
347
350
|
} else { return -1; }
|
|
348
351
|
})
|
|
349
|
-
.forEach(
|
|
352
|
+
.forEach(_input => {
|
|
353
|
+
let input = RED.nodes.getNode(_input.id);
|
|
350
354
|
sNodeID = "\"" + input.id + "\"";
|
|
351
355
|
sName = "\"" + (input.name !== undefined ? input.name : "") + "\"";
|
|
352
356
|
sOptions = "\"" + "\"";
|
|
353
|
-
if (input.listenallga
|
|
357
|
+
if (input.listenallga === true) {
|
|
354
358
|
if (input.hasOwnProperty("isSceneController")) {
|
|
355
359
|
// Is a Scene Controller
|
|
356
360
|
sGA = "\"Scene Controller\"";
|
|
@@ -430,8 +434,9 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
430
434
|
var readHistory = [];
|
|
431
435
|
let delay = 0;
|
|
432
436
|
node.nodeClients
|
|
433
|
-
.filter(
|
|
434
|
-
.forEach(
|
|
437
|
+
.filter(_oClient => (_oClient.isWatchDog !== undefined && _oClient.isWatchDog === true))
|
|
438
|
+
.forEach(_oClient => {
|
|
439
|
+
let oClient = RED.nodes.getNode(_oClient.id);
|
|
435
440
|
oClient.signalNodeErrorCalledByConfigNode(_oError);
|
|
436
441
|
})
|
|
437
442
|
}
|
|
@@ -449,6 +454,20 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
449
454
|
saveExposedGAs(); // 04/04/2021 save the current values of GA payload
|
|
450
455
|
}
|
|
451
456
|
|
|
457
|
+
// node.addClient = (_Node) => {
|
|
458
|
+
// // Check if node already exists
|
|
459
|
+
// if (node.nodeClients.filter(x => x.id === _Node.id).length === 0) {
|
|
460
|
+
// // Add _Node to the clients array
|
|
461
|
+
// if (node.autoReconnect) {
|
|
462
|
+
// _Node.setNodeStatus({ fill: "grey", shape: "ring", text: "Node initialized.", payload: "", GA: "", dpt: "", devicename: "" });
|
|
463
|
+
// } else {
|
|
464
|
+
// _Node.setNodeStatus({ fill: "red", shape: "ring", text: "Autoconnect disabled. Please manually connect.", payload: "", GA: "", dpt: "", devicename: "" });
|
|
465
|
+
// }
|
|
466
|
+
// node.nodeClients.push(_Node);
|
|
467
|
+
// }
|
|
468
|
+
|
|
469
|
+
// }
|
|
470
|
+
|
|
452
471
|
node.addClient = (_Node) => {
|
|
453
472
|
// Check if node already exists
|
|
454
473
|
if (node.nodeClients.filter(x => x.id === _Node.id).length === 0) {
|
|
@@ -458,18 +477,25 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
458
477
|
} else {
|
|
459
478
|
_Node.setNodeStatus({ fill: "red", shape: "ring", text: "Autoconnect disabled. Please manually connect.", payload: "", GA: "", dpt: "", devicename: "" });
|
|
460
479
|
}
|
|
461
|
-
|
|
480
|
+
// 05/04/2022 create the Json variable and add it to the list
|
|
481
|
+
let jNode = {};
|
|
482
|
+
jNode.id = _Node.id;
|
|
483
|
+
jNode.topic = _Node.topic;
|
|
484
|
+
if (_Node.hasOwnProperty("isWatchDog")) jNode.isWatchDog = _Node.isWatchDog;
|
|
485
|
+
jNode.initialread = _Node.initialread;
|
|
486
|
+
jNode.notifywrite = _Node.notifywrite;
|
|
487
|
+
jNode.notifyresponse = _Node.notifyresponse;
|
|
488
|
+
jNode.notifyreadrequest = _Node.notifyreadrequest;
|
|
489
|
+
node.nodeClients.push(jNode);
|
|
462
490
|
}
|
|
463
491
|
|
|
464
492
|
}
|
|
465
493
|
|
|
466
494
|
node.removeClient = (_Node) => {
|
|
467
495
|
// Remove the client node from the clients array
|
|
468
|
-
//if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info( "BEFORE Node " + _Node.id + " has been unsubscribed from receiving KNX messages. " + node.nodeClients.length);
|
|
469
496
|
try {
|
|
470
497
|
node.nodeClients = node.nodeClients.filter(x => x.id !== _Node.id)
|
|
471
498
|
} catch (error) { }
|
|
472
|
-
//if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("AFTER Node " + _Node.id + " has been unsubscribed from receiving KNX messages. " + node.nodeClients.length);
|
|
473
499
|
|
|
474
500
|
// If no clien nodes, disconnect from bus.
|
|
475
501
|
if (node.nodeClients.length === 0) {
|
|
@@ -496,14 +522,15 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
496
522
|
|
|
497
523
|
// First, read from file. This allow all virtual devices to get their values from file.
|
|
498
524
|
node.nodeClients
|
|
499
|
-
.filter(
|
|
500
|
-
.filter(
|
|
501
|
-
.forEach(
|
|
525
|
+
.filter(_oClient => _oClient.initialread === 2 || _oClient.initialread === 3)
|
|
526
|
+
.filter(_oClient => _oClient.hasOwnProperty("isWatchDog") === false)
|
|
527
|
+
.forEach(_oClient => {
|
|
528
|
+
let oClient = RED.nodes.getNode(_oClient.id); // 05/04/2022 Get the real node
|
|
502
529
|
|
|
503
530
|
if (node.linkStatus !== "connected") return; // 16/08/2021 If not connected, exit
|
|
504
531
|
|
|
505
532
|
// 04/04/2020 selected READ FROM FILE 2 or from file then from bus 3
|
|
506
|
-
if (oClient.listenallga
|
|
533
|
+
if (oClient.listenallga === true) {
|
|
507
534
|
// 13/12/2021 DA FARE
|
|
508
535
|
} else {
|
|
509
536
|
try {
|
|
@@ -548,9 +575,10 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
548
575
|
// Then, after all values have been read from file, read from BUS
|
|
549
576
|
// This allow the virtual devices to get their values before this will be readed from bus
|
|
550
577
|
node.nodeClients
|
|
551
|
-
.filter(
|
|
552
|
-
.filter(
|
|
553
|
-
.forEach(
|
|
578
|
+
.filter(_oClient => _oClient.initialread === 1)
|
|
579
|
+
.filter(_oClient => _oClient.hasOwnProperty("isWatchDog") === false)
|
|
580
|
+
.forEach(_oClient => {
|
|
581
|
+
let oClient = RED.nodes.getNode(_oClient.id); // 05/04/2022 Get the real node
|
|
554
582
|
|
|
555
583
|
if (node.linkStatus !== "connected") return; // 16/08/2021 If not connected, exit
|
|
556
584
|
|
|
@@ -559,7 +587,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
559
587
|
oClient.initialReadAllDevicesInRules();
|
|
560
588
|
} else if (oClient.hasOwnProperty("isLoadControlNode") && oClient.isLoadControlNode) {
|
|
561
589
|
oClient.initialReadAllDevicesInRules();
|
|
562
|
-
} else if (oClient.listenallga
|
|
590
|
+
} else if (oClient.listenallga === true) {
|
|
563
591
|
for (let index = 0; index < node.csv.length; index++) {
|
|
564
592
|
const element = node.csv[index];
|
|
565
593
|
if (!readHistory.includes(element.ga)) {
|
|
@@ -837,7 +865,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
837
865
|
// Handle BUS events
|
|
838
866
|
// ---------------------------------------------------------------------------------------
|
|
839
867
|
function handleBusEvents(_datagram, _echoed) {
|
|
840
|
-
|
|
868
|
+
//console.time('handleBusEvents');
|
|
841
869
|
|
|
842
870
|
let _rawValue = null;
|
|
843
871
|
try {
|
|
@@ -897,9 +925,11 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
897
925
|
}
|
|
898
926
|
switch (_evt) {
|
|
899
927
|
case "GroupValue_Write": {
|
|
928
|
+
// console.time('GroupValue_Write'); // 05/04/2022 Fatto test velocità tra for..loop e forEach. E' risultato sempre comunque più veloce il forEach!
|
|
900
929
|
node.nodeClients
|
|
901
|
-
.filter(
|
|
902
|
-
.forEach(
|
|
930
|
+
.filter(_input => _input.notifywrite === true)
|
|
931
|
+
.forEach(_input => {
|
|
932
|
+
let input = RED.nodes.getNode(_input.id); // 05/04/2022 Get the real node
|
|
903
933
|
|
|
904
934
|
// 19/03/2020 in the middle of coronavirus. Whole italy is red zone, closed down. Scene Controller implementation
|
|
905
935
|
if (input.hasOwnProperty("isSceneController")) {
|
|
@@ -957,7 +987,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
957
987
|
//}).then(function () { }).catch(function () { });
|
|
958
988
|
}
|
|
959
989
|
|
|
960
|
-
} else if (input.listenallga
|
|
990
|
+
} else if (input.listenallga === true) {
|
|
961
991
|
|
|
962
992
|
// Get the GA from CVS
|
|
963
993
|
let oGA = undefined;
|
|
@@ -991,13 +1021,15 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
991
1021
|
};
|
|
992
1022
|
};
|
|
993
1023
|
});
|
|
1024
|
+
//console.timeEnd('GroupValue_Write');
|
|
994
1025
|
break;
|
|
995
1026
|
};
|
|
996
1027
|
case "GroupValue_Response": {
|
|
997
1028
|
|
|
998
1029
|
node.nodeClients
|
|
999
|
-
.filter(
|
|
1000
|
-
.forEach(
|
|
1030
|
+
.filter(_input => _input.notifyresponse === true)
|
|
1031
|
+
.forEach(_input => {
|
|
1032
|
+
let input = RED.nodes.getNode(_input.id); // 05/04/2022 Get the real node
|
|
1001
1033
|
|
|
1002
1034
|
if (input.hasOwnProperty("isLogger")) { // 26/03/2020 Coronavirus is slightly decreasing the affected numer of people. Logger Node
|
|
1003
1035
|
|
|
@@ -1010,7 +1042,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1010
1042
|
//}).then(function () { }).catch(function () { });
|
|
1011
1043
|
}
|
|
1012
1044
|
|
|
1013
|
-
} else if (input.listenallga
|
|
1045
|
+
} else if (input.listenallga === true) {
|
|
1014
1046
|
// Get the DPT
|
|
1015
1047
|
let oGA;
|
|
1016
1048
|
try {
|
|
@@ -1045,8 +1077,9 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1045
1077
|
case "GroupValue_Read": {
|
|
1046
1078
|
|
|
1047
1079
|
node.nodeClients
|
|
1048
|
-
.filter(
|
|
1049
|
-
.forEach(
|
|
1080
|
+
.filter(_input => _input.notifyreadrequest === true)
|
|
1081
|
+
.forEach(_input => {
|
|
1082
|
+
let input = RED.nodes.getNode(_input.id); // 05/04/2022 Get the real node
|
|
1050
1083
|
|
|
1051
1084
|
if (input.hasOwnProperty("isLogger")) { // 26/03/2020 Coronavirus is slightly decreasing the affected numer of people. Logger Node
|
|
1052
1085
|
|
|
@@ -1060,7 +1093,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1060
1093
|
//}).then(function () { }).catch(function () { });
|
|
1061
1094
|
}
|
|
1062
1095
|
|
|
1063
|
-
} else if (input.listenallga
|
|
1096
|
+
} else if (input.listenallga === true) {
|
|
1064
1097
|
// Get the DPT
|
|
1065
1098
|
let oGA;
|
|
1066
1099
|
try {
|
|
@@ -1102,6 +1135,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1102
1135
|
};
|
|
1103
1136
|
default: return
|
|
1104
1137
|
};
|
|
1138
|
+
//console.timeEnd('handleBusEvents');
|
|
1105
1139
|
};
|
|
1106
1140
|
// END Handle BUS events---------------------------------------------------------------------------------------
|
|
1107
1141
|
|
|
@@ -1170,7 +1204,8 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1170
1204
|
}
|
|
1171
1205
|
} catch (error) {
|
|
1172
1206
|
try {
|
|
1173
|
-
|
|
1207
|
+
let oNode = RED.nodes.getNode(oKNXMessage.nodecallerid); // 05/04/2022 Get the real node
|
|
1208
|
+
oNode.setNodeStatus({ fill: "red", shape: "dot", text: "Send response " + error, payload: oKNXMessage.payload, GA: oKNXMessage.grpaddr, dpt: oKNXMessage.dpt, devicename: "" })
|
|
1174
1209
|
} catch (error) { }
|
|
1175
1210
|
}
|
|
1176
1211
|
} else if (oKNXMessage.outputtype === "read") {
|
|
@@ -1194,7 +1229,9 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1194
1229
|
// }
|
|
1195
1230
|
try {
|
|
1196
1231
|
|
|
1197
|
-
node.nodeClients.forEach(
|
|
1232
|
+
node.nodeClients.forEach(_input => {
|
|
1233
|
+
|
|
1234
|
+
let input = RED.nodes.getNode(_input.id); // 05/04/2022 Get the real node
|
|
1198
1235
|
|
|
1199
1236
|
// 16/08/2021 If not connected, exit
|
|
1200
1237
|
if (node.linkStatus !== "connected") {
|
|
@@ -1207,7 +1244,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1207
1244
|
|
|
1208
1245
|
} else if (input.hasOwnProperty("isLogger")) { // 26/03/2020 Coronavirus is slightly decreasing the affected numer of people. Logger Node
|
|
1209
1246
|
|
|
1210
|
-
} else if (input.listenallga
|
|
1247
|
+
} else if (input.listenallga === true) {
|
|
1211
1248
|
|
|
1212
1249
|
} else if (input.topic == oKNXMessage.grpaddr) {
|
|
1213
1250
|
|
|
@@ -1250,7 +1287,8 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1250
1287
|
}
|
|
1251
1288
|
} catch (error) {
|
|
1252
1289
|
try {
|
|
1253
|
-
|
|
1290
|
+
let oNode = RED.nodes.getNode(oKNXMessage.nodecallerid); // 05/04/2022 Get the real node
|
|
1291
|
+
oNode.setNodeStatus({ fill: "red", shape: "dot", text: "Send write " + error, payload: oKNXMessage.payload, GA: oKNXMessage.grpaddr, dpt: oKNXMessage.dpt, devicename: "" })
|
|
1254
1292
|
} catch (error) { }
|
|
1255
1293
|
}
|
|
1256
1294
|
|
|
@@ -1456,6 +1494,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1456
1494
|
node.lockHandleTelegramQueue = false; // Unlock the telegram handling function
|
|
1457
1495
|
|
|
1458
1496
|
saveExposedGAs(); // 04/04/2021 save the current values of GA payload
|
|
1497
|
+
node.nodeClients = []; // 05/04/2023 Nullify
|
|
1459
1498
|
try {
|
|
1460
1499
|
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.destroy();
|
|
1461
1500
|
} catch (error) { }
|
|
@@ -1722,14 +1761,16 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1722
1761
|
if (_msg.outputtype === "response") sEvent = "GroupValue_Response";
|
|
1723
1762
|
if (_msg.outputtype === "read") sEvent = "GroupValue_Read";
|
|
1724
1763
|
|
|
1725
|
-
node.nodeClients.forEach(
|
|
1764
|
+
node.nodeClients.forEach(_input => {
|
|
1765
|
+
|
|
1766
|
+
let input = RED.nodes.getNode(_input.id); // 05/04/2022 Get the real node
|
|
1726
1767
|
|
|
1727
1768
|
// 19/03/2020 in the middle of coronavirus. Whole italy is red zone, closed down. Scene Controller implementation
|
|
1728
1769
|
if (input.hasOwnProperty("isSceneController")) {
|
|
1729
1770
|
|
|
1730
1771
|
} else if (input.hasOwnProperty("isLogger")) { // 26/03/2020 Coronavirus is slightly decreasing the affected numer of people. Logger Node
|
|
1731
1772
|
|
|
1732
|
-
} else if (input.listenallga
|
|
1773
|
+
} else if (input.listenallga === true) {
|
|
1733
1774
|
|
|
1734
1775
|
// Get the DPT
|
|
1735
1776
|
let oGA;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-knx-ultimate",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.38",
|
|
4
4
|
"description": "Control your KNX intallation via Node-Red! Single Node KNX IN/OUT with optional ETS group address importer. Easy to use and highly configurable.",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"mkdirp": "1.0.4",
|