node-red-contrib-knx-ultimate 6.3.10 → 6.3.12

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,14 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 6.3.12** - August 2026<br/>
10
+
11
+ - **HUE Controller**: completed the EN, IT, DE, FR, ES and zh-CN editor translations, including Light Level, Locate, Light mapping labels, mapping placeholders, error messages and tutorial links; the embedded profiles and dynamic device labels now follow Node-RED's active locale instead of a potentially stale page-language attribute. Corrected French/Spanish Hue and KNX terminology and realigned the localized help and wiki documentation.<br/>
12
+
13
+ **Version 6.3.11** - August 2026<br/>
14
+
15
+ - **Matter Controller**: fixed KNX GA mapping tabs being hidden by transient empty KNX/Matter config-selector values during Node-RED 5 editor initialization; saved gateways now remain selected until the user explicitly changes them.<br/>
16
+
9
17
  **Version 6.3.10** - August 2026<br/>
10
18
 
11
19
  - **Watchdog**: added a default-on option to listen for error states reported by KNX-Ultimate nodes, including KNX Device, through the selected gateway; disabling it suppresses `NodeError` flow messages without affecting the Watchdog's own bus checks or control messages. Existing flows retain the previous enabled behavior.<br/>
@@ -254,11 +254,18 @@
254
254
  `);
255
255
  };
256
256
 
257
- const migrationText = (node, key, fallback, values) => {
257
+ const controllerMessage = (key, values) => {
258
258
  try {
259
- const translated = node._(`knxUltimateHueController.${key}`, values);
260
- if (translated && translated !== `knxUltimateHueController.${key}`) return translated;
259
+ const messageKey = `node-red-contrib-knx-ultimate/knxUltimateHueController:knxUltimateHueController.${key}`;
260
+ const translated = RED._(messageKey, values);
261
+ if (translated && translated !== messageKey) return translated;
261
262
  } catch (error) { /* empty */ }
263
+ return undefined;
264
+ };
265
+
266
+ const migrationText = (_node, key, fallback, values) => {
267
+ const translated = controllerMessage(key, values);
268
+ if (translated) return translated;
262
269
  return fallback;
263
270
  };
264
271
 
@@ -318,7 +325,13 @@
318
325
  icon: 'node-hue-icon.svg',
319
326
  label() {
320
327
  const profile = PROFILE_TYPES[this.hueControllerType] || PROFILE_TYPES.light;
321
- return this.name || `HUE Controller · ${profile.label}`;
328
+ let typeLabel = profile.label;
329
+ try {
330
+ const translationKey = `node-red-contrib-knx-ultimate/knxUltimateHueController:knxUltimateHueController.types.${this.hueControllerType || 'light'}`;
331
+ const translated = RED._(translationKey);
332
+ if (translated && translated !== translationKey) typeLabel = translated;
333
+ } catch (error) { /* use catalog fallback */ }
334
+ return this.name || `HUE Controller · ${typeLabel}`;
322
335
  },
323
336
  paletteLabel: 'HUE Controller',
324
337
  oneditprepare() {
@@ -435,10 +448,8 @@
435
448
  };
436
449
 
437
450
  const controllerText = (key, fallback) => {
438
- try {
439
- const translated = controllerNode._(`knxUltimateHueController.${key}`);
440
- if (translated && translated !== `knxUltimateHueController.${key}`) return translated;
441
- } catch (error) { /* empty */ }
451
+ const translated = controllerMessage(key);
452
+ if (translated) return translated;
442
453
  return fallback;
443
454
  };
444
455
 
@@ -710,7 +721,9 @@
710
721
  $container.toggleClass('hue-controller-light-profile', controllerType === 'light');
711
722
 
712
723
  if (!definition || !template) {
713
- $container.html('<div class="hue-controller-editor-error">The selected Hue editor is unavailable. Restart Node-RED after updating the package.</div>');
724
+ $container.html($('<div class="hue-controller-editor-error"></div>').text(
725
+ controllerText('editor_unavailable', 'The selected Hue editor is unavailable. Restart Node-RED after updating the package.')
726
+ ));
714
727
  return;
715
728
  }
716
729
 
@@ -967,7 +980,7 @@
967
980
  <script type="text/html" data-template-name="knxUltimateHueController">
968
981
  <div class="form-row" style="margin-bottom:14px;">
969
982
  <span style="color:#ff0000"><i class="fa fa-youtube"></i></span>&nbsp;
970
- <a target="_blank" rel="noopener noreferrer" href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E"><b>KNX-Ultimate video tutorials (YouTube playlist)</b></a>
983
+ <a target="_blank" rel="noopener noreferrer" href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E"><b data-i18n="knxUltimateHueController.tutorials_link"></b></a>
971
984
  </div>
972
985
  <div class="form-row" id="hue-controller-migrate-legacy-flow-row" style="display:none; margin-bottom:14px;">
973
986
  <a class="hue-legacy-migration-video" href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer" style="display:inline-flex; align-items:center; gap:5px; margin-right:10px;" data-i18n="[title]knxUltimateHueController.migration_video_title">
@@ -3,6 +3,7 @@
3
3
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
4
4
 
5
5
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
6
+ <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/configNodeEditorSelection.js"></script>
6
7
 
7
8
  <script type="text/javascript">
8
9
  (function () {
@@ -161,16 +162,6 @@
161
162
  });
162
163
  }
163
164
  $("#node-input-updateLocalStateFromKNXWrite").prop("checked", node.updateLocalStateFromKNXWrite === true || node.updateLocalStateFromKNXWrite === "true"); // Starting from v 4.1.31
164
- const ensureConfigSelection = (selector) => {
165
- if ($(selector).val() !== "_ADD_") return;
166
- try {
167
- $(selector).prop("selectedIndex", 0);
168
- } catch (error) {
169
- // Ignore UI quirks for legacy Node-RED versions
170
- }
171
- };
172
- ["#node-input-server", "#node-input-serverMatter"].forEach(ensureConfigSelection);
173
-
174
165
  function ensureVerticalTabsStyle() {
175
166
  if ($('#knxUltimateMatterControllerDeviceVerticalTabs').length) return;
176
167
  const style = `
