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.
Files changed (47) hide show
  1. package/CHANGELOG.md +20 -10
  2. package/nodes/commonFunctions.js +9 -9
  3. package/nodes/knxUltimateIoTBridge.html +59 -2
  4. package/nodes/knxUltimateIoTBridge.js +4 -0
  5. package/nodes/knxUltimateMatterBridge.html +212 -299
  6. package/nodes/knxUltimateMatterBridge.js +166 -309
  7. package/nodes/knxUltimateMatterDevice.html +46 -0
  8. package/nodes/lib/mqtt-bridge.js +43 -1
  9. package/nodes/locales/de/knxUltimateIoTBridge.html +1 -0
  10. package/nodes/locales/de/knxUltimateIoTBridge.json +6 -0
  11. package/nodes/locales/de/knxUltimateMatterBridge.html +13 -24
  12. package/nodes/locales/de/knxUltimateMatterBridge.json +4 -1
  13. package/nodes/locales/de/matterbridge-config.html +34 -0
  14. package/nodes/locales/de/matterbridge-config.json +20 -0
  15. package/nodes/locales/en/knxUltimateIoTBridge.html +1 -0
  16. package/nodes/locales/en/knxUltimateIoTBridge.json +6 -0
  17. package/nodes/locales/en/knxUltimateMatterBridge.html +14 -25
  18. package/nodes/locales/en/knxUltimateMatterBridge.json +4 -1
  19. package/nodes/locales/en/matterbridge-config.html +34 -0
  20. package/nodes/locales/en/matterbridge-config.json +20 -0
  21. package/nodes/locales/es/knxUltimateIoTBridge.html +1 -0
  22. package/nodes/locales/es/knxUltimateIoTBridge.json +6 -0
  23. package/nodes/locales/es/knxUltimateMatterBridge.html +13 -24
  24. package/nodes/locales/es/knxUltimateMatterBridge.json +4 -1
  25. package/nodes/locales/es/matterbridge-config.html +34 -0
  26. package/nodes/locales/es/matterbridge-config.json +20 -0
  27. package/nodes/locales/fr/knxUltimateIoTBridge.html +1 -0
  28. package/nodes/locales/fr/knxUltimateIoTBridge.json +6 -0
  29. package/nodes/locales/fr/knxUltimateMatterBridge.html +13 -24
  30. package/nodes/locales/fr/knxUltimateMatterBridge.json +4 -1
  31. package/nodes/locales/fr/matterbridge-config.html +34 -0
  32. package/nodes/locales/fr/matterbridge-config.json +20 -0
  33. package/nodes/locales/it/knxUltimateIoTBridge.html +1 -0
  34. package/nodes/locales/it/knxUltimateIoTBridge.json +6 -0
  35. package/nodes/locales/it/knxUltimateMatterBridge.html +13 -24
  36. package/nodes/locales/it/knxUltimateMatterBridge.json +4 -1
  37. package/nodes/locales/it/matterbridge-config.html +34 -0
  38. package/nodes/locales/it/matterbridge-config.json +20 -0
  39. package/nodes/locales/zh-CN/knxUltimateIoTBridge.html +1 -0
  40. package/nodes/locales/zh-CN/knxUltimateIoTBridge.json +6 -0
  41. package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +13 -24
  42. package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +4 -1
  43. package/nodes/locales/zh-CN/matterbridge-config.html +34 -0
  44. package/nodes/locales/zh-CN/matterbridge-config.json +20 -0
  45. package/nodes/matterbridge-config.html +121 -0
  46. package/nodes/matterbridge-config.js +220 -0
  47. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -6,16 +6,26 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
- **Version 5.2.1** - July 2026<br/>
10
-
11
- - New **Matter Bridge (BETA)** node: 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 right in the node 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/>
12
- - **17 virtual 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/>
13
- - 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 the node can send a `GroupValue_Read` to every status GA so the Matter attributes are populated before the first app query.<br/>
14
- - **Live device reconciliation**: the Matter server stays alive across re-deploys added/renamed/removed devices appear on the paired controllers within seconds, without restarting Node-RED and without re-pairing.<br/>
15
- - Optional **node Input/Output PINs**: the output forwards every controller command to the flow; the input updates the Matter state of a virtual device from flow messages (`msg.payload = { device, function, value }`). Devices without a command GA become flow-only devices.<br/>
16
- - The **KNX gateway is optional**: without it the bridge runs in flow-only mode, useful to expose flow-computed or non-KNX devices to Alexa & Co.<br/>
17
- - 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).<br/>
18
- - Devices editor with ETS group-address autocomplete filtered per function and per-type fields. Localized in EN/IT/DE/FR/ES/zh-CN.<br/>
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
 
@@ -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 bridge node
1010
- RED.httpAdmin.get('/KNXUltimateMatterBridgeInfo', RED.auth.needsPermission('knxUltimateMatterBridge.read'), (req, res) => {
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 bridgeNode = RED.nodes.getNode(req.query.nodeId)
1013
- if (bridgeNode === null || bridgeNode === undefined || typeof bridgeNode.getBridgePairingInfo !== 'function') {
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(bridgeNode.getBridgePairingInfo())
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('knxUltimateMatterBridge.write'), async (req, res) => {
1025
+ RED.httpAdmin.get('/KNXUltimateMatterBridgeReset', RED.auth.needsPermission('matterbridge-config.write'), async (req, res) => {
1026
1026
  try {
1027
- const bridgeNode = RED.nodes.getNode(req.query.nodeId)
1028
- if (bridgeNode === null || bridgeNode === undefined || typeof bridgeNode.factoryResetBridge !== 'function') {
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 bridgeNode.factoryResetBridge()
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: 'iot' },
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
- $('<span/>').css({ flex: '1 1 auto', minWidth: '0', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: '#444' }).attr('title', item.devicename || '').text(item.devicename || '').appendTo(row);
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;">&nbsp;</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 : [],