node-red-contrib-knx-ultimate 6.3.10 → 6.3.11
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 +4 -0
- package/nodes/knxUltimateMatterControllerDevice.html +42 -38
- package/nodes/locales/de/knxUltimateMatterControllerDevice.html +1 -1
- package/nodes/locales/en/knxUltimateMatterControllerDevice.html +1 -1
- package/nodes/locales/es/knxUltimateMatterControllerDevice.html +1 -1
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +1 -1
- package/nodes/locales/it/knxUltimateMatterControllerDevice.html +1 -1
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +1 -1
- package/package.json +1 -1
- package/resources/configNodeEditorSelection.js +32 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 6.3.11** - August 2026<br/>
|
|
10
|
+
|
|
11
|
+
- **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/>
|
|
12
|
+
|
|
9
13
|
**Version 6.3.10** - August 2026<br/>
|
|
10
14
|
|
|
11
15
|
- **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/>
|
|
@@ -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', '
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
|
@@ -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`. |
|
|
@@ -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`. |
|
|
@@ -11,7 +11,7 @@ Sustituye a los nodos Matter separados no publicados y conserva toda la UI de lu
|
|
|
11
11
|
|
|
12
12
|
|Campo|Descripción|
|
|
13
13
|
|--|--|
|
|
14
|
-
| KNX GW | Gateway KNX usado para escribir y responder las direcciones de grupo configuradas. Puede quedar vacío si solo se necesita la salida Node-RED. |
|
|
14
|
+
| KNX GW | Gateway KNX usado para escribir y responder las direcciones de grupo configuradas. Puede quedar vacío si solo se necesita la salida Node-RED. El gateway guardado permanece seleccionado durante la inicialización del editor y solo cambia tras una selección explícita del usuario. |
|
|
15
15
|
| Matter controller | Nodo de configuración Matter Controller donde el dispositivo fue emparejado. |
|
|
16
16
|
| Dispositivo Matter | Endpoint Matter seleccionado entre los dispositivos emparejados. La UI se reconstruye a partir de sus capacidades reales. |
|
|
17
17
|
| Switch / Enchufe / Luz On-Off | Direcciones de grupo de comando y estado On/Off, normalmente DPT `1.001`. |
|
|
@@ -11,7 +11,7 @@ Il remplace les nœuds Matter séparés non publiés et conserve toute l'UI lumi
|
|
|
11
11
|
|
|
12
12
|
|Champ|Description|
|
|
13
13
|
|--|--|
|
|
14
|
-
| KNX GW | Passerelle KNX utilisée pour écrire et répondre sur les adresses de groupe configurées. Elle peut rester vide si seule la sortie Node-RED est utilisée. |
|
|
14
|
+
| KNX GW | Passerelle KNX utilisée pour écrire et répondre sur les adresses de groupe configurées. Elle peut rester vide si seule la sortie Node-RED est utilisée. La passerelle enregistrée reste sélectionnée pendant l'initialisation de l'éditeur et ne change qu'après une sélection explicite de l'utilisateur. |
|
|
15
15
|
| Matter controller | Nœud de configuration Matter Controller dans lequel le périphérique a été appairé. |
|
|
16
16
|
| Appareil Matter | Endpoint Matter choisi parmi les appareils appairés. L'UI est reconstruite à partir de ses capacités réelles. |
|
|
17
17
|
| Switch / Prise / Lumière On-Off | Adresses de groupe commande et état On/Off, généralement DPT `1.001`. |
|
|
@@ -11,7 +11,7 @@ Sostituisce i nodi Matter separati non pubblicati e mantiene tutta la UI luce qu
|
|
|
11
11
|
|
|
12
12
|
|Campo|Descrizione|
|
|
13
13
|
|--|--|
|
|
14
|
-
| KNX GW | Gateway KNX usato per scrivere e rispondere sugli indirizzi di gruppo configurati. Può restare vuoto se serve solo l'output Node-RED. |
|
|
14
|
+
| KNX GW | Gateway KNX usato per scrivere e rispondere sugli indirizzi di gruppo configurati. Può restare vuoto se serve solo l'output Node-RED. Il gateway salvato rimane selezionato durante l'inizializzazione dell'editor e cambia soltanto dopo una selezione esplicita dell'utente. |
|
|
15
15
|
| Matter controller | Nodo di configurazione Matter Controller in cui il dispositivo è stato associato. |
|
|
16
16
|
| Dispositivo Matter | Endpoint Matter selezionato tra i dispositivi abbinati. La UI viene ricostruita in base alle capability reali. |
|
|
17
17
|
| Switch / Presa / Luce On-Off | Indirizzi di gruppo di comando e stato On/Off, di solito DPT `1.001`. |
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
|字段|说明|
|
|
13
13
|
|--|--|
|
|
14
|
-
| KNX GW | 用于写入并响应已配置组地址的 KNX 网关。如果只需要 Node-RED
|
|
14
|
+
| KNX GW | 用于写入并响应已配置组地址的 KNX 网关。如果只需要 Node-RED 输出,可以留空。编辑器初始化期间会保留已保存的网关,只有用户明确选择后才会更改。 |
|
|
15
15
|
| Matter controller | 设备已在其中配网的 Matter Controller 配置节点。 |
|
|
16
16
|
| Matter device | 从已配对设备中选择的 Matter endpoint。UI 会根据真实能力重新构建。 |
|
|
17
17
|
| Switch / 插座 / 灯 On-Off | On/Off 命令和状态组地址,通常使用 DPT `1.001`。 |
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=20.18.1"
|
|
5
5
|
},
|
|
6
|
-
"version": "6.3.
|
|
6
|
+
"version": "6.3.11",
|
|
7
7
|
"description": "KNX Ultimate is the most advanced KNX integration for Node-RED, providing secure KNX/IP communication, routing, ETS project import, Philips Hue, Matter Controller and Matter Bridge (control matter device via KNX and expose KNX GA via Matter), MQTT, diagnostics with AI, virtual devices, and powerful automation nodes. Build professional, reliable, and scalable smart home and building automation projects with minimal effort.",
|
|
8
8
|
"files": [
|
|
9
9
|
"nodes/",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
const api = factory()
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api
|
|
4
|
+
if (root) root.KNXUltimateConfigNodeEditorSelection = api
|
|
5
|
+
}(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
6
|
+
const DEFAULT_EMPTY_VALUES = new Set(['', 'none', '_add_', '__none__'])
|
|
7
|
+
|
|
8
|
+
function normalizeSelection (value, emptyValues = DEFAULT_EMPTY_VALUES) {
|
|
9
|
+
const normalized = value === undefined || value === null ? '' : String(value).trim()
|
|
10
|
+
const normalizedEmptyValues = emptyValues instanceof Set
|
|
11
|
+
? emptyValues
|
|
12
|
+
: new Set(Array.isArray(emptyValues) ? emptyValues : DEFAULT_EMPTY_VALUES)
|
|
13
|
+
return normalizedEmptyValues.has(normalized.toLowerCase()) ? '' : normalized
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function shouldPreserveSelection (event, nextValue, currentValue, emptyValues = DEFAULT_EMPTY_VALUES) {
|
|
17
|
+
const programmaticChange = !event || !event.originalEvent
|
|
18
|
+
return programmaticChange &&
|
|
19
|
+
normalizeSelection(nextValue, emptyValues) === '' &&
|
|
20
|
+
normalizeSelection(currentValue, emptyValues) !== ''
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolveSelectedOrStoredSelection (selectedValue, storedValue, emptyValues = DEFAULT_EMPTY_VALUES) {
|
|
24
|
+
return normalizeSelection(selectedValue, emptyValues) || normalizeSelection(storedValue, emptyValues)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
normalizeSelection,
|
|
29
|
+
resolveSelectedOrStoredSelection,
|
|
30
|
+
shouldPreserveSelection
|
|
31
|
+
}
|
|
32
|
+
}))
|