@@ -261,7 +252,7 @@
261
252
  function onEditPrepare() {
262
253
  ensureVerticalTabsStyle();
263
254
  const $knxServerInput = $("#node-input-server");
264
- const KNX_EMPTY_VALUES = new Set(['', 'none', '_ADD_', '__NONE__']);
255
+ const KNX_EMPTY_VALUES = new Set(['', 'none', '_add_', '__none__']);
265
256
  // Historical variable names are retained for saved-flow compatibility; they point to Matter widgets.
266
257
  const $hueServerInput = $("#node-input-serverMatter");
267
258
  const $matterControllerModeInput = $("#node-input-matterControllerMode");
@@ -300,6 +291,21 @@
300
291
  let matterDevicePickerMouseDown = false;
301
292
  let showingNoHueDevicesPlaceholder = false;
302
293
  const HUE_EMPTY_SERVER_VALUES = new Set(['', 'none', '_add_', '__none__', '__null__', 'null', 'undefined']);
294
+ const selectionApi = window.KNXUltimateConfigNodeEditorSelection;
295
+ const normalizeConfigSelection = selectionApi && typeof selectionApi.normalizeSelection === 'function'
296
+ ? selectionApi.normalizeSelection
297
+ : (value, emptyValues) => {
298
+ const normalized = value === undefined || value === null ? '' : String(value).trim();
299
+ return emptyValues.has(normalized.toLowerCase()) ? '' : normalized;
300
+ };
301
+ const resolveSelectedOrStoredSelection = selectionApi && typeof selectionApi.resolveSelectedOrStoredSelection === 'function'
302
+ ? selectionApi.resolveSelectedOrStoredSelection
303
+ : (selectedValue, storedValue, emptyValues) => normalizeConfigSelection(selectedValue, emptyValues) || normalizeConfigSelection(storedValue, emptyValues);
304
+ const shouldPreserveSelection = selectionApi && typeof selectionApi.shouldPreserveSelection === 'function'
305
+ ? selectionApi.shouldPreserveSelection
306
+ : (event, nextValue, currentValue, emptyValues) => (!event || !event.originalEvent) && normalizeConfigSelection(nextValue, emptyValues) === '' && normalizeConfigSelection(currentValue, emptyValues) !== '';
307
+ let activeKnxServerId = resolveSelectedOrStoredSelection($knxServerInput.val(), node.server, KNX_EMPTY_VALUES);
308
+ let activeMatterServerId = resolveSelectedOrStoredSelection($hueServerInput.val(), node.serverMatter, HUE_EMPTY_SERVER_VALUES);
303
309
  let locateSessionActive = false;
304
310
  let locateAutoResetTimer = null;
305
311
  let locatePendingRequest = null;
@@ -483,14 +489,7 @@
483
489
  };
484
490
 
485
491
  const resolveKnxServerValue = () => {
486
- const domValue = $knxServerInput.val();
487
- if (domValue !== undefined && domValue !== null && domValue !== '') {
488
- return domValue;
489
- }
490
- if (node.server !== undefined && node.server !== null) {
491
- return node.server;
492
- }
493
- return '';
492
+ return activeKnxServerId;
494
493
  };
495
494
 
496
495
  const hasKnxServerSelected = () => {
@@ -500,17 +499,7 @@
500
499
  };
501
500
 
