node-red-contrib-knx-ultimate 3.3.39 → 3.3.40
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 -1
- package/img/wiki/MQQT-INFLUXDB.jpg +0 -0
- package/nodes/knxUltimate-config.js +92 -78
- package/nodes/knxUltimate.js +1 -1
- package/nodes/knxUltimateAutoResponder.js +12 -3
- package/package.json +1 -1
- /package/{KNXUltimate.code-workspace → Node-Red-KNXUltimate.code-workspace} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
-
**Version 3.3.
|
|
9
|
+
**Version 3.3.40** - August 2025<br/>
|
|
10
|
+
- KNX Peristent value files are now saved every 5 seconds, other than at the node disconnection.<br/>
|
|
11
|
+
- Fixed an issue preventing the AutoResponder node to start, if the imported ETS file contains malformed structure.<br/>
|
|
12
|
+
|
|
13
|
+
**Version 3.3.39** - July 2025<br/>
|
|
10
14
|
- Added other snippets to the KNX Device function tab.<br/>
|
|
11
15
|
|
|
12
16
|
**Version 3.3.38** - May 2025<br/>
|
|
Binary file
|
|
@@ -68,7 +68,6 @@ module.exports = (RED) => {
|
|
|
68
68
|
node.KNXEthInterfaceManuallyInput = typeof config.KNXEthInterfaceManuallyInput === "undefined" ? "" : config.KNXEthInterfaceManuallyInput; // If you manually set the interface name, it will be wrote here
|
|
69
69
|
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
|
|
70
70
|
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
|
|
71
|
-
node.csv = readCSV(config.csv); // Array from ETS CSV Group Addresses {ga:group address, dpt: datapoint, devicename: full device name with main and subgroups}
|
|
72
71
|
node.localEchoInTunneling = typeof config.localEchoInTunneling !== "undefined" ? config.localEchoInTunneling : true;
|
|
73
72
|
node.userDir = path.join(RED.settings.userDir, "knxultimatestorage"); // 04/04/2021 Supergiovane: Storage for service files
|
|
74
73
|
node.exposedGAs = [];
|
|
@@ -79,6 +78,8 @@ module.exports = (RED) => {
|
|
|
79
78
|
try {
|
|
80
79
|
node.sysLogger = new loggerClass({ loglevel: node.loglevel, setPrefix: node.type + " <" + (node.name || node.id || '') + ">" });
|
|
81
80
|
} catch (error) { console.log(error.stack) }
|
|
81
|
+
node.csv = readCSV(config.csv); // Array from ETS CSV Group Addresses {ga:group address, dpt: datapoint, devicename: full device name with main and subgroups}
|
|
82
|
+
|
|
82
83
|
// 12/11/2021 Connect at start delay
|
|
83
84
|
node.autoReconnect = true; // 20/03/2022 Default
|
|
84
85
|
if (config.autoReconnect === "no" || config.autoReconnect === false) {
|
|
@@ -99,6 +100,7 @@ module.exports = (RED) => {
|
|
|
99
100
|
node.delaybetweentelegrams = (config.delaybetweentelegrams === undefined || config.delaybetweentelegrams === null || config.delaybetweentelegrams === '') ? 25 : Number(config.delaybetweentelegrams);
|
|
100
101
|
if (node.delaybetweentelegrams < 25) node.delaybetweentelegrams = 25; // Protection avoiding handleKNXQueue hangs
|
|
101
102
|
if (node.delaybetweentelegrams > 100) node.delaybetweentelegrams = 100; // Protection avoiding handleKNXQueue hangs
|
|
103
|
+
node.timerSaveExposedGAs = null; // Timer to save the exposed GA every once in a while
|
|
102
104
|
|
|
103
105
|
// 05/12/2021 Set the protocol (this is undefined if coming from ild versions
|
|
104
106
|
if (node.hostProtocol === "Auto") {
|
|
@@ -194,7 +196,7 @@ module.exports = (RED) => {
|
|
|
194
196
|
node.sysLogger?.info("payload cache set to " + path.join(node.userDir, "knxpersistvalues"));
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
function saveExposedGAs() {
|
|
199
|
+
async function saveExposedGAs() {
|
|
198
200
|
const sFile = path.join(node.userDir, "knxpersistvalues", "knxpersist" + node.id + ".json");
|
|
199
201
|
try {
|
|
200
202
|
if (node.exposedGAs.length > 0) {
|
|
@@ -277,10 +279,16 @@ module.exports = (RED) => {
|
|
|
277
279
|
// 17/02/2020 Do initial read (called by node.timerDoInitialRead timer)
|
|
278
280
|
function DoInitialReadFromKNXBusOrFile() {
|
|
279
281
|
if (node.linkStatus !== "connected") return; // 29/08/2019 If not connected, exit
|
|
282
|
+
node.sysLogger?.info("Do DoInitialReadFromKNXBusOrFile");
|
|
280
283
|
loadExposedGAs(); // 04/04/2021 load the current values of GA payload
|
|
284
|
+
node.sysLogger?.info("Loaded persist GA values", node.exposedGAs?.length);
|
|
285
|
+
|
|
286
|
+
if (node.timerSaveExposedGAs !== null) clearInterval(node.timerSaveExposedGAs);
|
|
287
|
+
node.timerSaveExposedGAs = setInterval(async () => {
|
|
288
|
+
await saveExposedGAs();
|
|
289
|
+
}, 5000);
|
|
290
|
+
node.sysLogger?.info("Started timerSaveExposedGAs with array lenght ", node.exposedGAs?.length);
|
|
281
291
|
|
|
282
|
-
node.sysLogger?.info("Loaded saved GA values", node.exposedGAs?.length);
|
|
283
|
-
node.sysLogger?.info("Do DoInitialReadFromKNXBusOrFile");
|
|
284
292
|
try {
|
|
285
293
|
const readHistory = [];
|
|
286
294
|
|
|
@@ -1592,87 +1600,92 @@ module.exports = (RED) => {
|
|
|
1592
1600
|
let sSecondGroupName = "";
|
|
1593
1601
|
let sFather = "";
|
|
1594
1602
|
for (let index = 0; index < fileGA.length; index++) {
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1603
|
+
try {
|
|
1604
|
+
let element = fileGA[index];
|
|
1605
|
+
element = element.replace(/\"/g, ""); // Rimuovo le virgolette
|
|
1606
|
+
element = element.replace(/\#/g, ""); // Rimuovo evetuali #
|
|
1607
|
+
|
|
1608
|
+
if (element !== "") {
|
|
1609
|
+
// Main and secondary group names
|
|
1610
|
+
if ((element.split("\t")[1].match(/-/g) || []).length == 2) {
|
|
1611
|
+
// Found main group family name (Example Light Actuators)
|
|
1612
|
+
sFirstGroupName = element.split("\t")[0] || "";
|
|
1613
|
+
sSecondGroupName = "";
|
|
1614
|
+
}
|
|
1615
|
+
if ((element.split("\t")[1].match(/-/g) || []).length == 1) {
|
|
1616
|
+
// Found second group family name (Example First Floor light)
|
|
1617
|
+
sSecondGroupName = element.split("\t")[0] || "";
|
|
1618
|
+
}
|
|
1619
|
+
if (sFirstGroupName !== "" && sSecondGroupName !== "") {
|
|
1620
|
+
sFather = "(" + sFirstGroupName + "->" + sSecondGroupName + ") ";
|
|
1621
|
+
}
|
|
1613
1622
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1623
|
+
if (element.split("\t")[1].search("-") == -1 && element.split("\t")[1].search("/") !== -1) {
|
|
1624
|
+
// Ho trovato una riga contenente un GA valido, cioè con 2 "/"
|
|
1625
|
+
if (element.split("\t")[5] == "") {
|
|
1626
|
+
if (node.stopETSImportIfNoDatapoint === "stop") {
|
|
1627
|
+
node.error(
|
|
1628
|
+
"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.",
|
|
1629
|
+
);
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
if (node.stopETSImportIfNoDatapoint === "fake") {
|
|
1633
|
+
// 02/03/2020 Whould you like to continue without datapoint? Good. Here a totally fake datapoint
|
|
1634
|
+
node.warn(
|
|
1635
|
+
"KNXUltimate-config: WARNING IMPORT OF ETS CSV FILE. Datapoint not set. You choosed to continue import with a fake datapoint 1.001. -> " +
|
|
1636
|
+
element.split("\t")[0] +
|
|
1637
|
+
" " +
|
|
1638
|
+
element.split("\t")[1],
|
|
1639
|
+
);
|
|
1640
|
+
ajsonOutput.push({
|
|
1641
|
+
ga: element.split("\t")[1],
|
|
1642
|
+
dpt: "1.001",
|
|
1643
|
+
devicename: sFather + element.split("\t")[0] + " (DPT NOT SET IN ETS - FAKE DPT USED)",
|
|
1644
|
+
});
|
|
1645
|
+
} else {
|
|
1646
|
+
// 31/03/2020 Skip import
|
|
1647
|
+
node.warn(
|
|
1648
|
+
"KNXUltimate-config: WARNING IMPORT OF ETS CSV FILE. Datapoint not set. You choosed to skip -> " +
|
|
1649
|
+
element.split("\t")[0] +
|
|
1650
|
+
" " +
|
|
1651
|
+
element.split("\t")[1],
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
} else {
|
|
1655
|
+
const DPTa = element.split("\t")[5].split("-")[1];
|
|
1656
|
+
let DPTb = element.split("\t")[5].split("-")[2];
|
|
1657
|
+
if (typeof DPTb === "undefined") {
|
|
1658
|
+
node.warn(
|
|
1659
|
+
"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 ->" +
|
|
1660
|
+
element.split("\t")[0] +
|
|
1661
|
+
" " +
|
|
1662
|
+
element.split("\t")[1] +
|
|
1663
|
+
" Datapoint: " +
|
|
1664
|
+
element.split("\t")[5],
|
|
1665
|
+
);
|
|
1666
|
+
DPTb = "001"; // default
|
|
1667
|
+
}
|
|
1668
|
+
// Trailing zeroes
|
|
1669
|
+
if (DPTb.length == 1) {
|
|
1670
|
+
DPTb = "00" + DPTb;
|
|
1671
|
+
} else if (DPTb.length == 2) {
|
|
1672
|
+
DPTb = "0" + DPTb;
|
|
1673
|
+
}
|
|
1674
|
+
if (DPTb.length == 3) {
|
|
1675
|
+
DPTb = "" + DPTb; // stupid, but for readability
|
|
1676
|
+
}
|
|
1631
1677
|
ajsonOutput.push({
|
|
1632
1678
|
ga: element.split("\t")[1],
|
|
1633
|
-
dpt: "
|
|
1634
|
-
devicename: sFather + element.split("\t")[0]
|
|
1679
|
+
dpt: DPTa + "." + DPTb,
|
|
1680
|
+
devicename: sFather + element.split("\t")[0],
|
|
1635
1681
|
});
|
|
1636
|
-
} else {
|
|
1637
|
-
// 31/03/2020 Skip import
|
|
1638
|
-
node.warn(
|
|
1639
|
-
"KNXUltimate-config: WARNING IMPORT OF ETS CSV FILE. Datapoint not set. You choosed to skip -> " +
|
|
1640
|
-
element.split("\t")[0] +
|
|
1641
|
-
" " +
|
|
1642
|
-
element.split("\t")[1],
|
|
1643
|
-
);
|
|
1644
|
-
}
|
|
1645
|
-
} else {
|
|
1646
|
-
const DPTa = element.split("\t")[5].split("-")[1];
|
|
1647
|
-
let DPTb = element.split("\t")[5].split("-")[2];
|
|
1648
|
-
if (typeof DPTb === "undefined") {
|
|
1649
|
-
node.warn(
|
|
1650
|
-
"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 ->" +
|
|
1651
|
-
element.split("\t")[0] +
|
|
1652
|
-
" " +
|
|
1653
|
-
element.split("\t")[1] +
|
|
1654
|
-
" Datapoint: " +
|
|
1655
|
-
element.split("\t")[5],
|
|
1656
|
-
);
|
|
1657
|
-
DPTb = "001"; // default
|
|
1658
|
-
}
|
|
1659
|
-
// Trailing zeroes
|
|
1660
|
-
if (DPTb.length == 1) {
|
|
1661
|
-
DPTb = "00" + DPTb;
|
|
1662
|
-
} else if (DPTb.length == 2) {
|
|
1663
|
-
DPTb = "0" + DPTb;
|
|
1664
1682
|
}
|
|
1665
|
-
if (DPTb.length == 3) {
|
|
1666
|
-
DPTb = "" + DPTb; // stupid, but for readability
|
|
1667
|
-
}
|
|
1668
|
-
ajsonOutput.push({
|
|
1669
|
-
ga: element.split("\t")[1],
|
|
1670
|
-
dpt: DPTa + "." + DPTb,
|
|
1671
|
-
devicename: sFather + element.split("\t")[0],
|
|
1672
|
-
});
|
|
1673
1683
|
}
|
|
1674
1684
|
}
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
node.sysLogger?.error("readCSV " + " ROW:" + fileGA[index] + " Error:" + error.stack);
|
|
1675
1687
|
}
|
|
1688
|
+
|
|
1676
1689
|
}
|
|
1677
1690
|
|
|
1678
1691
|
return ajsonOutput;
|
|
@@ -1840,6 +1853,7 @@ module.exports = (RED) => {
|
|
|
1840
1853
|
}, 10000);
|
|
1841
1854
|
|
|
1842
1855
|
node.Disconnect = async (_sNodeStatus = "", _sColor = "grey") => {
|
|
1856
|
+
if (node.timerSaveExposedGAs !== null) clearInterval(node.timerSaveExposedGAs);
|
|
1843
1857
|
if (node.linkStatus === "disconnected") {
|
|
1844
1858
|
node.sysLogger?.debug("Disconnect: already not connected:" + node.linkStatus + ", node.autoReconnect:" + node.autoReconnect);
|
|
1845
1859
|
return;
|
|
@@ -1854,7 +1868,7 @@ module.exports = (RED) => {
|
|
|
1854
1868
|
);
|
|
1855
1869
|
}
|
|
1856
1870
|
node.setAllClientsStatus("Disconnected", _sColor, _sNodeStatus);
|
|
1857
|
-
saveExposedGAs(); // 04/04/2021 save the current values of GA payload
|
|
1871
|
+
await saveExposedGAs(); // 04/04/2021 save the current values of GA payload
|
|
1858
1872
|
node.sysLogger?.debug("Disconnected, node.autoReconnect:" + node.autoReconnect);
|
|
1859
1873
|
};
|
|
1860
1874
|
|
package/nodes/knxUltimate.js
CHANGED
|
@@ -121,7 +121,7 @@ module.exports = function (RED) {
|
|
|
121
121
|
node.inputmessage = {}; // Stores the input message to be passed through
|
|
122
122
|
node.timerTTLInputMessage = null; // The stored node.inputmessage has a ttl.
|
|
123
123
|
try {
|
|
124
|
-
node.sysLogger = new loggerClass({ loglevel: node.serverKNX.loglevel, setPrefix: node.type + " <" + (node.name || node.id || '') + ">" });
|
|
124
|
+
node.sysLogger = new loggerClass({ loglevel: node.serverKNX.loglevel || 'error', setPrefix: node.type + " <" + (node.name || node.id || '') + ">" });
|
|
125
125
|
} catch (error) { console.log(error.stack) }
|
|
126
126
|
node.sendMsgToKNXCode = config.sendMsgToKNXCode || undefined;
|
|
127
127
|
node.receiveMsgFromKNXCode = config.receiveMsgFromKNXCode || undefined;
|
|
@@ -35,7 +35,7 @@ module.exports = function (RED) {
|
|
|
35
35
|
function knxUltimateAutoResponder(config) {
|
|
36
36
|
RED.nodes.createNode(this, config)
|
|
37
37
|
const node = this
|
|
38
|
-
node.serverKNX = RED.nodes.getNode(config.server)
|
|
38
|
+
node.serverKNX = RED.nodes.getNode(config.server)
|
|
39
39
|
node.topic = node.name
|
|
40
40
|
node.name = config.name === undefined ? 'Auto responder' : config.name
|
|
41
41
|
node.outputtopic = node.name
|
|
@@ -50,6 +50,9 @@ module.exports = function (RED) {
|
|
|
50
50
|
node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS)
|
|
51
51
|
node.exposedGAs = [];
|
|
52
52
|
node.commandText = []; // Raw list Respond To
|
|
53
|
+
node.timerSaveExposedGAs = null;
|
|
54
|
+
if (node.serverKNX === null) { node.status({ fill: 'red', shape: 'dot', text: '[NO GATEWAY SELECTED]' }); return; }
|
|
55
|
+
|
|
53
56
|
try {
|
|
54
57
|
node.sysLogger = new loggerClass({ loglevel: node.serverKNX.loglevel, setPrefix: node.type + " <" + (node.name || node.id || '') + ">" });
|
|
55
58
|
} catch (error) { console.log(error.stack) }
|
|
@@ -66,7 +69,7 @@ module.exports = function (RED) {
|
|
|
66
69
|
// }
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
node.saveExposedGAs = () => {
|
|
72
|
+
node.saveExposedGAs = async () => {
|
|
70
73
|
const sFile = path.join(node.serverKNX.userDir, "knxpersistvalues", "knxpersist" + node.id + ".json");
|
|
71
74
|
try {
|
|
72
75
|
if (node.exposedGAs.length > 0) {
|
|
@@ -77,6 +80,7 @@ module.exports = function (RED) {
|
|
|
77
80
|
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error("knxUltimateAutoResponder: unable to write peristent values to the file " + sFile + " " + err.message);
|
|
78
81
|
}
|
|
79
82
|
}
|
|
83
|
+
|
|
80
84
|
node.loadExposedGAs = () => {
|
|
81
85
|
const sFile = path.join(node.serverKNX.userDir, "knxpersistvalues", "knxpersist" + node.id + ".json");
|
|
82
86
|
try {
|
|
@@ -94,10 +98,14 @@ module.exports = function (RED) {
|
|
|
94
98
|
node.exposedGAs.forEach(element => {
|
|
95
99
|
element.enabled = false;
|
|
96
100
|
})
|
|
101
|
+
if (node.timerSaveExposedGAs !== null) clearInterval(node.timerSaveExposedGAs);
|
|
102
|
+
node.sysLogger?.info("Started timerSaveExposedGAs with array lenght ", node.exposedGAs?.length);
|
|
103
|
+
node.timerSaveExposedGAs = setInterval(async () => {
|
|
104
|
+
await node.saveExposedGAs();
|
|
105
|
+
}, 5000);
|
|
97
106
|
} catch (error) {
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
|
|
101
109
|
// Add the ETS CSV file list to exposedGAs
|
|
102
110
|
if (node.serverKNX.csv === undefined || node.serverKNX.csv === '' || node.serverKNX.csv.length === 0) {
|
|
103
111
|
node.status({ fill: 'grey', shape: 'ring', text: 'No ETS file imported', payload: '', dpt: '', devicename: '' });
|
|
@@ -219,6 +227,7 @@ module.exports = function (RED) {
|
|
|
219
227
|
|
|
220
228
|
node.on('close', function (done) {
|
|
221
229
|
try {
|
|
230
|
+
if (node.timerSaveExposedGAs !== null) clearInterval(node.timerSaveExposedGAs);
|
|
222
231
|
node.saveExposedGAs();
|
|
223
232
|
} catch (error) {
|
|
224
233
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=20.18.1"
|
|
5
5
|
},
|
|
6
|
-
"version": "3.3.
|
|
6
|
+
"version": "3.3.40",
|
|
7
7
|
"description": "Control your KNX intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"binary-parser": "2.2.1",
|
|
File without changes
|