node-red-contrib-knx-ultimate 4.0.4 → 4.0.6

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.6** - September 2025<br/>
10
+ - Fixed regression in automatic datapoint selection, based on group address DPT.<br/>
11
+
12
+ **Version 4.0.5** - September 2025<br/>
13
+ - NEW: You have now the choide to select multiple modes to authenticate to a KNX Gateway.<br/>
14
+
9
15
  **Version 4.0.4** - September 2025<br/>
10
16
  - NEW: you can now login to your secure gateway using only tunnel and password, without the keyring file.<br/>
11
17
 
@@ -25,11 +25,12 @@
25
25
  tunnelIASelection: { value: "Auto" },
26
26
  tunnelIA: { value: "" },
27
27
  tunnelInterfaceIndividualAddress: { value: "" },
28
+ tunnelUserPassword: { value: "" },
29
+ tunnelUserId: { value: "" },
28
30
  autoReconnect: { value: "yes" }
29
31
  },
30
32
  credentials: {
31
- keyringFilePassword: { type: "password" },
32
- tunnelUserPassword: { type: "password" }
33
+ keyringFilePassword: { type: "password" }
33
34
  },
34
35
  oneditprepare: function () {
35
36
  // Go to the help panel
@@ -71,13 +72,13 @@
71
72
  active: (node.knxSecureSelected === true || node.knxSecureSelected === 'true') ? 1 : 0,
72
73
  activate: function (event, ui) {
73
74
  node.knxSecureSelected = $(ui.newTab).index() === 1;
74
- try { updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); } catch (e) { }
75
+ try { updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); } catch (e) { }
75
76
  }
76
77
  });
77
78
  // Ensure the correct tab is enforced after all UI init
78
79
  try {
79
80
  const initialActive = (node.knxSecureSelected === true || node.knxSecureSelected === 'true') ? 1 : 0;
80
- setTimeout(() => { $("#tabsMain").tabs("option", "active", initialActive); updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); }, 0);
81
+ setTimeout(() => { $("#tabsMain").tabs("option", "active", initialActive); updateTunnelIAVisibility(); updatePhysAddrVisibility(); updateSecureMulticastHint(); enforceProtocolFromIP(); }, 0);
81
82
  } catch (e) { }
82
83
 
83
84
  // Keyring ACE editor setup
@@ -110,17 +111,85 @@
110
111
  }