502
501
  const resolveHueServerValue = ({ allowStored = false } = {}) => {
503
- if ($hueServerInput.length) {
504
- const domValue = $hueServerInput.val();
505
- if (domValue !== undefined && domValue !== null) {
506
- const trimmed = String(domValue).trim();
507
- if (trimmed !== '' && !HUE_EMPTY_SERVER_VALUES.has(trimmed.toLowerCase())) return trimmed;
508
- }
509
- }
510
- if (node.serverMatter !== undefined && node.serverMatter !== null) {
511
- const stored = String(node.serverMatter).trim();
512
- if (stored !== '' && !HUE_EMPTY_SERVER_VALUES.has(stored.toLowerCase())) return stored;
513
- }
502
+ if (activeMatterServerId !== '') return activeMatterServerId;
514
503
  if (allowStored && node.__locateSessionInfo && node.__locateSessionInfo.serverId) {
515
504
  return node.__locateSessionInfo.serverId;
516
505
  }
@@ -1458,8 +1447,20 @@
1458
1447
  $matterControllerModeInput.on('change.knxUltimateMatterControllerDevice', applyControllerMode);
1459
1448
  $universalServiceInput.on('change.knxUltimateMatterControllerDevice', refreshUniversalServiceVisibility);
1460
1449
 
1461
- $knxServerInput.on('change.knxUltimateMatterControllerDevice', () => {
1462
- refreshKnxBindings();
1450
+ $knxServerInput.on('change.knxUltimateMatterControllerDevice', function (event) {
1451
+ const selectedValue = $(this).val();
1452
+ if (shouldPreserveSelection(event, selectedValue, activeKnxServerId, KNX_EMPTY_VALUES)) {
1453
+ // Node-RED 5 can briefly emit an empty placeholder while rebuilding
1454
+ // config-node selectors. Restore the effective saved value so an
1455
+ // untouched save and the mapping tabs both keep the KNX gateway.
1456
+ $knxServerInput.val(activeKnxServerId);
1457
+ updateTabsVisibility();
1458
+ return;
1459
+ }
1460
+ activeKnxServerId = normalizeConfigSelection(selectedValue, KNX_EMPTY_VALUES);
1461
+ // Selecting none only changes visibility. Keep the mounted GA/DPT
1462
+ // controls intact so choosing the gateway again cannot lose values.
1463
+ if (activeKnxServerId !== '') refreshKnxBindings();
1463
1464
  updateTabsVisibility();
1464
1465
  });
1465
1466
 
@@ -1553,7 +1554,15 @@
1553
1554
  }
1554
1555
 
1555
1556
  if ($hueServerInput.length) {
1556
- $hueServerInput.off('.knxUltimateMatterControllerDeviceDevices').on('change.knxUltimateMatterControllerDeviceDevices', () => {
1557
+ $hueServerInput.off('.knxUltimateMatterControllerDeviceDevices').on('change.knxUltimateMatterControllerDeviceDevices', function (event) {
1558
+ const selectedValue = $(this).val();
1559
+ if (shouldPreserveSelection(event, selectedValue, activeMatterServerId, HUE_EMPTY_SERVER_VALUES)) {
1560
+ $hueServerInput.val(activeMatterServerId);
1561
+ return;
1562
+ }
1563
+ const nextMatterServerId = normalizeConfigSelection(selectedValue, HUE_EMPTY_SERVER_VALUES);
1564
+ if (nextMatterServerId === activeMatterServerId) return;
1565
+ activeMatterServerId = nextMatterServerId;
1557
1566
  cachedHueDevices = [];
1558
1567
  node._cachedHueLightDevices = cachedHueDevices;
1559
1568
  showingNoHueDevicesPlaceholder = false;
@@ -1772,11 +1781,6 @@
1772
1781
 
1773
1782
  // The Matter controller needs no readiness poll here: the light list is fetched
1774
1783
  // on demand by the autocomplete. Start the editor immediately.
1775
- $("#node-input-serverMatter").change(function () {
1776
- try {
1777
- node._cachedHueLightDevices = [];
1778
- } catch (error) { }
1779
- });
1780
1784
  Go();
1781
1785
  // ################################################################
1782
1786
 
@@ -1,5 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueController">
2
- <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate video tutorials (YouTube playlist)</strong></a> Sehen Sie sich vor dem Start die <a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">Videoanleitung auf YouTube</a> an.</p>
2
+ <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate-Videotutorials (YouTube-Playlist)</strong></a> Sehen Sie sich vor dem Start die <a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">Videoanleitung auf YouTube</a> an.</p>
3
3
  <p><strong>HUE Controller</strong> ist der einheitliche Knoten für Hue-API-v2-Ressourcen.</p>
4
4
  <p>Wählen Sie ein Hue-Gerät oder eine Hue-Ressource. HUE Controller erkennt den Gerätetyp automatisch und öffnet den passenden Editor für KNX-Zuordnungen, Verhalten und Node-Anschlüsse.</p>
5
5
  <p>Geräteauswahl, Aktualisierung und erkannter Typ stehen wie beim Matter Controller in derselben kompakten Zeile.</p>
@@ -154,6 +154,8 @@ Die Tabelle **Hue-native Effekte** ordnet KNX-Werte den vom Bridge gemeldeten Ef
154
154
  | Night lighting | Nacht-Profil (Farbe oder Temperatur/Helligkeit) |
155
155
  | Day/Night | GA zur Umschaltung Tag/Nacht (_true_ = Tag, _false_ = Nacht) |
156
156
  | Invert Day/Night | Wert der Tag/Nacht-GA invertieren |
157
+ | Status beim Start lesen | Status beim Start lesen und bei Start/Wiederverbindung als Ereignis auf den KNX-Bus senden (Standard: „Nein“) |
158
+ | Tagmodus erzwingen | Der Tagmodus kann durch schnelles Aus- und Einschalten dieser Leuchte oder aller Leuchtenknoten aktiviert werden. |
157
159
  | Node I/O pins | Ein/Ausblenden der Eingangs/Ausgangs-Pins; Input folgt HUE API v2 (z. B. <code>msg.on = { on: true }</code>) |
158
160
 
159
161
  Hinweis: Start/Stop-Dimmen im KNX-Modus wird über die üblichen Start/Stop-Telegramme gesteuert.
@@ -4,6 +4,9 @@
4
4
  "hue_bridge": "Hue Bridge"
5
5
  },
6
6
  "knxUltimateHueController": {
7
+ "locale": "de",
8
+ "tutorials_link": "KNX-Ultimate-Videotutorials (YouTube-Playlist)",
9
+ "editor_unavailable": "Der ausgewählte Hue-Editor ist nicht verfügbar. Starten Sie Node-RED nach der Paketaktualisierung neu.",
7
10
  "device_function": "Gerätefunktion",
8
11
  "hue_device": "Hue-Gerät",
9
12
  "hue_device_placeholder": "Hue-Gerät oder -Ressource suchen",
@@ -11,7 +11,7 @@ Er ersetzt die unveröffentlichten getrennten Matter-Controller-Nodes und behäl
11
11
 
12
12
  |Feld|Beschreibung|
13
13
  |--|--|
14
- | KNX GW | KNX-Gateway zum Schreiben und Beantworten der konfigurierten Gruppenadressen. Kann leer bleiben, wenn nur der Node-RED-Ausgang benötigt wird. |
14
+ | KNX GW | KNX-Gateway zum Schreiben und Beantworten der konfigurierten Gruppenadressen. Kann leer bleiben, wenn nur der Node-RED-Ausgang benötigt wird. Das gespeicherte Gateway bleibt während der Initialisierung des Editors ausgewählt und ändert sich erst nach einer ausdrücklichen Benutzerauswahl. |
15
15
  | Matter controller | Matter-Controller-Konfigurationsknoten, in dem das Gerät gekoppelt wurde. |
16
16
  | Matter device | Matter-Endpunkt aus den gekoppelten Geräten. Die UI wird aus den echten Fähigkeiten neu aufgebaut. |
17
17
  | Switch / Steckdose / Licht On-Off | On/Off-Befehls- und Status-Gruppenadressen, normalerweise DPT `1.001`. |
@@ -4,6 +4,9 @@
4
4
  "hue_bridge": "Hue Bridge"
5
5
  },
6
6
  "knxUltimateHueController": {
7
+ "locale": "en",
8
+ "tutorials_link": "KNX-Ultimate video tutorials (YouTube playlist)",
9
+ "editor_unavailable": "The selected Hue editor is unavailable. Restart Node-RED after updating the package.",
7
10
  "device_function": "Device function",
8
11
  "hue_device": "Hue device",
9
12
  "hue_device_placeholder": "Search your Hue device or resource",
@@ -11,7 +11,7 @@ It replaces the unpublished per-device Matter controller nodes and keeps the ful
11
11
 
12
12
  |Field|Description|
13
13
  |--|--|
14
- | KNX GW | KNX gateway used to write and answer the configured group addresses. It can be left empty when only Node-RED output is needed. |
14
+ | KNX GW | KNX gateway used to write and answer the configured group addresses. It can be left empty when only Node-RED output is needed. The saved gateway remains selected while the editor initializes and changes only after an explicit user selection. |
15
15
  | Matter controller | Matter controller configuration node where the device has been commissioned. |
16
16
  | Matter device | Matter endpoint selected from commissioned devices. The UI is rebuilt from its real capabilities. |
17
17
  | Switch / Plug / Light On-Off | On/Off command and status group addresses, usually DPT `1.001`. |