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.
- package/CHANGELOG.md +7 -1
- package/nodes/commonFunctions.js +0 -1
- package/nodes/hue-config.js +12 -6
- package/nodes/knxUltimate-config copy.html +456 -0
- package/nodes/knxUltimate-config copy.js +1939 -0
- package/nodes/knxUltimate-config.html +4 -9
- package/nodes/knxUltimate-config.js +128 -213
- package/nodes/knxUltimate.js +41 -32
- package/nodes/knxUltimateAlerter.js +11 -15
- package/nodes/knxUltimateAutoResponder.js +21 -13
- package/nodes/knxUltimateGarageDoorBarrierOpener.js +16 -8
- package/nodes/knxUltimateGlobalContext.js +10 -10
- package/nodes/knxUltimateHueBattery.js +8 -8
- package/nodes/knxUltimateHueButton.js +9 -9
- package/nodes/knxUltimateHueContactSensor.js +7 -7
- package/nodes/knxUltimateHueLight.js +26 -26
- package/nodes/knxUltimateHueLightSensor.js +8 -8
- package/nodes/knxUltimateHueMotion.js +7 -7
- package/nodes/knxUltimateHueScene.js +17 -9
- package/nodes/knxUltimateHueTapDial.js +13 -13
- package/nodes/knxUltimateHueTemperatureSensor.js +8 -8
- package/nodes/knxUltimateHueZigbeeConnectivity.js +8 -8
- package/nodes/knxUltimateHuedevice_software_update.js +8 -8
- package/nodes/knxUltimateLoadControl.js +24 -21
- package/nodes/knxUltimateLogger.js +8 -8
- package/nodes/knxUltimateSceneController.js +20 -13
- package/nodes/knxUltimateViewer.js +8 -8
- package/nodes/knxUltimateWatchDog.js +14 -14
- package/nodes/utils/http.js +0 -1
- package/nodes/utils/hueEngine.js +13 -6
- package/nodes/utils/payloadManipulation.js +2 -2
- package/nodes/utils/sysLogger.js +23 -60
- 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
|
+
};
|