node-red-contrib-knx-ultimate 5.2.1 → 5.2.3
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 +20 -10
- package/nodes/commonFunctions.js +9 -9
- package/nodes/knxUltimateIoTBridge.html +59 -2
- package/nodes/knxUltimateIoTBridge.js +4 -0
- package/nodes/knxUltimateMatterBridge.html +212 -299
- package/nodes/knxUltimateMatterBridge.js +166 -309
- package/nodes/knxUltimateMatterDevice.html +46 -0
- package/nodes/lib/mqtt-bridge.js +43 -1
- package/nodes/locales/de/knxUltimateIoTBridge.html +1 -0
- package/nodes/locales/de/knxUltimateIoTBridge.json +6 -0
- package/nodes/locales/de/knxUltimateMatterBridge.html +13 -24
- package/nodes/locales/de/knxUltimateMatterBridge.json +4 -1
- package/nodes/locales/de/matterbridge-config.html +34 -0
- package/nodes/locales/de/matterbridge-config.json +20 -0
- package/nodes/locales/en/knxUltimateIoTBridge.html +1 -0
- package/nodes/locales/en/knxUltimateIoTBridge.json +6 -0
- package/nodes/locales/en/knxUltimateMatterBridge.html +14 -25
- package/nodes/locales/en/knxUltimateMatterBridge.json +4 -1
- package/nodes/locales/en/matterbridge-config.html +34 -0
- package/nodes/locales/en/matterbridge-config.json +20 -0
- package/nodes/locales/es/knxUltimateIoTBridge.html +1 -0
- package/nodes/locales/es/knxUltimateIoTBridge.json +6 -0
- package/nodes/locales/es/knxUltimateMatterBridge.html +13 -24
- package/nodes/locales/es/knxUltimateMatterBridge.json +4 -1
- package/nodes/locales/es/matterbridge-config.html +34 -0
- package/nodes/locales/es/matterbridge-config.json +20 -0
- package/nodes/locales/fr/knxUltimateIoTBridge.html +1 -0
- package/nodes/locales/fr/knxUltimateIoTBridge.json +6 -0
- package/nodes/locales/fr/knxUltimateMatterBridge.html +13 -24
- package/nodes/locales/fr/knxUltimateMatterBridge.json +4 -1
- package/nodes/locales/fr/matterbridge-config.html +34 -0
- package/nodes/locales/fr/matterbridge-config.json +20 -0
- package/nodes/locales/it/knxUltimateIoTBridge.html +1 -0
- package/nodes/locales/it/knxUltimateIoTBridge.json +6 -0
- package/nodes/locales/it/knxUltimateMatterBridge.html +13 -24
- package/nodes/locales/it/knxUltimateMatterBridge.json +4 -1
- package/nodes/locales/it/matterbridge-config.html +34 -0
- package/nodes/locales/it/matterbridge-config.json +20 -0
- package/nodes/locales/zh-CN/knxUltimateIoTBridge.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateIoTBridge.json +6 -0
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +13 -24
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +4 -1
- package/nodes/locales/zh-CN/matterbridge-config.html +34 -0
- package/nodes/locales/zh-CN/matterbridge-config.json +20 -0
- package/nodes/matterbridge-config.html +121 -0
- package/nodes/matterbridge-config.js +220 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,16 +6,26 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
-
**Version 5.2.
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
**Version 5.2.3** - July 2026<br/>
|
|
10
|
+
|
|
11
|
+
- **MQTT / Home Assistant bridge — Entity name format**: new selector to reshape the names exposed to Home Assistant. The ETS import builds each name with the group-address path first (e.g. `(Lights->Ground floor) Living room`); now you can choose *As imported from ETS* (default), *Name first* (`Living room (Lights->Ground floor)`), *Name only* (`Living room`) or *Name + group address* (`Living room (0/1/2)`). Changing it only updates the friendly name (the `unique_id`/`object_id` stay stable, so existing entities are not duplicated).<br/>
|
|
12
|
+
- The group-address list in the editor now previews the chosen format **live**: pick a format and every row is re-labelled instantly, exactly as it will appear in Home Assistant, without reloading the list or losing the expose / read-only ticks.<br/>
|
|
13
|
+
- Entity names are sanitized before publishing: ASCII control characters are stripped and runs of whitespace collapsed (ETS exports sometimes lose an accented character and leave a double space, e.g. `Velocità vento` → `Velocit vento`), and a name that would be empty falls back to the group address.<br/>
|
|
14
|
+
- **Documentation**: node help and the online IoT-Bridge page updated for the new option in all six languages (EN/IT/DE/FR/ES/zh-CN).<br/>
|
|
15
|
+
|
|
16
|
+
**Version 5.2.2** - July 2026<br/>
|
|
17
|
+
|
|
18
|
+
- New **Matter Bridge (BETA)**: exposes **KNX group addresses as Matter devices**. Alexa, Google Home, Apple Home (or any Matter controller) commission the bridge once — via QR code or 11-digit manual code shown in the editor — and see all your KNX devices with the names you typed, ready for app and voice control. It is the opposite direction of the Matter Device node introduced in 5.1.0.<br/>
|
|
19
|
+
- Two-node architecture (like the HUE and Matter Controller integrations): a **Matter Bridge** configuration node is the bridge itself (holds the pairing, the QR code and the port), and a **Matter Bridge device** node is added per exposed device and points to it. Spread the device nodes across your flows, each with its own status — no giant list.<br/>
|
|
20
|
+
- **17 device types**: on/off light, dimmable light, **RGB color light** (DPT 232.600, converted to/from the Matter color wheel), **tunable white light** (color temperature in Kelvin, DPT 7.600), plug, cover/shutter (up/down, stop, position % with optional inversion), thermostat (current temperature + heating setpoint, both ways), **fan/ventilation** (speed %), sensors (temperature, humidity, illuminance, occupancy, contact), **smoke/CO alarm**, **water leak detector**, **air quality sensor** (CO2 ppm with automatic air-quality classification) and **robot vacuum** (flow-only: integrate your robot with any Node-RED node and expose it to the voice assistants).<br/>
|
|
21
|
+
- Bidirectional with loop protection: assistant commands write to the KNX *command* group addresses; telegrams on the *status* group addresses update the Matter attributes (and the apps). At startup each device node can send a `GroupValue_Read` to its status GAs so the Matter attributes are populated before the first app query.<br/>
|
|
22
|
+
- **Live device reconciliation**: the Matter server stays alive across re-deploys — added/renamed/removed device nodes appear on the paired controllers within seconds, without restarting Node-RED and without re-pairing.<br/>
|
|
23
|
+
- Optional **node Input/Output PINs**: the output forwards every controller command to the flow; the input updates the Matter state from flow messages (`msg.payload = { function, value }`). Devices without a command GA become flow-only devices.<br/>
|
|
24
|
+
- The **KNX gateway is optional** on the device node: without it the device runs in flow-only mode, useful to expose flow-computed or non-KNX devices to Alexa & Co.<br/>
|
|
25
|
+
- Multi-fabric: several controllers can be paired at the same time; the pairing panel lists them and a **Reset pairing** button decommissions everything. The bridge identity persists across re-deploys (no re-pairing needed). Each configuration node can use a different UDP port, so multiple independent bridges are possible.<br/>
|
|
26
|
+
- Editor with ETS group-address autocomplete filtered per function: the KNX gateway is resolved automatically (auto-selected when only one exists), so the address list is always available. Localized in EN/IT/DE/FR/ES/zh-CN.<br/>
|
|
27
|
+
- Note for early 5.2.x BETA testers: the two-node split changes the Matter identity of the bridge - remove the old bridge from your Matter app and pair the new configuration node once.<br/>
|
|
28
|
+
- **Matter Device** node editor: the mappings list grows to fill the edit panel and has a native resize grip (bottom-right corner) to drag it to any height.<br/>
|
|
19
29
|
- Matter integration hardening: try/catch protection on all critical paths (controller startup, client registration, structure queries, commissioning aftermath) so errors never crash Node-RED. Fixed the devices editor losing the configured group addresses on close/reopen (with automatic recovery of configurations saved by the affected versions).<br/>
|
|
20
30
|
- **Documentation**: node help for the three Matter nodes in all six languages, new **Matter section** in the online docs sidebar with the Matter Controller, Matter Device and Matter Bridge pages (EN/IT/DE/FR/ES/zh-CN), the two new nodes on the homepage node list and the Matter logo alongside Home Assistant and MQTT.<br/>
|
|
21
31
|
|
package/nodes/commonFunctions.js
CHANGED
|
@@ -1006,15 +1006,15 @@ module.exports = (RED) => {
|
|
|
1006
1006
|
}
|
|
1007
1007
|
})
|
|
1008
1008
|
|
|
1009
|
-
// MATTER BRIDGE: returns the pairing info (QR code, manual code, commissioned fabrics) of a
|
|
1010
|
-
RED.httpAdmin.get('/KNXUltimateMatterBridgeInfo', RED.auth.needsPermission('
|
|
1009
|
+
// MATTER BRIDGE: returns the pairing info (QR code, manual code, commissioned fabrics) of a matterbridge-config node
|
|
1010
|
+
RED.httpAdmin.get('/KNXUltimateMatterBridgeInfo', RED.auth.needsPermission('matterbridge-config.read'), (req, res) => {
|
|
1011
1011
|
try {
|
|
1012
|
-
const
|
|
1013
|
-
if (
|
|
1012
|
+
const bridgeConfig = RED.nodes.getNode(req.query.configId)
|
|
1013
|
+
if (bridgeConfig === null || bridgeConfig === undefined || typeof bridgeConfig.getPairingInfo !== 'function') {
|
|
1014
1014
|
res.json({ error: 'PLEASE DEPLOY FIRST: then try again.' })
|
|
1015
1015
|
return
|
|
1016
1016
|
}
|
|
1017
|
-
res.json(
|
|
1017
|
+
res.json(bridgeConfig.getPairingInfo())
|
|
1018
1018
|
} catch (error) {
|
|
1019
1019
|
RED.log.error(`Err KNXUltimateMatterBridgeInfo: ${error.message}`)
|
|
1020
1020
|
res.json({ error: error.message })
|
|
@@ -1022,14 +1022,14 @@ module.exports = (RED) => {
|
|
|
1022
1022
|
})
|
|
1023
1023
|
|
|
1024
1024
|
// MATTER BRIDGE: factory reset (removes all paired controllers, restarts pairing advertising)
|
|
1025
|
-
RED.httpAdmin.get('/KNXUltimateMatterBridgeReset', RED.auth.needsPermission('
|
|
1025
|
+
RED.httpAdmin.get('/KNXUltimateMatterBridgeReset', RED.auth.needsPermission('matterbridge-config.write'), async (req, res) => {
|
|
1026
1026
|
try {
|
|
1027
|
-
const
|
|
1028
|
-
if (
|
|
1027
|
+
const bridgeConfig = RED.nodes.getNode(req.query.configId)
|
|
1028
|
+
if (bridgeConfig === null || bridgeConfig === undefined || typeof bridgeConfig.factoryResetBridge !== 'function') {
|
|
1029
1029
|
res.json({ error: 'PLEASE DEPLOY FIRST: then try again.' })
|
|
1030
1030
|
return
|
|
1031
1031
|
}
|
|
1032
|
-
await
|
|
1032
|
+
await bridgeConfig.factoryResetBridge()
|
|
1033
1033
|
res.json({ status: 'ok' })
|
|
1034
1034
|
} catch (error) {
|
|
1035
1035
|
RED.log.error(`Err KNXUltimateMatterBridgeReset: ${error.message}`)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
readOnDeploy: { value: true },
|
|
13
13
|
acceptFlowInput: { value: true },
|
|
14
14
|
mappings: { value: [] },
|
|
15
|
-
nodeMode: { value: '
|
|
15
|
+
nodeMode: { value: 'homeassistant' },
|
|
16
16
|
haBusMode: { value: 'standalone' },
|
|
17
17
|
inputs: { value: 1 },
|
|
18
18
|
outputs: { value: 2 },
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
mqttBaseTopic: { value: 'knx-ultimate' },
|
|
21
21
|
mqttDiscovery: { value: true },
|
|
22
22
|
mqttDiscoveryPrefix: { value: 'homeassistant' },
|
|
23
|
+
mqttNameFormat: { value: 'full' },
|
|
23
24
|
mqttCustomEntities: { value: [] },
|
|
24
25
|
mqttExposedGAs: { value: [] },
|
|
25
26
|
mqttReadOnlyGAs: { value: [] },
|
|
@@ -327,6 +328,41 @@
|
|
|
327
328
|
const readOnlySet = new Set(Array.isArray(node.mqttReadOnlyGAs) ? node.mqttReadOnlyGAs : []);
|
|
328
329
|
const exposeConfigured = node.mqttExposeConfigured === true;
|
|
329
330
|
|
|
331
|
+
// Mirror of formatEntityName() in lib/mqtt-bridge.js so the preview here matches
|
|
332
|
+
// exactly what gets published to Home Assistant (control chars stripped, whitespace
|
|
333
|
+
// collapsed, ETS path reshaped per the chosen format).
|
|
334
|
+
function sanitizeGaName(value) {
|
|
335
|
+
return String(value == null ? '' : value)
|
|
336
|
+
.replace(/[\u0000-\u001f\u007f]/g, ' ')
|
|
337
|
+
.replace(/\s+/g, ' ')
|
|
338
|
+
.trim();
|
|
339
|
+
}
|
|
340
|
+
function formatGaName(raw, ga, format) {
|
|
341
|
+
const full = sanitizeGaName(raw);
|
|
342
|
+
if (full === '') return ga;
|
|
343
|
+
if (!format || format === 'full') return full;
|
|
344
|
+
const m = full.match(/^\(([^)]*)\)\s*(.*)$/);
|
|
345
|
+
const path = m ? m[1].trim() : '';
|
|
346
|
+
const name = m ? (m[2].trim() || ga) : full;
|
|
347
|
+
let result;
|
|
348
|
+
switch (format) {
|
|
349
|
+
case 'name-first': result = path ? (name + ' (' + path + ')') : name; break;
|
|
350
|
+
case 'name-only': result = name; break;
|
|
351
|
+
case 'name-ga': result = name + ' (' + ga + ')'; break;
|
|
352
|
+
default: result = full;
|
|
353
|
+
}
|
|
354
|
+
return sanitizeGaName(result) || ga;
|
|
355
|
+
}
|
|
356
|
+
// Re-label every GA row from its stored raw name using the currently selected format.
|
|
357
|
+
function applyGaNameFormat() {
|
|
358
|
+
const fmt = $('#node-input-mqttNameFormat').val() || 'full';
|
|
359
|
+
$('#mqtt-ga-list .mqtt-ga-name').each(function () {
|
|
360
|
+
const span = $(this);
|
|
361
|
+
const text = formatGaName(span.data('raw'), span.data('ga'), fmt);
|
|
362
|
+
span.text(text).attr('title', text);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
330
366
|
function updateGaCount() {
|
|
331
367
|
const total = $('#mqtt-ga-list input.mqtt-ga-cb').length;
|
|
332
368
|
const sel = $('#mqtt-ga-list input.mqtt-ga-cb:checked').length;
|
|
@@ -359,7 +395,9 @@
|
|
|
359
395
|
const cb = $('<input/>', { type: 'checkbox', class: 'mqtt-ga-cb', value: item.ga, style: 'width:auto; margin:0; flex:0 0 auto;' }).appendTo(row);
|
|
360
396
|
cb.prop('checked', exposeConfigured ? savedSet.has(item.ga) : true);
|
|
361
397
|
$('<span/>').css({ flex: '0 0 auto', fontFamily: 'monospace', minWidth: '64px' }).text(item.ga).appendTo(row);
|
|
362
|
-
|
|
398
|
+
// Keep the raw ETS name + GA on the element so applyGaNameFormat() can
|
|
399
|
+
// re-label it live when the "Entity name format" select changes.
|
|
400
|
+
$('<span/>').addClass('mqtt-ga-name').css({ flex: '1 1 auto', minWidth: '0', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: '#444' }).data('raw', item.devicename || '').data('ga', item.ga).appendTo(row);
|
|
363
401
|
$('<span/>').css({ flex: '0 0 auto', color: '#888', fontSize: '11px' }).text('DPT' + (item.dpt || '')).appendTo(row);
|
|
364
402
|
// Read-only toggle: expose the GA to HA but never accept commands back to KNX.
|
|
365
403
|
const roLabel = $('<label/>').css({ flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: '3px', margin: '0', fontSize: '11px', color: '#888', cursor: 'pointer' }).appendTo(row);
|
|
@@ -374,6 +412,7 @@
|
|
|
374
412
|
});
|
|
375
413
|
});
|
|
376
414
|
$('#mqtt-ga-list input.mqtt-ga-cb').on('change', updateGaCount);
|
|
415
|
+
applyGaNameFormat();
|
|
377
416
|
updateGaCount();
|
|
378
417
|
}).fail(function () {
|
|
379
418
|
list.empty().append($('<div/>').css('color', '#c0392b').text(node._('knxUltimateIoTBridge.ha.csv_error')));
|
|
@@ -395,6 +434,8 @@
|
|
|
395
434
|
|
|
396
435
|
buildGaList();
|
|
397
436
|
$('#node-input-server').on('change', buildGaList);
|
|
437
|
+
// Live-preview the entity naming: re-label the list when the format select changes.
|
|
438
|
+
$('#node-input-mqttNameFormat').on('change', applyGaNameFormat);
|
|
398
439
|
} catch (error) { }
|
|
399
440
|
|
|
400
441
|
// Home Assistant composite entities (cover / climate): editable list.
|
|
@@ -675,6 +716,19 @@
|
|
|
675
716
|
<label for="node-input-mqttDiscoveryPrefix" data-i18n="knxUltimateIoTBridge.ha.discovery_prefix"></label>
|
|
676
717
|
<input type="text" id="node-input-mqttDiscoveryPrefix" placeholder="homeassistant" />
|
|
677
718
|
</div>
|
|
719
|
+
<div class="form-row ha-mode-row">
|
|
720
|
+
<label for="node-input-mqttNameFormat" data-i18n="knxUltimateIoTBridge.ha.name_format"></label>
|
|
721
|
+
<select id="node-input-mqttNameFormat" style="width:70%;">
|
|
722
|
+
<option value="full" data-i18n="knxUltimateIoTBridge.ha.name_format_full"></option>
|
|
723
|
+
<option value="name-first" data-i18n="knxUltimateIoTBridge.ha.name_format_name_first"></option>
|
|
724
|
+
<option value="name-only" data-i18n="knxUltimateIoTBridge.ha.name_format_name_only"></option>
|
|
725
|
+
<option value="name-ga" data-i18n="knxUltimateIoTBridge.ha.name_format_name_ga"></option>
|
|
726
|
+
</select>
|
|
727
|
+
</div>
|
|
728
|
+
<div class="form-row ha-mode-row" style="margin-top:-4px;">
|
|
729
|
+
<label style="width:auto;"> </label>
|
|
730
|
+
<span style="font-size:11px; color:#888;" data-i18n="knxUltimateIoTBridge.ha.name_format_hint"></span>
|
|
731
|
+
</div>
|
|
678
732
|
<div class="form-row ha-mode-row" id="rowMqttGaSelect">
|
|
679
733
|
<label style="width:auto; font-weight:600;" data-i18n="knxUltimateIoTBridge.ha.exposed_gas"></label>
|
|
680
734
|
<div style="margin:4px 0; display:flex; gap:6px; align-items:center;">
|
|
@@ -733,6 +787,9 @@ Native MQTT bridge with Home Assistant discovery. Every group address imported i
|
|
|
733
787
|
|
|
734
788
|
Requires an MQTT broker reachable by both Node-RED and Home Assistant, with the MQTT integration enabled in HA. Entities appear under a device named after this node.
|
|
735
789
|
|
|
790
|
+
### Entity name format
|
|
791
|
+
The ETS import builds each name with the group-address path first, e.g. `(Lights->Ground floor) Living room`. The **Entity name format** selector controls how the Home Assistant entity names are built: *As imported from ETS* (default), *Name first* (`Living room (Lights->Ground floor)`), *Name only* (`Living room`) or *Name + group address* (`Living room (0/1/2)`).
|
|
792
|
+
|
|
736
793
|
### KNX bus connection
|
|
737
794
|
Choose how the node exchanges telegrams with KNX:
|
|
738
795
|
|
|
@@ -51,6 +51,9 @@ module.exports = function (RED) {
|
|
|
51
51
|
node.mqttBaseTopic = typeof config.mqttBaseTopic === 'string' && config.mqttBaseTopic.trim() !== '' ? config.mqttBaseTopic.trim() : 'knx-ultimate'
|
|
52
52
|
node.mqttDiscovery = config.mqttDiscovery !== false && config.mqttDiscovery !== 'false'
|
|
53
53
|
node.mqttDiscoveryPrefix = typeof config.mqttDiscoveryPrefix === 'string' && config.mqttDiscoveryPrefix.trim() !== '' ? config.mqttDiscoveryPrefix.trim() : 'homeassistant'
|
|
54
|
+
// Entity name format for the exposed GAs: 'full' (ETS path + name, as imported),
|
|
55
|
+
// 'name-first' (name first, path after), 'name-only' (path removed), 'name-ga' (name + GA).
|
|
56
|
+
node.mqttNameFormat = typeof config.mqttNameFormat === 'string' && config.mqttNameFormat.trim() !== '' ? config.mqttNameFormat.trim() : 'full'
|
|
54
57
|
node.mqttCustomEntities = Array.isArray(config.mqttCustomEntities) ? config.mqttCustomEntities : []
|
|
55
58
|
// Group addresses to expose as simple entities. Once the user curates the list
|
|
56
59
|
// (mqttExposeConfigured), only the listed GAs are exposed; otherwise all imported GAs are.
|
|
@@ -180,6 +183,7 @@ module.exports = function (RED) {
|
|
|
180
183
|
baseTopic: node.mqttBaseTopic,
|
|
181
184
|
discovery: node.mqttDiscovery,
|
|
182
185
|
discoveryPrefix: node.mqttDiscoveryPrefix,
|
|
186
|
+
nameFormat: node.mqttNameFormat,
|
|
183
187
|
username: node.credentials ? node.credentials.mqttUsername : undefined,
|
|
184
188
|
password: node.credentials ? node.credentials.mqttPassword : undefined,
|
|
185
189
|
groupAddresses: (node.serverKNX && Array.isArray(node.serverKNX.csv)) ? node.serverKNX.csv : [],
|