node-red-contrib-knx-ultimate 4.0.3 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 4.0.4** - September 2025<br/>
10
+ - NEW: You have now the choide to select multiple modes to authenticate to a KNX Gateway.<br/>
11
+
12
+ **Version 4.0.4** - September 2025<br/>
13
+ - NEW: you can now login to your secure gateway using only tunnel and password, without the keyring file.<br/>
14
+
9
15
  **Version 4.0.3** - September 2025<br/>
10
16
  - GlobalContextNode: Added "lastupdate" property.<br/>
11
17
  - Device Node. Refractor snippet list and UI.<br/>
package/README.md CHANGED
@@ -86,10 +86,8 @@ Please subscribe to the [Youtube channel](https://www.youtube.com/playlist?list=
86
86
  |--|--|
87
87
  | KNX Tunnelling | ![](https://placehold.co/200x20/green/white?text=YES) |
88
88
  | KNX Routing | ![](https://placehold.co/200x20/green/white?text=YES) |
89
+ | KNX IP Secure/Data secure | ![](https://placehold.co/200x20/green/white?text=YES) |
89
90
  | Philips Hue v2 | ![](https://placehold.co/200x20/green/white?text=YES) |
90
- | KNX Secure Tunnelling | ![](https://placehold.co/200x20/green/white?text=YES) |
91
- | KNX Secure Routing | ![](https://placehold.co/200x20/green/white?text=YES) |
92
-
93
91
 
94
92
  <br/>
95
93
 
@@ -97,7 +95,7 @@ Please subscribe to the [Youtube channel](https://www.youtube.com/playlist?list=
97
95
  ## DOCUMENTATION
98
96
 
99
97
  * [Wiki and Help](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki)
100
- * [FAQ + Troubleshoot](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/5.-FAQ-Troubleshoot)
98
+ * [FAQ + Troubleshoot](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/FAQ-Troubleshoot)
101
99
  * [Security best practices](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/SECURITY)
102
100
 
103
101
 
@@ -21,8 +21,12 @@
21
21
  ignoreTelegramsWithRepeatedFlag: { value: false, required: false },
22
22
  keyringFileXML: { value: "" },
23
23
  knxSecureSelected: { value: false },
24
+ secureCredentialsMode: { value: "keyring" },
24
25
  tunnelIASelection: { value: "Auto" },
25
26
  tunnelIA: { value: "" },
27
+ tunnelInterfaceIndividualAddress: { value: "" },
28
+ tunnelUserPassword: { value: "" },
29
+ tunnelUserId: { value: "" },
26
30
  autoReconnect: { value: "yes" }
27
31
  },
28
32
  credentials: {
@@ -68,13 +72,13 @@
68
72
  active: (node.knxSecureSelected === true || node.knxSecureSelected === 'true') ? 1 : 0,
69
73
  activate: function (event, ui) {
70
74
  node.knxSecureSelected = $(ui.newTab).index() === 1;
71
- try { updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); } catch (e) { }
75
+ try { updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); } catch (e) { }
72
76
  }
73
77
  });
74
78
  // Ensure the correct tab is enforced after all UI init
75
79
  try {
76
80
  const initialActive = (node.knxSecureSelected === true || node.knxSecureSelected === 'true') ? 1 : 0;
77
- setTimeout(() => { $("#tabsMain").tabs("option", "active", initialActive); updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); }, 0);
81
+ setTimeout(() => { $("#tabsMain").tabs("option", "active", initialActive); updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); }, 0);
78
82
  } catch (e) { }
79
83
 
80
84
  // Keyring ACE editor setup
@@ -98,6 +102,96 @@
98
102
  try { node._csvEditor.getSession().setUseWrapMode(true); } catch (e) { }
99
103
  } catch (e) { }
100
104
 
105
+ function getSecureCredentialsMode() {
106
+ try {
107
+ const sel = $("#node-config-input-secureCredentialsMode").val();
108
+ return (typeof sel === 'string' && sel.length > 0) ? sel : 'keyring';
109
+ } catch (e) {
110
+ return 'keyring';
111
+ }
112
+ }
113
+
114
+ function isSecureTabActive() {
115
+ try {
116
+ return $("#tabsMain").tabs("option", "active") === 1;
117
+ } catch (e) {
118
+ return !!(typeof node.knxSecureSelected !== 'undefined' ? node.knxSecureSelected : false);
119
+ }
120
+ }
121
+
122
+ function parseIPv4Address(str) {
123
+ if (typeof str !== 'string') return null;
124
+ const parts = str.trim().split('.');
125
+ if (parts.length !== 4) return null;
126
+ const octets = [];
127
+ for (let i = 0; i < parts.length; i++) {
128
+ const part = parts[i];
129
+ if (!/^\d+$/.test(part)) return null;
130
+ const value = Number(part);
131
+ if (value < 0 || value > 255) return null;
132
+ octets.push(value);
133
+ }
134
+ return octets;
135
+ }
136
+
137
+ function isMulticastIPv4(octets) {
138
+ if (!Array.isArray(octets) || octets.length !== 4) return false;
139
+ const first = octets[0];
140
+ return first >= 224 && first <= 239;
141
+ }
142
+
143
+ function normalizeTunnelIAValue(value) {
144
+ if (typeof value !== 'string') return '';
145
+ const trimmed = value.trim();
146
+ return (trimmed && trimmed !== 'undefined') ? trimmed : '';
147
+ }
148
+
149
+ function setTunnelIAValue(value) {
150
+ const normalized = normalizeTunnelIAValue(value || '');
151
+ try { $("#node-config-input-tunnelIA").val(normalized); } catch (e) { }
152
+ try { $("#node-config-input-tunnelInterfaceIndividualAddress").val(normalized); } catch (e) { }
153
+ }
154
+
155
+ function syncTunnelIAFromManual() {
156
+ try { setTunnelIAValue($("#node-config-input-tunnelInterfaceIndividualAddress").val()); } catch (e) { }
157
+ }
158
+
159
+ function syncTunnelIAFromKeyring() {
160
+ try { setTunnelIAValue($("#node-config-input-tunnelIA").val()); } catch (e) { }
161
+ }
162
+
163
+ function enforceProtocolFromIP() {
164
+ try {
165
+ const ipTyped = String($("#node-config-input-host").val() || '').trim();
166
+ if (!ipTyped) return;
167
+ const octets = parseIPv4Address(ipTyped);
168
+ if (!octets || isMulticastIPv4(octets)) return;
169
+ const autoProto = isSecureTabActive() ? 'TunnelTCP' : 'TunnelUDP';
170
+ const $sel = $("#node-config-input-hostProtocol");
171
+ if ($sel.val() !== autoProto) {
172
+ $sel.val(autoProto);
173
+ updateTunnelIAVisibility();
174
+ updateSecureMulticastHint();
175
+ updatePhysAddrVisibility();
176
+ }
177
+ } catch (e) { }
178
+ }
179
+
180
+ function updateSecureCredentialMode() {
181
+ try {
182
+ const mode = getSecureCredentialsMode();
183
+ const showKeyringFields = (mode === 'keyring' || mode === 'combined');
184
+ const showKeyringIAControls = (mode === 'keyring');
185
+ $("#secureKeyringFields").toggle(showKeyringFields);
186
+ if (!showKeyringIAControls) {
187
+ $("#rowTunnelIASelection").hide();
188
+ $("#divTunnelIA").hide();
189
+ }
190
+ } catch (e) { }
191
+ updateTunnelIAVisibility();
192
+ enforceProtocolFromIP();
193
+ }
194
+
101
195
  // Helper: show/hide Tunnel IA controls depending on mode
102
196
  function updateTunnelIAVisibility() {
103
197
  try {
@@ -105,7 +199,10 @@
105
199
  const hostProtocol = $("#node-config-input-hostProtocol").val();
106
200
  const ip = $("#node-config-input-host").val();
107
201
  const isMulticast = (hostProtocol === 'Multicast') || (ip === '224.0.23.12');
108
- const showTunnelIA = isSecure && !isMulticast;
202
+ const mode = getSecureCredentialsMode();
203
+ const keyringMode = (mode === 'keyring');
204
+ const manualMode = (mode === 'manual' || mode === 'combined');
205
+ const showTunnelIA = isSecure && !isMulticast && keyringMode;
109
206
  if (showTunnelIA) {
110
207
  $("#rowTunnelIASelection").show();
111
208
  if ($("#node-config-input-tunnelIASelection").val() === 'Manual') {
@@ -117,6 +214,15 @@
117
214
  $("#rowTunnelIASelection").hide();
118
215
  $("#divTunnelIA").hide();
119
216
  }
217
+ const showManualTunnelFields = isSecure && !isMulticast && manualMode;
218
+ $("#rowTunnelInterfaceIndividualAddress").toggle(showManualTunnelFields);
219
+ $("#rowTunnelUserPassword").toggle(showManualTunnelFields);
220
+ $("#rowTunnelUserId").toggle(showManualTunnelFields);
221
+ if (!showManualTunnelFields) {
222
+ try {
223
+ $("#node-config-input-tunnelInterfaceIndividualAddress").removeClass('input-error');
224
+ } catch (e) { }
225
+ }
120
226
  } catch (e) { }
121
227
  }
122
228
  // Helper: ensure Physical Address row is visible (required also in multicast)
@@ -203,6 +309,18 @@
203
309
  let aTunnelIAs = [];
204
310
  function populateTunnelIAList() {
205
311
  try {
312
+ const mode = getSecureCredentialsMode();
313
+ const keyringMode = (mode === 'keyring');
314
+ if (!keyringMode) {
315
+ aTunnelIAs = [];
316
+ try {
317
+ const $ia = $("#node-config-input-tunnelIA");
318
+ if ($ia.data('ui-autocomplete')) {
319
+ $ia.autocomplete('option', 'source', aTunnelIAs);
320
+ }
321
+ } catch (e) { }
322
+ return;
323
+ }
206
324
  const keyring = $("#node-config-input-keyringFileXML").val();
207
325
  const pwd = $("#node-config-input-keyringFilePassword").val();
208
326
  const params = { serverId: node.id };
@@ -231,8 +349,14 @@
231
349
 
232
350
  // Secure: Tunnel IA selection
233
351
  try {
352
+ const storedTunnelIA = (() => {
353
+ const manualStored = normalizeTunnelIAValue(node.tunnelInterfaceIndividualAddress);
354
+ const keyringStored = normalizeTunnelIAValue(node.tunnelIA);
355
+ return manualStored || keyringStored;
356
+ })();
357
+ setTunnelIAValue(storedTunnelIA);
234
358
  $("#node-config-input-tunnelIASelection").val(typeof node.tunnelIASelection === 'undefined' ? 'Auto' : node.tunnelIASelection);
235
- $("#node-config-input-tunnelIA").val(typeof node.tunnelIA === 'undefined' ? '' : node.tunnelIA);
359
+ $("#node-config-input-tunnelIA").on('change input', syncTunnelIAFromKeyring);
236
360
  const toggleTunnelIA = () => {
237
361
  if ($("#node-config-input-tunnelIASelection").val() === 'Manual') {
238
362
  $("#divTunnelIA").show();
@@ -255,8 +379,8 @@
255
379
  $("#node-config-input-tunnelIA").autocomplete({
256
380
  minLength: 0,
257
381
  source: function (request, response) { response(aTunnelIAs); },
258
- focus: function (event, ui) { $(this).val(ui.item.value); return false; },
259
- select: function (event, ui) { $(this).val(ui.item.value); return false; }
382
+ focus: function (event, ui) { setTunnelIAValue(ui.item.value); return false; },
383
+ select: function (event, ui) { setTunnelIAValue(ui.item.value); return false; }
260
384
  }).on('focus click', function () {
261
385
  // Show dropdown on focus/click
262
386
  $(this).autocomplete('search', '');
@@ -264,6 +388,18 @@
264
388
  } catch (e) { }
265
389
  } catch (error) { }
266
390
 
391
+ try {
392
+ $("#node-config-input-secureCredentialsMode").val(typeof node.secureCredentialsMode === 'undefined' ? 'keyring' : node.secureCredentialsMode);
393
+ $("#node-config-input-tunnelInterfaceIndividualAddress").on('change input', syncTunnelIAFromManual);
394
+ $("#node-config-input-tunnelUserPassword").val(typeof node.tunnelUserPassword === 'undefined' ? '' : node.tunnelUserPassword);
395
+ $("#node-config-input-tunnelUserId").val(typeof node.tunnelUserId === 'undefined' ? '' : node.tunnelUserId);
396
+ $("#node-config-input-secureCredentialsMode").on('change', function () {
397
+ updateSecureCredentialMode();
398
+ updateSecureMulticastHint();
399
+ });
400
+ updateSecureCredentialMode();
401
+ } catch (error) { }
402
+
267
403
  // Autocomplete with KNX IP Interfaces + Refresh capability
268
404
  let aKNXInterfaces = [];
269
405
  function refreshKNXGateways(openDropdown) {
@@ -454,9 +590,7 @@
454
590
  // Abilita manualmente la lista dei protocolli quando l'utente modifica l'IP a mano
455
591
  try {
456
592
  $("#node-config-input-host").on('input', function () {
457
- const isSecure = (function () {
458
- try { return $("#tabsMain").tabs("option", "active") === 1; } catch (e) { return !!node.knxSecureSelected; }
459
- })();
593
+ const isSecure = isSecureTabActive();
460
594
  const $sel = $("#node-config-input-hostProtocol");
461
595
  // Abilita select
462
596
  $sel.prop('disabled', false);
@@ -494,6 +628,8 @@
494
628
  }
495
629
  // If discovery knows protocol, preselect it (keep select enabled for manual override)
496
630
  if (proto) { $("#node-config-input-hostProtocol").val(proto); }
631
+ const octets = parseIPv4Address(ipTyped);
632
+ if (octets && !isMulticastIPv4(octets)) enforceProtocolFromIP();
497
633
  }
498
634
  } catch (e) { }
499
635
  });
@@ -560,6 +696,9 @@
560
696
  node._csvEditor.destroy();
561
697
  node._csvEditor = null;
562
698
  }
699
+ try {
700
+ $("#node-config-input-tunnelIA").val($("#node-config-input-tunnelInterfaceIndividualAddress").val());
701
+ } catch (e) { }
563
702
  } catch (e) { }
564
703
  // Check if the csv file contains errors
565
704
  if (($("#node-config-input-csv").val() != 'undefined' && $("#node-config-input-csv").val() != "") || ($("#node-config-input-keyring").val() != 'undefined' && $("#node-config-input-keyring").val() != "")) {
@@ -675,6 +814,15 @@
675
814
  <span data-i18n="knxUltimate-config.ets.youtubeKeyring"></span>
676
815
  </a>
677
816
  </div>
817
+ <div class="form-row">
818
+ <label for="node-config-input-secureCredentialsMode"><i class="fa fa-shield"></i> <span data-i18n="knxUltimate-config.ets.secure_credentials_mode"></span></label>
819
+ <select id="node-config-input-secureCredentialsMode" style="width:200px;">
820
+ <option value="keyring" data-i18n="knxUltimate-config.ets.secure_credentials_mode_keyring"></option>
821
+ <option value="manual" data-i18n="knxUltimate-config.ets.secure_credentials_mode_manual"></option>
822
+ <option value="combined" data-i18n="knxUltimate-config.ets.secure_credentials_mode_combined"></option>
823
+ </select>
824
+ </div>
825
+ <div id="secureKeyringFields">
678
826
  <div class="form-row">
679
827
  <label for="node-config-input-keyringFileXML"><span data-i18n="knxUltimate-config.ets.keyring_file"></span></label>
680
828
  <div id="node-config-input-keyringFileXML-editor" class="node-text-editor" style="height:200px; min-height:140px; width:100%"></div>
@@ -695,6 +843,19 @@
695
843
  <label for="node-config-input-tunnelIA"><i class="fa fa-map-marker"></i> <span data-i18n="knxUltimate-config.properties.tunnel_ia_input_label"></span></label>
696
844
  <input type="text" id="node-config-input-tunnelIA" style="width:150px;" data-i18n="[placeholder]knxUltimate-config.properties.tunnel_ia_placeholder"/>
697
845
  </div>
846
+ </div>
847
+ <div class="form-row" id="rowTunnelInterfaceIndividualAddress" style="display:none;">
848
+ <label for="node-config-input-tunnelInterfaceIndividualAddress"><i class="fa fa-map-marker"></i> <span data-i18n="knxUltimate-config.ets.tunnel_interface_individual_address"></span></label>
849
+ <input type="text" id="node-config-input-tunnelInterfaceIndividualAddress" style="width:150px;" data-i18n="[placeholder]knxUltimate-config.ets.tunnel_interface_individual_address_placeholder"/>
850
+ </div>
851
+ <div class="form-row" id="rowTunnelUserId" style="display:none;">
852
+ <label for="node-config-input-tunnelUserId"><i class="fa fa-id-badge"></i> <span data-i18n="knxUltimate-config.ets.tunnel_user_id"></span></label>
853
+ <input type="text" id="node-config-input-tunnelUserId" style="width:150px;" />
854
+ </div>
855
+ <div class="form-row" id="rowTunnelUserPassword" style="display:none;">
856
+ <label for="node-config-input-tunnelUserPassword"><i class="fa fa-lock"></i> <span data-i18n="knxUltimate-config.ets.tunnel_user_password"></span></label>
857
+ <input type="text" id="node-config-input-tunnelUserPassword" style="width:200px;" />
858
+ </div>
698
859
  </p>
699
860
  </div>
700
861
  </div>
@@ -98,9 +98,27 @@ module.exports = (RED) => {
98
98
  // 24/07/2021 KNX Secure checks...
99
99
  node.keyringFileXML = typeof config.keyringFileXML === "undefined" || config.keyringFileXML.trim() === "" ? "" : config.keyringFileXML;
100
100
  node.knxSecureSelected = typeof config.knxSecureSelected === "undefined" ? false : config.knxSecureSelected;
101
+ node.secureCredentialsMode = typeof config.secureCredentialsMode === "undefined" ? "keyring" : config.secureCredentialsMode;
101
102
  // 2025-09 Secure Tunnel Interface IA selection (Auto/Manual)
102
103
  node.tunnelIASelection = typeof config.tunnelIASelection === "undefined" ? "Auto" : config.tunnelIASelection;
103
- node.tunnelIA = typeof config.tunnelIA === "undefined" ? "" : config.tunnelIA;
104
+ node.tunnelIA = typeof config.tunnelIA === "undefined" || config.tunnelIA === null ? "" : String(config.tunnelIA);
105
+ node.tunnelInterfaceIndividualAddress = typeof config.tunnelInterfaceIndividualAddress === "undefined" || config.tunnelInterfaceIndividualAddress === null
106
+ ? ""
107
+ : String(config.tunnelInterfaceIndividualAddress);
108
+ const normalizedTunnelIA = (value) => {
109
+ if (typeof value !== "string") return "";
110
+ const trimmed = value.trim();
111
+ return trimmed === "undefined" ? "" : trimmed;
112
+ };
113
+ node.tunnelIA = normalizedTunnelIA(node.tunnelIA);
114
+ node.tunnelInterfaceIndividualAddress = normalizedTunnelIA(node.tunnelInterfaceIndividualAddress);
115
+ if (!node.tunnelInterfaceIndividualAddress && node.tunnelIA) {
116
+ node.tunnelInterfaceIndividualAddress = node.tunnelIA;
117
+ } else if (!node.tunnelIA && node.tunnelInterfaceIndividualAddress) {
118
+ node.tunnelIA = node.tunnelInterfaceIndividualAddress;
119
+ }
120
+ node.tunnelUserPassword = typeof config.tunnelUserPassword === "undefined" ? "" : config.tunnelUserPassword;
121
+ node.tunnelUserId = typeof config.tunnelUserId === "undefined" ? "" : config.tunnelUserId;
104
122
  node.name = config.name === undefined || config.name === "" ? node.host : config.name; // 12/08/2021
105
123
  node.timerKNXUltimateCheckState = null; // 08/10/2021 Check the state. If not connected and autoreconnect is true, retrig the connetion attempt.
106
124
  node.knxConnectionProperties = null; // Retains the connection properties
@@ -126,7 +144,8 @@ module.exports = (RED) => {
126
144
  ) {
127
145
  node.hostProtocol = "Multicast";
128
146
  } else {
129
- node.hostProtocol = "TunnelUDP";
147
+ const isSecure = node.knxSecureSelected === true || node.knxSecureSelected === "true";
148
+ node.hostProtocol = isSecure ? "TunnelTCP" : "TunnelUDP";
130
149
  }
131
150
  node.sysLogger?.info("IP Protocol AUTO SET to " + node.hostProtocol + ", based on IP " + node.host);
132
151
  }
@@ -158,35 +177,68 @@ module.exports = (RED) => {
158
177
  (async () => {
159
178
  try {
160
179
  if (node.knxSecureSelected) {
161
- // Prepare secure config for KNXClient. The loader accepts either a
162
- // filesystem path to .knxkeys or the raw XML/base64 content.
163
- node.secureTunnelConfig = {
164
- knxkeys_file_path: node.keyringFileXML || "",
165
- knxkeys_password: node.credentials?.keyringFilePassword || "",
166
- };
167
- // Manual IA selection
168
- try {
169
- if (node.tunnelIASelection === "Manual" && typeof node.tunnelIA === "string" && node.tunnelIA.trim() !== "") {
170
- node.secureTunnelConfig.tunnelInterfaceIndividualAddress = node.tunnelIA.trim();
171
- } else {
172
- node.secureTunnelConfig.tunnelInterfaceIndividualAddress = ""; // Auto
180
+ const secureMode = typeof node.secureCredentialsMode === "string" ? node.secureCredentialsMode : "keyring";
181
+ const useManual = secureMode === "manual" || secureMode === "combined";
182
+ const useKeyring = secureMode === "keyring" || secureMode === "combined";
183
+
184
+ const secureConfig = {};
185
+ const modeParts = [];
186
+
187
+ if (useManual) {
188
+ const manualIA = (node.tunnelInterfaceIndividualAddress || "").trim();
189
+ const manualUserId = (node.tunnelUserId || "").trim();
190
+ const manualPwd = node.tunnelUserPassword || "";
191
+
192
+ if (manualIA) {
193
+ secureConfig.tunnelInterfaceIndividualAddress = manualIA;
194
+ }
195
+ if (manualUserId) {
196
+ secureConfig.tunnelUserId = manualUserId;
173
197
  }
174
- } catch (e) { /* empty */ }
198
+ // Always include password property so KNX library receives the intended value (even if empty)
199
+ secureConfig.tunnelUserPassword = manualPwd;
200
+ modeParts.push("manual tunnel credentials");
201
+ }
202
+
203
+ if (useKeyring) {
204
+ secureConfig.knxkeys_file_path = node.keyringFileXML || "";
205
+ secureConfig.knxkeys_password = node.credentials?.keyringFilePassword || "";
175
206
 
176
- // Optional early validation to give immediate feedback (non-fatal)
177
- if (Keyring && node.keyringFileXML && (node.credentials?.keyringFilePassword || "") !== "") {
178
207
  try {
179
- const kr = new Keyring();
180
- await kr.load(node.keyringFileXML, node.credentials.keyringFilePassword);
181
- const createdBy = kr.getCreatedBy?.() || "unknown";
182
- const created = kr.getCreated?.() || "unknown";
183
- RED.log.info(`KNX-Secure: Keyring validated (Created by ${createdBy} on ${created}) using node ${node.name || node.id}`);
184
- } catch (err) {
185
- node.sysLogger?.error("KNX Secure: keyring validation failed: " + err.message);
186
- // Keep secure enabled: KNXClient will emit detailed errors on connect
208
+ const manualSelectionIA = normalizedTunnelIA(node.tunnelIA);
209
+ if (node.tunnelIASelection === "Manual" && manualSelectionIA) {
210
+ if (!secureConfig.tunnelInterfaceIndividualAddress) {
211
+ secureConfig.tunnelInterfaceIndividualAddress = manualSelectionIA;
212
+ }
213
+ } else if (!secureConfig.tunnelInterfaceIndividualAddress) {
214
+ secureConfig.tunnelInterfaceIndividualAddress = ""; // Auto (let KNX stack select)
215
+ }
216
+ } catch (e) { /* empty */ }
217
+
218
+ // Optional early validation to give immediate feedback (non-fatal)
219
+ if (Keyring && node.keyringFileXML && (node.credentials?.keyringFilePassword || "") !== "") {
220
+ try {
221
+ const kr = new Keyring();
222
+ await kr.load(node.keyringFileXML, node.credentials.keyringFilePassword);
223
+ const createdBy = kr.getCreatedBy?.() || "unknown";
224
+ const created = kr.getCreated?.() || "unknown";
225
+ RED.log.info(`KNX-Secure: Keyring validated (Created by ${createdBy} on ${created}) using node ${node.name || node.id}`);
226
+ } catch (err) {
227
+ node.sysLogger?.error("KNX Secure: keyring validation failed: " + err.message);
228
+ // Keep secure enabled: KNXClient will emit detailed errors on connect
229
+ }
187
230
  }
231
+ modeParts.push("keyring file/password");
232
+ }
233
+
234
+ if (Object.keys(secureConfig).length > 0) {
235
+ node.secureTunnelConfig = secureConfig;
188
236
  } else {
189
- RED.log.info("KNX-Secure: secure mode selected. Using provided keyring and password.");
237
+ node.secureTunnelConfig = undefined;
238
+ }
239
+
240
+ if (modeParts.length > 0) {
241
+ RED.log.info(`KNX-Secure: secure mode selected (${modeParts.join(" + ")}). Node ${node.name || node.id}`);
190
242
  }
191
243
  } else {
192
244
  RED.log.info("KNX-Unsecure: connection to insecure interface/router using node " + (node.name || node.id));
@@ -750,7 +802,8 @@ module.exports = (RED) => {
750
802
  _dest = _datagram.cEMIMessage.dstAddress.toString();
751
803
 
752
804
  if (_evt === null || _src === null || _dest === null) {
753
- node.sysLogger?.error("HandleBusEvent: unable to parse telegram, ignored"); return
805
+ node.sysLogger?.warn(`HandleBusEvent: unable to parse telegram, ignored (evt=${_evt || 'n/a'} src=${_src || 'n/a'} dest=${_dest || 'n/a'})`);
806
+ return;
754
807
  };
755
808
 
756
809
  _echoed = _echoed || false;
@@ -85,6 +85,14 @@
85
85
  "youtubeKeyring": "Anleitung des XML Keyring Exports auf Youtube",
86
86
  "keyring_file": "Keyring-Datei",
87
87
  "password": "Passwort",
88
+ "secure_credentials_mode": "Quelle für Secure-Zugangsdaten",
89
+ "secure_credentials_mode_keyring": "ETS-Keyring-Datei",
90
+ "secure_credentials_mode_manual": "Manuelle Zugangsdaten",
91
+ "secure_credentials_mode_combined": "Keyring + manuelles Tunnel-Passwort",
92
+ "tunnel_interface_individual_address": "Individuelle Adresse der Tunnel-Schnittstelle",
93
+ "tunnel_interface_individual_address_placeholder": "1.1.1",
94
+ "tunnel_user_id": "Tunnel-Benutzer-ID",
95
+ "tunnel_user_password": "Tunnel-Benutzerpasswort",
88
96
  "ga_list_label": "ETS-Gruppenadressliste"
89
97
  },
90
98
  "utility": {
@@ -19,6 +19,14 @@ Dieser Node stellt die Verbindung zu deinem KNX/IP‑Gateway her.
19
19
  | KNX Physical Address | Physikalische KNX‑Adresse, z. B. `1.1.200`. Standard: `15.15.22`. |
20
20
  | Bind to local interface | Lokales Netzwerk‑Interface für die Kommunikation. "Auto" wählt automatisch. Bei mehreren Interfaces (Ethernet/WLAN) ist eine manuelle Auswahl empfehlenswert, damit keine UDP‑Telegramme verloren gehen. |
21
21
  | Automatically connect to KNX BUS at start | Automatisch beim Start verbinden. Standard: "Yes". |
22
+ | Secure credentials source | Leg fest, wie KNX Secure Daten bereitgestellt werden: **ETS-Keyring-Datei** (Data-Secure-Schlüssel und ggf. Tunnel-Zugang aus dem Keyring), **Manuelle Zugangsdaten** (nur KNX IP Tunnelling Secure mit manuell eingetragenem Benutzer) oder **Keyring + manuelles Tunnel-Passwort** (Data-Secure-Schlüssel aus dem Keyring, Tunnel-Benutzer/-Passwort manuell). Achtung: KNX Data Secure Telegramme benötigen immer einen Keyring. |
23
+ | Tunnel interface individual address | Sichtbar, sobald die gewählte Option manuelle Zugangsdaten enthält (Manuelle Zugangsdaten oder Keyring + manuelles Tunnel-Passwort). Optionale KNX-Individualadresse der Tunnel-Schnittstelle (z. B. `1.1.1`); leer lassen, damit KNX Ultimate automatisch verhandelt. |
24
+ | Tunnel user ID | Sichtbar bei manuellen Zugangsdaten. Optionale Kennung des KNX Secure Tunnel-Benutzers aus ETS. |
25
+ | Tunnel user password | Sichtbar bei manuellen Zugangsdaten. Passwort des KNX Secure Tunnel-Benutzers aus ETS. |
26
+
27
+ > **KNX Secure im Überblick**
28
+ > • *KNX Data Secure* schützt Gruppenadress-Telegramme und benötigt stets eine Keyring-Datei mit den Gruppenschlüsseln.
29
+ > • *KNX IP Tunnelling Secure* schützt den Verbindungsaufbau mithilfe eines Commissioning-Passworts. Dieses kann – je nach Modus – aus dem Keyring kommen oder manuell eingetragen werden.
22
30
 
23
31
  <br/>
24
32
 
@@ -19,6 +19,14 @@
19
19
  | KNX Physical Address | The physical KNX address, example 1.1.200. Default is "15.15.22".|
20
20
  | Bind to local interface | The Node will use this local interface for communications. Leave "Auto" for automatic selection. If you have more than one lan connection, for example Ethernet and Wifi, it's strongly recommended to manually select the interface, otherwise not all UDP telegram will reach your computer, thus the Node may not work as expected. Default is "Auto". |
21
21
  | Automatically connect to KNX BUS at start | Auto connect to the bus at start. Default is "Yes". |
22
+ | Secure credentials source | Choose how KNX Secure data is provided: **ETS keyring file** (Data Secure keys and, if available, tunnelling credentials come from the keyring), **Manual credentials** (only KNX IP Tunnelling Secure with a manually entered user) or **Keyring + manual tunnel password** (Data Secure keys from the keyring while the secure tunnel uses the manual user/password). Remember: KNX Data Secure telegrams always require a keyring. |
23
+ | Tunnel interface individual address | Visible whenever the selected mode includes manual credentials (Manual or Keyring + manual tunnel password). Optional KNX individual address for the secure tunnel interface (for example `1.1.1`); leave empty to let KNX Ultimate negotiate it automatically. |
24
+ | Tunnel user ID | Visible when manual credentials are used. Optional KNX Secure tunnel user identifier defined in ETS. |
25
+ | Tunnel user password | Visible when manual credentials are used. Password of the secure tunnelling user configured in ETS. |
26
+
27
+ > **KNX Secure at a glance**
28
+ > • *KNX Data Secure* protects group-address telegrams and **always** requires a keyring file containing the group keys.
29
+ > • *KNX IP Tunnelling Secure* protects the connection handshake using a commissioning password. The password can come from the keyring or be entered manually depending on the selected mode.
22
30
 
23
31
  <br/>
24
32
 
@@ -82,6 +82,14 @@
82
82
  "youtubeKeyring": "See how to export the Keyring XML file on Youtube",
83
83
  "keyring_file": "Keyring file",
84
84
  "password": "Password",
85
+ "secure_credentials_mode": "Secure credentials source",
86
+ "secure_credentials_mode_keyring": "ETS keyring file",
87
+ "secure_credentials_mode_manual": "Manual credentials",
88
+ "secure_credentials_mode_combined": "Keyring + manual tunnel password",
89
+ "tunnel_interface_individual_address": "Tunnel interface individual address",
90
+ "tunnel_interface_individual_address_placeholder": "1.1.1",
91
+ "tunnel_user_id": "Tunnel user ID",
92
+ "tunnel_user_password": "Tunnel user password",
85
93
  "ga_list_label": "ETS group address list"
86
94
  },
87
95
  "utility": {
@@ -86,6 +86,14 @@
86
86
  "youtubeKeyring": "Guarda come si esporta il Keyring XML su YouTube",
87
87
  "keyring_file": "File Keyring",
88
88
  "password": "Password",
89
+ "secure_credentials_mode": "Origine credenziali KNX Secure",
90
+ "secure_credentials_mode_keyring": "File keyring ETS",
91
+ "secure_credentials_mode_manual": "Credenziali manuali",
92
+ "secure_credentials_mode_combined": "File keyring + password tunnel manuale",
93
+ "tunnel_interface_individual_address": "Indirizzo individuale interfaccia tunnel",
94
+ "tunnel_interface_individual_address_placeholder": "1.1.1",
95
+ "tunnel_user_id": "ID utente tunnel",
96
+ "tunnel_user_password": "Password utente tunnel",
89
97
  "ga_list_label": "Lista indirizzi di gruppo ETS"
90
98
  },
91
99
  "utility": {
@@ -19,6 +19,14 @@ Questo nodo si connette al tuo KNX/IP Gateway.
19
19
  | KNX Physical Address | Indirizzo fisico KNX, es. `1.1.200`. Default: `15.15.22`. |
20
20
  | Bind to local interface | Interfaccia di rete locale usata dal nodo. Lascia "Auto" per selezione automatica. Se hai più interfacce (Ethernet/Wi‑Fi), è consigliato impostarla manualmente per evitare perdita di telegrammi UDP. Default: "Auto". |
21
21
  | Automatically connect to KNX BUS at start | Connessione automatica al BUS all’avvio. Default: "Yes". |
22
+ | Secure credentials source | Scegli come fornire i dati KNX Secure: **File keyring ETS** (le chiavi Data Secure e, se presenti, le credenziali di tunnelling arrivano dal keyring), **Credenziali manuali** (solo KNX IP Tunnelling Secure con utente inserito a mano) oppure **File keyring + password tunnel manuale** (le chiavi Data Secure dal keyring e l’utente/password del tunnel impostati manualmente). Ricorda: i telegrammi KNX Data Secure richiedono sempre un keyring. |
23
+ | Tunnel interface individual address | Visibile quando la modalità selezionata prevede credenziali manuali (Credenziali manuali o File keyring + password tunnel manuale). Indirizzo individuale opzionale dell’interfaccia tunnel sicura (es. `1.1.1`); lascia vuoto per negoziazione automatica da parte di KNX Ultimate. |
24
+ | Tunnel user ID | Visibile quando sono attive credenziali manuali. ID opzionale dell’utente tunnel KNX Secure definito in ETS. |
25
+ | Tunnel user password | Visibile quando sono attive credenziali manuali. Password dell’utente tunnel KNX Secure configurata in ETS. |
26
+
27
+ > **Nota su KNX Secure**
28
+ > • *KNX Data Secure* protegge i telegrammi sugli indirizzi di gruppo e richiede sempre un file keyring con le chiavi di gruppo.
29
+ > • *KNX IP Tunnelling Secure* protegge l’handshake di connessione tramite una password di commissioning, che può essere letta dal keyring oppure inserita manualmente in base alla modalità scelta.
22
30
 
23
31
  <br/>
24
32
 
@@ -19,6 +19,14 @@
19
19
  | KNX Physical Address | 物理地址,如 `1.1.200`。默认 `15.15.22`。|
20
20
  | Bind to local interface | 使用的本地网络接口。"Auto" 自动选择。若有多网卡(以太网/无线),建议手动指定,避免 UDP 丢包。|
21
21
  | Automatically connect to KNX BUS at start | 启动时自动连接总线。默认 "Yes"。|
22
+ | Secure credentials source | 选择如何提供 KNX Secure 数据:**ETS 密钥环文件**(Data Secure 密钥以及—若存在—隧道凭据来自密钥环)、**手动凭据**(仅启用 KNX IP 安全隧道,手动输入用户)或 **密钥环 + 手动隧道密码**(Data Secure 由密钥环提供,隧道用户/密码手动输入)。注意:KNX Data Secure 报文始终需要密钥环文件。|
23
+ | Tunnel interface individual address | 当所选模式包含手动凭据时显示(手动凭据或密钥环 + 手动隧道密码)。可选的安全隧道 KNX 个人地址(如 `1.1.1`);留空则由 KNX Ultimate 自动协商。|
24
+ | Tunnel user ID | 启用手动凭据时显示。可选的 KNX Secure 隧道用户 ID(在 ETS 中配置)。|
25
+ | Tunnel user password | 启用手动凭据时显示。输入 ETS 中配置的 KNX Secure 隧道用户密码。|
26
+
27
+ > **KNX Secure 概览**
28
+ > • *KNX Data Secure* 用于保护组地址报文,**始终** 需要包含组密钥的密钥环文件。
29
+ > • *KNX IP Tunnelling Secure* 通过调试密码保护连接握手,密码可以根据模式从密钥环中读取,或在界面中手动输入。
22
30
 
23
31
  <br/>
24
32
 
@@ -81,6 +81,14 @@
81
81
  "youtubeKeyring": "在 Youtube 上了解如何导出 Keyring XML 文件",
82
82
  "keyring_file": "Keyring 文件",
83
83
  "password": "密码",
84
+ "secure_credentials_mode": "安全凭据来源",
85
+ "secure_credentials_mode_keyring": "ETS 密钥环文件",
86
+ "secure_credentials_mode_manual": "手动凭据",
87
+ "secure_credentials_mode_combined": "密钥环 + 手动隧道密码",
88
+ "tunnel_interface_individual_address": "隧道接口个人地址",
89
+ "tunnel_interface_individual_address_placeholder": "1.1.1",
90
+ "tunnel_user_id": "隧道用户 ID",
91
+ "tunnel_user_password": "隧道用户密码",
84
92
  "ga_list_label": "ETS 组地址列表"
85
93
  },
86
94
  "utility": {
package/package.json CHANGED
@@ -3,14 +3,14 @@
3
3
  "engines": {
4
4
  "node": ">=20.18.1"
5
5
  },
6
- "version": "4.0.3",
6
+ "version": "4.0.5",
7
7
  "description": "Control your KNX and KNX Secure intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.",
8
8
  "dependencies": {
9
9
  "binary-parser": "2.2.1",
10
10
  "crypto-js": "4.2.0",
11
11
  "dns-sync": "0.2.1",
12
12
  "js-yaml": "4.1.0",
13
- "knxultimate": "5.0.0",
13
+ "knxultimate": "5.1.1",
14
14
  "lodash": "4.17.21",
15
15
  "mkdirp": "3.0.1",
16
16
  "node-color-log": "12.0.1",