111
112
  }
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
+
113
180
  function updateSecureCredentialMode() {
114
181
  try {
115
182
  const mode = getSecureCredentialsMode();
116
- const isKeyring = (mode === 'keyring');
117
- $("#secureKeyringFields").toggle(isKeyring);
118
- if (!isKeyring) {
183
+ const showKeyringFields = (mode === 'keyring' || mode === 'combined');
184
+ const showKeyringIAControls = (mode === 'keyring');
185
+ $("#secureKeyringFields").toggle(showKeyringFields);
186
+ if (!showKeyringIAControls) {
119
187
  $("#rowTunnelIASelection").hide();
120
188
  $("#divTunnelIA").hide();
121
189
  }
122
190
  } catch (e) { }
123
191
  updateTunnelIAVisibility();
192
+ enforceProtocolFromIP();
124
193
  }
125
194
 
126
195
  // Helper: show/hide Tunnel IA controls depending on mode
@@ -131,7 +200,9 @@
131
200
  const ip = $("#node-config-input-host").val();
132
201
  const isMulticast = (hostProtocol === 'Multicast') || (ip === '224.0.23.12');
133
202
  const mode = getSecureCredentialsMode();
134
- const showTunnelIA = isSecure && !isMulticast && mode === 'keyring';
203
+ const keyringMode = (mode === 'keyring');
204
+ const manualMode = (mode === 'manual' || mode === 'combined');
205
+ const showTunnelIA = isSecure && !isMulticast && keyringMode;
135
206
  if (showTunnelIA) {
136
207
  $("#rowTunnelIASelection").show();
137
208
  if ($("#node-config-input-tunnelIASelection").val() === 'Manual') {
@@ -143,9 +214,10 @@
143
214
  $("#rowTunnelIASelection").hide();
144
215
  $("#divTunnelIA").hide();
145
216
  }
146
- const showManualTunnelFields = isSecure && !isMulticast && mode === 'manual';
217
+ const showManualTunnelFields = isSecure && !isMulticast && manualMode;
147
218
  $("#rowTunnelInterfaceIndividualAddress").toggle(showManualTunnelFields);
148
219
  $("#rowTunnelUserPassword").toggle(showManualTunnelFields);
220
+ $("#rowTunnelUserId").toggle(showManualTunnelFields);
149
221
  if (!showManualTunnelFields) {
150
222
  try {
151
223
  $("#node-config-input-tunnelInterfaceIndividualAddress").removeClass('input-error');
@@ -237,7 +309,9 @@
237
309
  let aTunnelIAs = [];
238
310
  function populateTunnelIAList() {
239
311
  try {
240
- if (getSecureCredentialsMode() !== 'keyring') {
312
+ const mode = getSecureCredentialsMode();
313
+ const keyringMode = (mode === 'keyring');
314
+ if (!keyringMode) {
241
315
  aTunnelIAs = [];
242
316
  try {
243
317
  const $ia = $("#node-config-input-tunnelIA");
@@ -275,8 +349,14 @@
275
349
 
276
350
  // Secure: Tunnel IA selection
277
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);
278
358
  $("#node-config-input-tunnelIASelection").val(typeof node.tunnelIASelection === 'undefined' ? 'Auto' : node.tunnelIASelection);
279
- $("#node-config-input-tunnelIA").val(typeof node.tunnelIA === 'undefined' ? '' : node.tunnelIA);
359
+ $("#node-config-input-tunnelIA").on('change input', syncTunnelIAFromKeyring);
280
360
  const toggleTunnelIA = () => {
281
361
  if ($("#node-config-input-tunnelIASelection").val() === 'Manual') {
282
362
  $("#divTunnelIA").show();
@@ -299,8 +379,8 @@
299
379
  $("#node-config-input-tunnelIA").autocomplete({
300
380
  minLength: 0,
301
381
  source: function (request, response) { response(aTunnelIAs); },
302
- focus: function (event, ui) { $(this).val(ui.item.value); return false; },
303
- 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; }
304
384
  }).on('focus click', function () {
305
385
  // Show dropdown on focus/click
306
386
  $(this).autocomplete('search', '');
@@ -310,7 +390,9 @@
310
390
 
311
391
  try {
312
392
  $("#node-config-input-secureCredentialsMode").val(typeof node.secureCredentialsMode === 'undefined' ? 'keyring' : node.secureCredentialsMode);
313
- $("#node-config-input-tunnelInterfaceIndividualAddress").val(typeof node.tunnelInterfaceIndividualAddress === 'undefined' ? '' : node.tunnelInterfaceIndividualAddress);
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);
314
396
  $("#node-config-input-secureCredentialsMode").on('change', function () {
315
397
  updateSecureCredentialMode();
316
398
  updateSecureMulticastHint();
@@ -508,9 +590,7 @@
508
590
  // Abilita manualmente la lista dei protocolli quando l'utente modifica l'IP a mano
509
591
  try {
510
592
  $("#node-config-input-host").on('input', function () {
511
- const isSecure = (function () {
512
- try { return $("#tabsMain").tabs("option", "active") === 1; } catch (e) { return !!node.knxSecureSelected; }
513
- })();
593
+ const isSecure = isSecureTabActive();
514
594
  const $sel = $("#node-config-input-hostProtocol");
515
595
  // Abilita select
516
596
  $sel.prop('disabled', false);
@@ -548,6 +628,8 @@
548
628
  }
549
629
  // If discovery knows protocol, preselect it (keep select enabled for manual override)
550
630
  if (proto) { $("#node-config-input-hostProtocol").val(proto); }
631
+ const octets = parseIPv4Address(ipTyped);
632
+ if (octets && !isMulticastIPv4(octets)) enforceProtocolFromIP();
551
633
  }
552
634
  } catch (e) { }
553
635
  });
@@ -614,6 +696,9 @@
614
696
  node._csvEditor.destroy();
615
697
  node._csvEditor = null;
616
698
  }
699
+ try {
700
+ $("#node-config-input-tunnelIA").val($("#node-config-input-tunnelInterfaceIndividualAddress").val());
701
+ } catch (e) { }
617
702
  } catch (e) { }
618
703
  // Check if the csv file contains errors
619
704
  if (($("#node-config-input-csv").val() != 'undefined' && $("#node-config-input-csv").val() != "") || ($("#node-config-input-keyring").val() != 'undefined' && $("#node-config-input-keyring").val() != "")) {
@@ -734,6 +819,7 @@
734
819
  <select id="node-config-input-secureCredentialsMode" style="width:200px;">
735
820
  <option value="keyring" data-i18n="knxUltimate-config.ets.secure_credentials_mode_keyring"></option>
736
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>
737
823
  </select>
738
824
  </div>
739
825
  <div id="secureKeyringFields">
@@ -762,9 +848,13 @@
762
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>
763
849
  <input type="text" id="node-config-input-tunnelInterfaceIndividualAddress" style="width:150px;" data-i18n="[placeholder]knxUltimate-config.ets.tunnel_interface_individual_address_placeholder"/>
764
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>
765
855
  <div class="form-row" id="rowTunnelUserPassword" style="display:none;">
766
856
  <label for="node-config-input-tunnelUserPassword"><i class="fa fa-lock"></i> <span data-i18n="knxUltimate-config.ets.tunnel_user_password"></span></label>
767
- <input type="password" id="node-config-input-tunnelUserPassword" style="width:200px;" />
857
+ <input type="text" id="node-config-input-tunnelUserPassword" style="width:200px;" />
768
858
  </div>
769
859
  </p>
770
860
  </div>
@@ -101,8 +101,24 @@ module.exports = (RED) => {
101
101
  node.secureCredentialsMode = typeof config.secureCredentialsMode === "undefined" ? "keyring" : config.secureCredentialsMode;
102
102
  // 2025-09 Secure Tunnel Interface IA selection (Auto/Manual)
103
103
  node.tunnelIASelection = typeof config.tunnelIASelection === "undefined" ? "Auto" : config.tunnelIASelection;
104
- node.tunnelIA = typeof config.tunnelIA === "undefined" ? "" : config.tunnelIA;
105
- node.tunnelInterfaceIndividualAddress = typeof config.tunnelInterfaceIndividualAddress === "undefined" ? "" : config.tunnelInterfaceIndividualAddress;
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;
106
122
  node.name = config.name === undefined || config.name === "" ? node.host : config.name; // 12/08/2021
107
123
  node.timerKNXUltimateCheckState = null; // 08/10/2021 Check the state. If not connected and autoreconnect is true, retrig the connetion attempt.
108
124
  node.knxConnectionProperties = null; // Retains the connection properties
@@ -128,7 +144,8 @@ module.exports = (RED) => {
128
144
  ) {
129
145
  node.hostProtocol = "Multicast";
130
146
  } else {
131
- node.hostProtocol = "TunnelUDP";
147
+ const isSecure = node.knxSecureSelected === true || node.knxSecureSelected === "true";
148
+ node.hostProtocol = isSecure ? "TunnelTCP" : "TunnelUDP";
132
149
  }
133
150
  node.sysLogger?.info("IP Protocol AUTO SET to " + node.hostProtocol + ", based on IP " + node.host);
134
151
  }
@@ -161,29 +178,40 @@ module.exports = (RED) => {
161
178
  try {
162
179
  if (node.knxSecureSelected) {
163
180
  const secureMode = typeof node.secureCredentialsMode === "string" ? node.secureCredentialsMode : "keyring";
164
- if (secureMode === "manual") {
165
- // Manual credentials: no keyring, only IA + password
166
- node.secureTunnelConfig = {
167
- tunnelInterfaceIndividualAddress: (node.tunnelInterfaceIndividualAddress || "").trim(),
168
- tunnelUserPassword: node.credentials?.tunnelUserPassword || "",
169
- };
170
- if (!node.secureTunnelConfig.tunnelInterfaceIndividualAddress) {
171
- delete node.secureTunnelConfig.tunnelInterfaceIndividualAddress;
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;
172
194
  }
173
- RED.log.info("KNX-Secure: secure mode selected. Using manual tunnel credentials.");
174
- } else {
175
- // Prepare secure config for KNXClient. The loader accepts either a
176
- // filesystem path to .knxkeys or the raw XML/base64 content.
177
- node.secureTunnelConfig = {
178
- knxkeys_file_path: node.keyringFileXML || "",
179
- knxkeys_password: node.credentials?.keyringFilePassword || "",
180
- };
181
- // Manual IA selection based on keyring content
195
+ if (manualUserId) {
196
+ secureConfig.tunnelUserId = manualUserId;
197
+ }
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 || "";
206
+
182
207
  try {
183
- if (node.tunnelIASelection === "Manual" && typeof node.tunnelIA === "string" && node.tunnelIA.trim() !== "") {
184
- node.secureTunnelConfig.tunnelInterfaceIndividualAddress = node.tunnelIA.trim();
185
- } else {
186
- node.secureTunnelConfig.tunnelInterfaceIndividualAddress = ""; // Auto
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)
187
215
  }
188
216
  } catch (e) { /* empty */ }
189
217
 
@@ -199,9 +227,18 @@ module.exports = (RED) => {
199
227
  node.sysLogger?.error("KNX Secure: keyring validation failed: " + err.message);
200
228
  // Keep secure enabled: KNXClient will emit detailed errors on connect
201
229
  }
202
- } else {
203
- RED.log.info("KNX-Secure: secure mode selected. Using provided keyring and password.");
204
230
  }
231
+ modeParts.push("keyring file/password");
232
+ }
233
+
234
+ if (Object.keys(secureConfig).length > 0) {
235
+ node.secureTunnelConfig = secureConfig;
236
+ } else {
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}`);
205
242
  }
206
243
  } else {
207
244
  RED.log.info("KNX-Unsecure: connection to insecure interface/router using node " + (node.name || node.id));
@@ -765,7 +802,8 @@ module.exports = (RED) => {
765
802
  _dest = _datagram.cEMIMessage.dstAddress.toString();
766
803
 
767
804
  if (_evt === null || _src === null || _dest === null) {
768
- 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;
769
807
  };
770
808
 
771
809
  _echoed = _echoed || false;
@@ -1494,7 +1532,7 @@ module.exports = (RED) => {
1494
1532
  try {
1495
1533
  jsValue = dptlib.fromBuffer(_Rawvalue, dpt);
1496
1534
  if (jsValue === null) {
1497
- node.sysLogger?.error(
1535
+ node.sysLogger?.warn(
1498
1536
  "buildInputMessage: received a wrong datagram form KNX BUS, from device " +
1499
1537
  _srcGA +
1500
1538
  " Destination " +
@@ -417,9 +417,12 @@
417
417
  try { sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim(); } catch (error) { }
418
418
  $('#node-input-name').val(sDevName);
419
419
  var optVal = $("#node-input-dpt option:contains('" + (ui.item.dpt || '').trim() + "')").attr('value');
420
- // Select the option value
421
- $("#node-input-dpt").val(optVal);
422
- $("#node-input-dpt").trigger("change");
420
+ var $dptSelect = $("#node-input-dpt");
421
+ if (optVal !== undefined && optVal !== null) {
422
+ $dptSelect.val(optVal).trigger('change');
423
+ } else {
424
+ $dptSelect.trigger('change');
425
+ }
423
426
  // Persist secure flag for dynamic icon
424
427
  try {
425
428
  $("#node-input-gaSecure").val(ui.item.isSecure ? 'true' : 'false');
@@ -946,4 +949,4 @@
946
949
  </div>
947
950
  </div>
948
951
  <br /><br />
949
- </script>
952
+ </script>
@@ -106,8 +106,12 @@
106
106
  }
107
107
  $('#node-input-namebatterysensor').val(sDevName);
108
108
  var optVal = $("#node-input-dptbatterysensor option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
109
- // Select the option value
110
- $("#node-input-dptbatterysensor").val(optVal);
109
+ var $dptSelect = $("#node-input-dptbatterysensor");
110
+ if (optVal !== undefined && optVal !== null) {
111
+ $dptSelect.val(optVal).trigger('change');
112
+ } else {
113
+ $dptSelect.trigger('change');
114
+ }
111
115
  }
112
116
  }).focus(function () {
113
117
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -243,4 +247,3 @@
243
247
 
244
248
  </script>
245
249
 
246
-
@@ -144,8 +144,12 @@
144
144
  }
145
145
  $('#node-input-nameDim').val(sDevName);
146
146
  var optVal = $("#node-input-dptrepeat option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
147
- // Select the option value
148
- $("#node-input-dptrepeat").val(optVal);
147
+ var $dptSelect = $("#node-input-dptrepeat");
148
+ if (optVal !== undefined && optVal !== null) {
149
+ $dptSelect.val(optVal).trigger('change');
150
+ } else {
151
+ $dptSelect.trigger('change');
152
+ }
149
153
  }
150
154
  }).focus(function () {
151
155
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -194,8 +198,12 @@
194
198
  }
195
199
  $('#node-input-nameshort_release').val(sDevName);
196
200
  var optVal = $("#node-input-dptshort_release option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
197
- // Select the option value
198
- $("#node-input-dptshort_release").val(optVal);
201
+ var $dptSelect = $("#node-input-dptshort_release");
202
+ if (optVal !== undefined && optVal !== null) {
203
+ $dptSelect.val(optVal).trigger('change');
204
+ } else {
205
+ $dptSelect.trigger('change');
206
+ }
199
207
  }
200
208
  }).focus(function () {
201
209
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -227,8 +235,12 @@
227
235
  }
228
236
  $('#node-input-nameshort_releaseStatus').val(sDevName);
229
237
  var optVal = $("#node-input-dptshort_releaseStatus option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
230
- // Select the option value
231
- $("#node-input-dptshort_releaseStatus").val(optVal);
238
+ var $dptSelect = $("#node-input-dptshort_releaseStatus");
239
+ if (optVal !== undefined && optVal !== null) {
240
+ $dptSelect.val(optVal).trigger('change');
241
+ } else {
242
+ $dptSelect.trigger('change');
243
+ }
232
244
  }
233
245
  }).focus(function () {
234
246
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -261,8 +273,12 @@
261
273
  }
262
274
  $('#node-input-namedouble_short_release').val(sDevName);
263
275
  var optVal = $("#node-input-dptdouble_short_release option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
264
- // Select the option value
265
- $("#node-input-dptdouble_short_release").val(optVal);
276
+ var $dptSelect = $("#node-input-dptdouble_short_release");
277
+ if (optVal !== undefined && optVal !== null) {
278
+ $dptSelect.val(optVal).trigger('change');
279
+ } else {
280
+ $dptSelect.trigger('change');
281
+ }
266
282
  }
267
283
  }).focus(function () {
268
284
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -294,8 +310,12 @@
294
310
  }
295
311
  $('#node-input-namedouble_short_releaseStatus').val(sDevName);
296
312
  var optVal = $("#node-input-dptdouble_short_releaseStatus option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
297
- // Select the option value
298
- $("#node-input-dptdouble_short_releaseStatus").val(optVal);
313
+ var $dptSelect = $("#node-input-dptdouble_short_releaseStatus");
314
+ if (optVal !== undefined && optVal !== null) {
315
+ $dptSelect.val(optVal).trigger('change');
316
+ } else {
317
+ $dptSelect.trigger('change');
318
+ }
299
319
  }
300
320
  }).focus(function () {
301
321
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -460,5 +480,3 @@
460
480
 
461
481
 
462
482
  </script>
463
-
464
-
@@ -115,8 +115,12 @@
115
115
  }
116
116
  $('#node-input-namecontact').val(sDevName)
117
117
  var optVal = $('#node-input-dptcontact option:contains(\'' + ui.item.label.split('#')[2].trim() + '\')').attr('value')
118
- // Select the option value
119
- $('#node-input-dptcontact').val(optVal)
118
+ var $dptSelect = $('#node-input-dptcontact')
119
+ if (optVal !== undefined && optVal !== null) {
120
+ $dptSelect.val(optVal).trigger('change')
121
+ } else {
122
+ $dptSelect.trigger('change')
123
+ }
120
124
  },
121
125
  }).focus(function () {
122
126
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -241,4 +245,3 @@
241
245
  <br />
242
246
  </script>
243
247
 
244
-
@@ -233,8 +233,13 @@
233
233
  } catch (error) { }
234
234
  $(_destinationWidgetName).val(sDevName);
235
235
  var optVal = $(_destinationWidgetDPT + " option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr("value");
236
- // Select the option value
237
- $(_destinationWidgetDPT).val(optVal);
236
+ const $dptSelect = $(_destinationWidgetDPT);
237
+ if (optVal !== undefined && optVal !== null) {
238
+ $dptSelect.val(optVal).trigger('change');
239
+ } else {
240
+ // Ensure downstream widgets refresh even when the DPT is missing
241
+ $dptSelect.trigger('change');
242
+ }
238
243
  },
239
244
  }).focus(function () {
240
245
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -1485,4 +1490,3 @@
1485
1490
  </script>
1486
1491
 
1487
1492
 
1488
-
@@ -105,8 +105,12 @@
105
105
  }
106
106
  $('#node-input-namelightsensor').val(sDevName);
107
107
  var optVal = $("#node-input-dptlightsensor option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
108
- // Select the option value
109
- $("#node-input-dptlightsensor").val(optVal);
108
+ var $dptSelect = $("#node-input-dptlightsensor");
109
+ if (optVal !== undefined && optVal !== null) {
110
+ $dptSelect.val(optVal).trigger('change');
111
+ } else {
112
+ $dptSelect.trigger('change');
113
+ }
110
114
  }
111
115
  }).focus(function () {
112
116
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -245,4 +249,3 @@
245
249
  </script>
246
250
 
247
251
 
248
-
@@ -104,8 +104,12 @@
104
104
  }
105
105
  $('#node-input-namemotion').val(sDevName);
106
106
  var optVal = $("#node-input-dptmotion option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
107
- // Select the option value
108
- $("#node-input-dptmotion").val(optVal);
107
+ var $dptSelect = $("#node-input-dptmotion");
108
+ if (optVal !== undefined && optVal !== null) {
109
+ $dptSelect.val(optVal).trigger('change');
110
+ } else {
111
+ $dptSelect.trigger('change');
112
+ }
109
113
  }
110
114
  }).focus(function () {
111
115
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -233,4 +237,3 @@
233
237
 
234
238
  </script>
235
239
 
236
-
@@ -147,8 +147,12 @@
147
147
  }
148
148
  $('#node-input-namescene').val(sDevName);
149
149
  var optVal = $("#node-input-dptscene option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
150
- // Select the option value
151
- $("#node-input-dptscene").val(optVal);
150
+ var $dptSelect = $("#node-input-dptscene");
151
+ if (optVal !== undefined && optVal !== null) {
152
+ $dptSelect.val(optVal).trigger('change');
153
+ } else {
154
+ $dptSelect.trigger('change');
155
+ }
152
156
  ShowHideValScene();
153
157
  }
154
158
  }).focus(function () {
@@ -184,8 +188,12 @@
184
188
  }
185
189
  $('#node-input-namesceneStatus').val(sDevName);
186
190
  var optVal = $("#node-input-dptsceneStatus option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
187
- // Select the option value
188
- $("#node-input-dptsceneStatus").val(optVal);
191
+ var $dptSelect = $("#node-input-dptsceneStatus");
192
+ if (optVal !== undefined && optVal !== null) {
193
+ $dptSelect.val(optVal).trigger('change');
194
+ } else {
195
+ $dptSelect.trigger('change');
196
+ }
189
197
  ShowHideValScene();
190
198
  }
191
199
  }).focus(function () {
@@ -221,8 +229,12 @@
221
229
  }
222
230
  $('#node-input-namesceneMulti').val(sDevName);
223
231
  var optVal = $("#node-input-dptsceneMulti option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
224
- // Select the option value
225
- $("#node-input-dptsceneMulti").val(optVal);
232
+ var $dptSelect = $("#node-input-dptsceneMulti");
233
+ if (optVal !== undefined && optVal !== null) {
234
+ $dptSelect.val(optVal).trigger('change');
235
+ } else {
236
+ $dptSelect.trigger('change');
237
+ }
226
238
  ShowHideValScene();
227
239
  }
228
240
  }).focus(function () {
@@ -589,5 +601,3 @@
589
601
  </div> <!-- // End TABS -->
590
602
 
591
603
  </script>
592
-
593
-
@@ -107,8 +107,12 @@
107
107
  }
108
108
  $('#node-input-namerepeat').val(sDevName);
109
109
  var optVal = $("#node-input-dptrepeat option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
110
- // Select the option value
111
- $("#node-input-dptrepeat").val(optVal);
110
+ var $dptSelect = $("#node-input-dptrepeat");
111
+ if (optVal !== undefined && optVal !== null) {
112
+ $dptSelect.val(optVal).trigger('change');
113
+ } else {
114
+ $dptSelect.trigger('change');
115
+ }
112
116
  }
113
117
  }).focus(function () {
114
118
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -235,4 +239,3 @@
235
239
 
236
240
  </script>
237
241
 
238
-
@@ -105,8 +105,12 @@
105
105
  }
106
106
  $('#node-input-nametemperaturesensor').val(sDevName);
107
107
  var optVal = $("#node-input-dpttemperaturesensor option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
108
- // Select the option value
109
- $("#node-input-dpttemperaturesensor").val(optVal);
108
+ var $dptSelect = $("#node-input-dpttemperaturesensor");
109
+ if (optVal !== undefined && optVal !== null) {
110
+ $dptSelect.val(optVal).trigger('change');
111
+ } else {
112
+ $dptSelect.trigger('change');
113
+ }
110
114
  }
111
115
  }).focus(function () {
112
116
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -243,4 +247,3 @@
243
247
 
244
248
  </script>
245
249
 
246
-
@@ -106,8 +106,12 @@
106
106
  }
107
107
  $('#node-input-namezigbeeconnectivity').val(sDevName);
108
108
  var optVal = $("#node-input-dptzigbeeconnectivity option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
109
- // Select the option value
110
- $("#node-input-dptzigbeeconnectivity").val(optVal);
109
+ var $dptSelect = $("#node-input-dptzigbeeconnectivity");
110
+ if (optVal !== undefined && optVal !== null) {
111
+ $dptSelect.val(optVal).trigger('change');
112
+ } else {
113
+ $dptSelect.trigger('change');
114
+ }
111
115
  }
112
116
  }).focus(function () {
113
117
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -243,4 +247,3 @@
243
247
 
244
248
  </script>
245
249
 
246
-
@@ -106,8 +106,12 @@
106
106
  }
107
107
  $('#node-input-namedevice_software_update').val(sDevName);
108
108
  var optVal = $("#node-input-dptdevice_software_update option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
109
- // Select the option value
110
- $("#node-input-dptdevice_software_update").val(optVal);
109
+ var $dptSelect = $("#node-input-dptdevice_software_update");
110
+ if (optVal !== undefined && optVal !== null) {
111
+ $dptSelect.val(optVal).trigger('change');
112
+ } else {
113
+ $dptSelect.trigger('change');
114
+ }
111
115
  }
112
116
  }).focus(function () {
113
117
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -243,4 +247,3 @@
243
247
 
244
248
  </script>
245
249
 
246
-
@@ -141,9 +141,12 @@
141
141
  }
142
142
 
143
143
  var optVal = $("#node-input-dpt option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
144
- // Select the option value
145
- $("#node-input-" + _DPT).val(optVal);
146
- $("#node-input-" + _DPT).trigger("change");
144
+ var $dptSelect = $("#node-input-" + _DPT);
145
+ if (optVal !== undefined && optVal !== null) {
146
+ $dptSelect.val(optVal).trigger('change');
147
+ } else {
148
+ $dptSelect.trigger('change');
149
+ }
147
150
  }
148
151
  };
149
152
  return paramAutoComplete;
@@ -358,4 +361,3 @@
358
361
  </br>
359
362
  </script>
360
363
 
361
-
@@ -97,9 +97,12 @@
97
97
  }
98
98
  $('#node-input-name').val("Recall: " + sDevName + "/" + $('#node-input-name').val().split("/")[1]);
99
99
  var optVal = $("#node-input-dpt option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
100
- // Select the option value
101
- $("#node-input-dpt").val(optVal);
102
- $("#node-input-dpt").trigger("change");
100
+ var $dptSelect = $("#node-input-dpt");
101
+ if (optVal !== undefined && optVal !== null) {
102
+ $dptSelect.val(optVal).trigger('change');
103
+ } else {
104
+ $dptSelect.trigger('change');
105
+ }
103
106
  }
104
107
  }).focus(function () {
105
108
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -222,9 +225,12 @@
222
225
  }
223
226
  $('#node-input-name').val($('#node-input-name').val().split("/")[0] + "/Save: " + sDevName);
224
227
  var optVal = $("#node-input-dptSave option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
225
- // Select the option value
226
- $("#node-input-dptSave").val(optVal);
227
- $("#node-input-dptSave").trigger("change");
228
+ var $dptSelect = $("#node-input-dptSave");
229
+ if (optVal !== undefined && optVal !== null) {
230
+ $dptSelect.val(optVal).trigger('change');
231
+ } else {
232
+ $dptSelect.trigger('change');
233
+ }
228
234
  }
229
235
  }).focus(function () {
230
236
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -363,7 +369,12 @@
363
369
  } catch (error) {
364
370
  }
365
371
  var optVal = $(".rowRuleDPT option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
366
- oDPTField.val(optVal);
372
+ var $dptSelect = oDPTField;
373
+ if (optVal !== undefined && optVal !== null) {
374
+ $dptSelect.val(optVal).trigger('change');
375
+ } else {
376
+ $dptSelect.trigger('change');
377
+ }
367
378
  }
368
379
  }).focus(function () {
369
380
  $(this).autocomplete('search', $(this).val() + 'exactmatch');
@@ -493,5 +504,3 @@
493
504
  </div>
494
505
 
495
506
  </script>
496
-
497
-
@@ -88,8 +88,10 @@
88
88
  "secure_credentials_mode": "Quelle für Secure-Zugangsdaten",
89
89
  "secure_credentials_mode_keyring": "ETS-Keyring-Datei",
90
90
  "secure_credentials_mode_manual": "Manuelle Zugangsdaten",
91
+ "secure_credentials_mode_combined": "Keyring + manuelles Tunnel-Passwort",
91
92
  "tunnel_interface_individual_address": "Individuelle Adresse der Tunnel-Schnittstelle",
92
93
  "tunnel_interface_individual_address_placeholder": "1.1.1",
94
+ "tunnel_user_id": "Tunnel-Benutzer-ID",
93
95
  "tunnel_user_password": "Tunnel-Benutzerpasswort",
94
96
  "ga_list_label": "ETS-Gruppenadressliste"
95
97
  },
@@ -19,9 +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 | Wähle **ETS-Keyring-Datei** (Standard), um den Inhalt der `.knxkeys`-Datei samt Passwort bereitzustellen, oder **Manuelle Zugangsdaten**, um Tunnel-IA und Passwort ohne Keyring zu hinterlegen. Je nach Auswahl werden nur die passenden Eingabefelder angezeigt. |
23
- | Tunnel interface individual address | Sichtbar bei **Manuelle Zugangsdaten**. Optionale KNX-Individualadresse der sicheren Tunnel-Schnittstelle (z. B. `1.1.1`). Leer lassen, damit KNX Ultimate sie automatisch aushandelt. |
24
- | Tunnel user password | Sichtbar bei **Manuelle Zugangsdaten**. Passwort des sicheren Tunnel-Benutzers aus ETS. |
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.
25
30
 
26
31
  <br/>
27
32
 
@@ -19,9 +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 | Select **ETS keyring file** (default) to provide the `.knxkeys` content and password, or **Manual credentials** to enter the tunnel interface IA and password without a keyring. Switching mode shows only the relevant fields. |
23
- | Tunnel interface individual address | Visible when **Manual credentials** is selected. Optional KNX individual address of the secure tunnel interface (for example `1.1.1`). Leave empty to let KNX Ultimate negotiate it. |
24
- | Tunnel user password | Visible when **Manual credentials** is selected. Password of the secure tunnelling user configured in ETS. |
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.
25
30
 
26
31
  <br/>
27
32
 
@@ -85,8 +85,10 @@
85
85
  "secure_credentials_mode": "Secure credentials source",
86
86
  "secure_credentials_mode_keyring": "ETS keyring file",
87
87
  "secure_credentials_mode_manual": "Manual credentials",
88
+ "secure_credentials_mode_combined": "Keyring + manual tunnel password",
88
89
  "tunnel_interface_individual_address": "Tunnel interface individual address",
89
90
  "tunnel_interface_individual_address_placeholder": "1.1.1",
91
+ "tunnel_user_id": "Tunnel user ID",
90
92
  "tunnel_user_password": "Tunnel user password",
91
93
  "ga_list_label": "ETS group address list"
92
94
  },
@@ -89,8 +89,10 @@
89
89
  "secure_credentials_mode": "Origine credenziali KNX Secure",
90
90
  "secure_credentials_mode_keyring": "File keyring ETS",
91
91
  "secure_credentials_mode_manual": "Credenziali manuali",
92
+ "secure_credentials_mode_combined": "File keyring + password tunnel manuale",
92
93
  "tunnel_interface_individual_address": "Indirizzo individuale interfaccia tunnel",
93
94
  "tunnel_interface_individual_address_placeholder": "1.1.1",
95
+ "tunnel_user_id": "ID utente tunnel",
94
96
  "tunnel_user_password": "Password utente tunnel",
95
97
  "ga_list_label": "Lista indirizzi di gruppo ETS"
96
98
  },
@@ -19,9 +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 **File keyring ETS** (default) per fornire il contenuto `.knxkeys` e la password, oppure **Credenziali manuali** per usare indirizzo individuale e password del tunnel senza keyring. Il cambio modalità mostra solo i campi pertinenti. |
23
- | Tunnel interface individual address | Visibile solo con **Credenziali manuali**. Indirizzo individuale dell’interfaccia tunnel sicura (es. `1.1.1`). Lascia vuoto per negoziazione automatica da parte di KNX Ultimate. |
24
- | Tunnel user password | Visibile solo con **Credenziali manuali**. Password dell’utente di tunnelling sicuro configurato in ETS. |
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.
25
30
 
26
31
  <br/>
27
32
 
@@ -19,9 +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 | 选择 **ETS 密钥环文件**(默认)以提供 `.knxkeys` 内容与密码,或选择 **手动凭据** 直接输入隧道 IA 和密码而无需密钥环。切换后仅显示相关字段。|
23
- | Tunnel interface individual address | 仅在 **手动凭据** 模式下显示。可填写安全隧道接口的 KNX 个人地址(如 `1.1.1`),留空则由 KNX Ultimate 自动协商。|
24
- | Tunnel user password | 仅在 **手动凭据** 模式下显示。输入 ETS 中配置的安全隧道用户密码。|
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* 通过调试密码保护连接握手,密码可以根据模式从密钥环中读取,或在界面中手动输入。
25
30
 
26
31
  <br/>
27
32
 
@@ -84,8 +84,10 @@
84
84
  "secure_credentials_mode": "安全凭据来源",
85
85
  "secure_credentials_mode_keyring": "ETS 密钥环文件",
86
86
  "secure_credentials_mode_manual": "手动凭据",
87
+ "secure_credentials_mode_combined": "密钥环 + 手动隧道密码",
87
88
  "tunnel_interface_individual_address": "隧道接口个人地址",
88
89
  "tunnel_interface_individual_address_placeholder": "1.1.1",
90
+ "tunnel_user_id": "隧道用户 ID",
89
91
  "tunnel_user_password": "隧道用户密码",
90
92
  "ga_list_label": "ETS 组地址列表"
91
93
  },
package/package.json CHANGED
@@ -3,14 +3,14 @@
3
3
  "engines": {
4
4
  "node": ">=20.18.1"
5
5
  },
6
- "version": "4.0.4",
6
+ "version": "4.0.6",
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.1.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",