node-red-contrib-knx-ultimate 6.0.5 → 6.0.7

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 (61) hide show
  1. package/CHANGELOG.md +8 -2
  2. package/README.md +1 -1
  3. package/nodes/commonFunctions.js +33 -0
  4. package/nodes/icons/node-matter-bridge-icon.svg +6 -0
  5. package/nodes/icons/node-matter-controller-icon.svg +6 -0
  6. package/nodes/knxUltimate-config.js +5 -1
  7. package/nodes/knxUltimateMatterBridge.html +1 -6
  8. package/nodes/knxUltimateMatterControllerDevice.html +156 -16
  9. package/nodes/knxUltimateMatterControllerDevice.js +5 -0
  10. package/nodes/locales/de/knxUltimateMatterBridge.html +2 -0
  11. package/nodes/locales/de/knxUltimateMatterControllerDevice.html +2 -0
  12. package/nodes/locales/de/knxUltimateMatterControllerDevice.json +10 -0
  13. package/nodes/locales/de/matter-config.html +4 -0
  14. package/nodes/locales/de/matter-config.json +7 -2
  15. package/nodes/locales/de/matterbridge-config.html +4 -0
  16. package/nodes/locales/de/matterbridge-config.json +4 -1
  17. package/nodes/locales/en/knxUltimateMatterBridge.html +2 -0
  18. package/nodes/locales/en/knxUltimateMatterControllerDevice.html +2 -0
  19. package/nodes/locales/en/knxUltimateMatterControllerDevice.json +10 -0
  20. package/nodes/locales/en/matter-config.html +4 -0
  21. package/nodes/locales/en/matter-config.json +7 -2
  22. package/nodes/locales/en/matterbridge-config.html +4 -0
  23. package/nodes/locales/en/matterbridge-config.json +4 -1
  24. package/nodes/locales/es/knxUltimateMatterBridge.html +2 -0
  25. package/nodes/locales/es/knxUltimateMatterControllerDevice.html +2 -0
  26. package/nodes/locales/es/knxUltimateMatterControllerDevice.json +10 -0
  27. package/nodes/locales/es/matter-config.html +4 -0
  28. package/nodes/locales/es/matter-config.json +7 -2
  29. package/nodes/locales/es/matterbridge-config.html +4 -0
  30. package/nodes/locales/es/matterbridge-config.json +4 -1
  31. package/nodes/locales/fr/knxUltimateMatterBridge.html +2 -0
  32. package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +2 -0
  33. package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +10 -0
  34. package/nodes/locales/fr/matter-config.html +4 -0
  35. package/nodes/locales/fr/matter-config.json +7 -2
  36. package/nodes/locales/fr/matterbridge-config.html +4 -0
  37. package/nodes/locales/fr/matterbridge-config.json +4 -1
  38. package/nodes/locales/it/knxUltimateMatterBridge.html +2 -0
  39. package/nodes/locales/it/knxUltimateMatterControllerDevice.html +2 -0
  40. package/nodes/locales/it/knxUltimateMatterControllerDevice.json +10 -0
  41. package/nodes/locales/it/matter-config.html +4 -0
  42. package/nodes/locales/it/matter-config.json +7 -2
  43. package/nodes/locales/it/matterbridge-config.html +4 -0
  44. package/nodes/locales/it/matterbridge-config.json +4 -1
  45. package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +2 -0
  46. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +2 -0
  47. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +10 -0
  48. package/nodes/locales/zh-CN/matter-config.html +4 -0
  49. package/nodes/locales/zh-CN/matter-config.json +7 -2
  50. package/nodes/locales/zh-CN/matterbridge-config.html +4 -0
  51. package/nodes/locales/zh-CN/matterbridge-config.json +4 -1
  52. package/nodes/matter-config.html +59 -18
  53. package/nodes/matter-config.js +33 -0
  54. package/nodes/matterbridge-config.html +73 -3
  55. package/nodes/matterbridge-config.js +38 -0
  56. package/nodes/utils/matterControllerProfiles/doorLock.js +210 -0
  57. package/nodes/utils/matterControllerProfiles/index.js +21 -0
  58. package/nodes/utils/matterControllerProfiles/mappedEndpoint.js +148 -0
  59. package/nodes/utils/matterStorageBackup.js +100 -0
  60. package/package.json +6 -2
  61. package/resources/htmlUtils.js +11 -0
package/CHANGELOG.md CHANGED
@@ -6,8 +6,14 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
- **Version 6.0.5** - July 2026<br/>
10
-
9
+ **Version 6.0.7** - July 2026<br/>
10
+
11
+ - **Control Matter from KNX (BETA) — Door Lock support**: commissioned Door Lock endpoints (including locks bridged by a vendor Matter hub) are now detected from their real `0x0101` cluster and expose KNX DPT 1 command/status mappings. `true` invokes `lockDoor`, `false` invokes `unlockDoor`, and subscribed `lockState` feedback updates KNX without command reflection. `NotFullyLocked` and `Unlatched` remain explicit flow states and are never collapsed into an unsafe binary KNX value. The optional remote-operation PIN is stored as a Node-RED credential; endpoints that do not advertise a requested command are rejected instead of receiving an invented operation. This is the first controller-side device profile in the extensible profile architecture requested in [discussion #519](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/discussions/519).<br/>
12
+ - **Control Matter from KNX (BETA) — multi-purpose endpoint profiles**: the controller node now keeps its established light path unchanged while routing non-light endpoints through a separate capability-driven mapped profile. Selecting a plug/On-Off actuator, cover, thermostat, fan, environmental/contact/occupancy sensor, battery, electrical-power or energy endpoint builds only the KNX mappings backed by clusters, attributes and supported commands actually reported by that endpoint. These mappings now live inside a dedicated **Mappings** tab beside **Behaviour**, matching the established light editor layout. Each mapping can be disabled by leaving its GA empty; cached status supports KNX read responses and startup publication, attribute reports never reflect commands back to Matter, and cluster events remain available on the optional flow output. Saved mappings survive an untouched editor save even when the Matter endpoint is temporarily offline.<br/>
13
+ - **Matter nodes — cleaner editors**: long pairing, storage, cache and endpoint-structure explanations were removed from the Matter node forms and consolidated in the localized HTML help, keeping the editors focused on fields, actions and live operational status.<br/>
14
+ - **Matter nodes — distinct canvas icons**: Matter Controller and Matter Bridge nodes now use separate compact icons, with `mat` above a large right/left direction arrow, making the two integration directions immediately distinguishable in the palette, workspace and configuration selectors.<br/>
15
+ - **Matter Controller/Bridge — protocol version and portable storage backups**: the Node-RED startup version line now logs the implemented Matter protocol revision (Matter 1.5.1, separately from the matter.js package version). Both Matter configuration editors can export and import a per-instance JSON backup containing the complete persistent Matter storage required to preserve fabrics, private credentials, sessions and commissioned/paired nodes. Import atomically replaces only the selected instance and restarts its engine; backup files contain secrets and must be protected like passwords.<br/>
16
+ - **Homepage — KNX integration engines, MQTT and Matter overview**: the integration-logo heading and Supported technologies section now identify the KNX integration engines and include dedicated MQTT and Matter cards in all six languages (EN/IT/DE/FR/ES/zh-CN), highlighting MQTT/Home Assistant discovery and the bidirectional Matter Controller/Bridge integration for controlling Matter devices and exposing KNX objects to Matter. The feature-request/enterprise-support solicitation, standalone changelog promotion and donation section have been removed.<br/>
11
17
  - **Expose KNX to Matter (BETA) — dedup for duplicate Alexa light commands**: same Alexa quirk as the covers below, on lights: a single "turn on" / "set to 50%" voice request could arrive as up to two *identical* cluster commands (two `On`, or `MoveToLevelWithOnOff` + `MoveToLevel` with the same level), making the node emit duplicate raw msgs on `OnOffLightDevice`/`DimmableLightDevice` ([discussion #516](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/discussions/516)). A short leading-edge dedup now forwards the first command immediately (no added latency — lights react instantly) and drops only a command identical to the one just emitted for the same function within the window; any change of value (a real On→Off, or a different brightness) always passes through at once, so genuine commands are never swallowed. The window is exposed in the node's **Advanced** tab as *Alexa duplicate-command window* (default 1.2s, **0 = disabled/raw**), the same approach the Home Assistant Matter Hub adopted for this Alexa behavior. The complementary *ignore brightness after On* option (already present) covers Alexa's other habit of sending a brightness right after On.<br/>
12
18
  - **Expose KNX to Matter (BETA) — Home Assistant Matter Hub-style cover compatibility**: `WindowCovering` now exposes **Swap Open / Close** and **Cover slider debounce** in the Advanced tab. Swap reverses the binary KNX Up/Down command and percentage direction symmetrically for Alexa installations that interpret them backwards. Debounce collapses rapid `GoToLiftPercentage` targets into the last KNX command; `0` uses the adaptive Home Assistant Matter Hub windows (400ms first command / 150ms subsequent commands), while `1`–`5000` selects a fixed per-cover window. `StopMotion` remains immediate and cancels every pending movement. Help HTML and Matter Bridge docs were updated in EN/IT/DE/FR/ES/zh-CN.<br/>
13
19
  - **Expose KNX to Matter (BETA) — cover "arrived" safety net**: in *Wait for KNX status* mode, if no KNX status telegram confirms the exact requested position within a generous window (45s), the node now falls back to reflecting the requested position to Matter anyway. Without this, a real actuator that never reports back the *exact* requested percentage (positioning tolerance/drift) could leave the Matter device reporting "still moving" forever, since matter.js only clears `OperationalStatus` on an exact match.<br/>
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## The most popular KNX node for Node-RED
8
8
 
9
- KNX Ultimate is the most advanced KNX integration for Node-RED, providing secure KNX/IP communication, routing, ETS project import, Philips Hue, Matter, MQTT, diagnostics, virtual devices, and powerful automation nodes. Build professional, reliable, and scalable smart home and building automation projects with minimal effort.
9
+ 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, virtual devices, and powerful automation nodes. Build professional, reliable, and scalable smart home and building automation projects with minimal effort.
10
10
 
11
11
  <br/>
12
12
  <br/>
@@ -1043,6 +1043,36 @@ module.exports = (RED) => {
1043
1043
  }
1044
1044
  })
1045
1045
 
1046
+ const exportMatterStorage = async (req, res, idKey) => {
1047
+ try {
1048
+ const configNode = RED.nodes.getNode(req.query[idKey])
1049
+ if (!configNode || typeof configNode.exportMatterStorage !== 'function') throw new Error('PLEASE DEPLOY FIRST: then try again.')
1050
+ const backup = await configNode.exportMatterStorage()
1051
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-')
1052
+ res.setHeader('Content-Type', 'application/json; charset=utf-8')
1053
+ res.setHeader('Content-Disposition', `attachment; filename="knx-ultimate-matter-${backup.kind}-${stamp}.json"`)
1054
+ res.send(JSON.stringify(backup, null, 2))
1055
+ } catch (error) {
1056
+ RED.log.error(`Err Matter storage export: ${error.message}`)
1057
+ if (!res.headersSent) res.status(500).json({ error: error.message })
1058
+ }
1059
+ }
1060
+
1061
+ const importMatterStorage = async (req, res, idKey) => {
1062
+ try {
1063
+ const configNode = RED.nodes.getNode(req.query[idKey])
1064
+ if (!configNode || typeof configNode.importMatterStorage !== 'function') throw new Error('PLEASE DEPLOY FIRST: then try again.')
1065
+ await configNode.importMatterStorage(req.body)
1066
+ res.json({ status: 'ok' })
1067
+ } catch (error) {
1068
+ RED.log.error(`Err Matter storage import: ${error.message}`)
1069
+ res.status(400).json({ error: error.message })
1070
+ }
1071
+ }
1072
+
1073
+ RED.httpAdmin.get('/KNXUltimateMatterStorageExport', normalizeAuthFromAccessTokenQuery, RED.auth.needsPermission('matter-config.read'), (req, res) => exportMatterStorage(req, res, 'serverId'))
1074
+ RED.httpAdmin.post('/KNXUltimateMatterStorageImport', RED.auth.needsPermission('matter-config.write'), (req, res) => importMatterStorage(req, res, 'serverId'))
1075
+
1046
1076
  // MATTER BRIDGE: returns the pairing info (QR code, manual code, commissioned fabrics) of a matterbridge-config node
1047
1077
  RED.httpAdmin.get('/KNXUltimateMatterBridgeInfo', RED.auth.needsPermission('matterbridge-config.read'), (req, res) => {
1048
1078
  try {
@@ -1074,6 +1104,9 @@ module.exports = (RED) => {
1074
1104
  }
1075
1105
  })
1076
1106
 
1107
+ RED.httpAdmin.get('/KNXUltimateMatterBridgeStorageExport', normalizeAuthFromAccessTokenQuery, RED.auth.needsPermission('matterbridge-config.read'), (req, res) => exportMatterStorage(req, res, 'configId'))
1108
+ RED.httpAdmin.post('/KNXUltimateMatterBridgeStorageImport', RED.auth.needsPermission('matterbridge-config.write'), (req, res) => importMatterStorage(req, res, 'configId'))
1109
+
1077
1110
  RED.httpAdmin.get('/knxUltimateDpts', (req, res) => {
1078
1111
  try {
1079
1112
  const dpts = Object.entries(dptlib.dpts).filter(onlyDptKeys).map(extractBaseNo).sort(sortBy('base'))
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="40" height="60" viewBox="0 0 40 60" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Matter Bridge">
3
+ <text x="20" y="21" fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="700" textLength="38" lengthAdjust="spacingAndGlyphs">mat</text>
4
+ <path d="M 37 34 H 12 V 29 L 2 38 L 12 47 V 42 H 25"
5
+ fill="none" stroke="#ffffff" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="40" height="60" viewBox="0 0 40 60" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Matter Controller">
3
+ <text x="20" y="21" fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="20" font-weight="700" textLength="38" lengthAdjust="spacingAndGlyphs">mat</text>
4
+ <path d="M 3 34 H 28 V 29 L 38 38 L 28 47 V 42 H 15"
5
+ fill="none" stroke="#ffffff" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
6
+ </svg>
@@ -29,6 +29,10 @@ let NODE_VERSION = 'unknown'
29
29
  try { NODE_VERSION = require('../package.json').version } catch (e) { /* empty */ }
30
30
  let KNX_ENGINE_VERSION = 'unknown'
31
31
  try { KNX_ENGINE_VERSION = require('knxultimate/package.json').version } catch (e) { /* empty */ }
32
+ let MATTER_PACKAGE_VERSION = 'unknown'
33
+ let Specification = { REVISION: 'unknown' }
34
+ try { MATTER_PACKAGE_VERSION = require('@matter/main').version } catch (e) { /* empty */ }
35
+ try { Specification = require('@matter/model').Specification } catch (e) { /* empty */ }
32
36
 
33
37
  // DATAPONT MANIPULATION HELPERS
34
38
  // ####################
@@ -193,7 +197,7 @@ const findAutoEthernetInterface = (targetIP) => {
193
197
  module.exports = (RED) => {
194
198
  // Log node and KNXUltimate engine versions once, at Node-RED startup.
195
199
  try {
196
- RED.log.info(`KNXUltimate: node-red-contrib-knx-ultimate v${NODE_VERSION} (KNXUltimate engine v${KNX_ENGINE_VERSION})`)
200
+ RED.log.info(`KNXUltimate: node-red-contrib-knx-ultimate v${NODE_VERSION} (KNXUltimate engine v${KNX_ENGINE_VERSION}, Matter protocol ${Specification.REVISION}, matter.js v${MATTER_PACKAGE_VERSION})`)
197
201
  } catch (e) { /* empty */ }
198
202
 
199
203
  function knxUltimateConfigNode (config) {
@@ -100,7 +100,7 @@
100
100
  defaults: gaDefaults,
101
101
  inputs: 0,
102
102
  outputs: 0,
103
- icon: 'node-matter-icon.svg',
103
+ icon: 'node-matter-bridge-icon.svg',
104
104
  label() {
105
105
  return this.name || 'Expose KNX to Matter (BETA)';
106
106
  },
@@ -337,11 +337,6 @@
337
337
  </ul>
338
338
 
339
339
  <div id="mb-tab-mappings" style="padding-top:12px;">
340
- <div class="form-tips" style="display:block; width:100%; max-width:none; box-sizing:border-box; margin-bottom:12px; border-left:4px solid #d9822b;">
341
- <i class="fa fa-triangle-exclamation"></i>
342
- <span data-i18n="knxUltimateMatterBridge.type_change_warning"></span>
343
- </div>
344
-
345
340
  <div id="mb-ga-fields"></div>
346
341
 
347
342
  <div class="form-row" id="mb-invert-row" style="display:none;">
@@ -18,6 +18,7 @@
18
18
  matterEndpointId: { value: 1 },
19
19
  matterDeviceName: { value: "" },
20
20
  matterDeviceCapabilities: { value: "" },
21
+ matterMappings: { value: "[]" },
21
22
  name: { value: "" },
22
23
 
23
24
  nameLightSwitch: { value: "" },
@@ -123,9 +124,12 @@
123
124
  restoreDayMode: { value: "no" }, // Starting from v 4.1.31
124
125
  updateLocalStateFromKNXWrite: { value: true } // Starting from v 4.1.31
125
126
  },
127
+ credentials: {
128
+ doorLockPin: { type: "password" }
129
+ },
126
130
  inputs: 0,
127
131
  outputs: 0,
128
- icon: "node-matter-icon.svg",
132
+ icon: "node-matter-controller-icon.svg",
129
133
  label: function () {
130
134
  return this.name || this.matterDeviceName || "Control Matter from KNX (BETA)";
131
135
  },
@@ -506,11 +510,11 @@
506
510
  }
507
511
  return val;
508
512
  };
509
- const matterTypePretty = { 'ma-extendedcolorlight': 'Colour light', 'ma-colortemperaturelight': 'Tunable white', 'ma-dimmablelight': 'Dimmable light', 'ma-onofflight': 'On/off light', 'ma-onoffpluginunit': 'Plug', 'ma-dimmablepluginunit': 'Dimmable plug' };
513
+ const matterTypePretty = { 'ma-extendedcolorlight': 'Colour light', 'ma-colortemperaturelight': 'Tunable white', 'ma-dimmablelight': 'Dimmable light', 'ma-onofflight': 'On/off light', 'ma-onoffpluginunit': 'Plug', 'ma-dimmablepluginunit': 'Dimmable plug', 'ma-doorlock': 'Door lock', 'ma-windowcovering': 'Cover', 'ma-thermostat': 'Thermostat', 'ma-fan': 'Fan', 'ma-temperaturesensor': 'Temperature sensor', 'ma-humiditysensor': 'Humidity sensor', 'ma-lightsensor': 'Light sensor', 'ma-occupancysensor': 'Occupancy sensor', 'ma-contactsensor': 'Contact sensor' };
510
514
  const matterPrettyType = (raw) => {
511
515
  const key = String(raw || '').toLowerCase();
512
516
  if (matterTypePretty[key]) return matterTypePretty[key];
513
- return String(raw || '').replace(/^ma-/i, '').replace(/light$/i, ' light') || 'Light';
517
+ return String(raw || '').replace(/^ma-/i, '').replace(/light$/i, ' light') || 'Matter endpoint';
514
518
  };
515
519
  const normalizeMatterToken = (value) => String(value || '').toLowerCase().replace(/[^a-z0-9]/g, '');
516
520
  const getMatterCluster = (ep, clusterId) => (ep.clusters || []).find((c) => Number(c.id) === Number(clusterId));
@@ -524,10 +528,64 @@
524
528
  };
525
529
  const matterCapabilitiesFromEndpoint = (ep) => {
526
530
  const deviceTypes = (ep.deviceTypes || []).map(normalizeMatterToken);
531
+ const primaryDeviceType = (ep.deviceTypes || []).find((value) => !/bridgednode|aggregator|controlbridge/i.test(String(value))) || ep.name;
532
+ const deviceTypeDisplay = matterPrettyType(primaryDeviceType);
527
533
  const typeText = deviceTypes.join(' ');
528
534
  const onOffCluster = getMatterCluster(ep, 6);
529
535
  const levelCluster = getMatterCluster(ep, 8);
530
536
  const colorCluster = getMatterCluster(ep, 768);
537
+ const doorLockCluster = getMatterCluster(ep, 257);
538
+ if (doorLockCluster) {
539
+ return {
540
+ known: true,
541
+ profile: 'doorLock',
542
+ deviceTypeDisplay,
543
+ onOff: true,
544
+ level: false,
545
+ colorTemperature: false,
546
+ color: false,
547
+ lockDoor: hasMatterCommand(doorLockCluster, ['lockDoor']),
548
+ unlockDoor: hasMatterCommand(doorLockCluster, ['unlockDoor']),
549
+ requirePinForRemoteOperation: (() => {
550
+ const attribute = (doorLockCluster.attributes || []).find((item) => normalizeMatterToken(item.name) === 'requirepinforremoteoperation');
551
+ return attribute ? attribute.value === true : false;
552
+ })()
553
+ };
554
+ }
555
+ const isMatterLight = deviceTypes.some((type) => /(?:onoff|dimmable|colortemperature|extendedcolor)light/.test(type));
556
+ if (!isMatterLight) {
557
+ const mappedTargets = [];
558
+ const addCommand = (clusterId, target, label, dpt) => {
559
+ const cluster = getMatterCluster(ep, clusterId);
560
+ if (hasMatterCommand(cluster, [target])) mappedTargets.push({ direction: 'command', endpointId: Number(ep.endpointId), clusterId, targetKind: 'command', target, label, dpt });
561
+ };
562
+ const addAttribute = (clusterId, target, label, dpt, writable) => {
563
+ const cluster = getMatterCluster(ep, clusterId);
564
+ if (hasMatterAttribute(cluster, [target])) mappedTargets.push({ direction: writable ? 'command' : 'status', endpointId: Number(ep.endpointId), clusterId, targetKind: 'attribute', target, label, dpt });
565
+ };
566
+ addCommand(6, 'on', 'onoff_command', '1.001');
567
+ addAttribute(6, 'onOff', 'onoff_state', '1.001', false);
568
+ addCommand(258, 'upOrOpen', 'cover_updown', '1.008');
569
+ addCommand(258, 'stopMotion', 'cover_stop', '1.017');
570
+ addCommand(258, 'goToLiftPercentage', 'cover_position_command', '5.001');
571
+ addAttribute(258, 'currentPositionLiftPercent100ths', 'cover_position_state', '5.001', false);
572
+ addAttribute(513, 'occupiedHeatingSetpoint', 'heating_command', '9.001', true);
573
+ addAttribute(513, 'occupiedHeatingSetpoint', 'heating_state', '9.001', false);
574
+ addAttribute(513, 'occupiedCoolingSetpoint', 'cooling_command', '9.001', true);
575
+ addAttribute(513, 'occupiedCoolingSetpoint', 'cooling_state', '9.001', false);
576
+ addAttribute(513, 'localTemperature', 'local_temperature', '9.001', false);
577
+ addAttribute(514, 'percentSetting', 'fan_command', '5.001', true);
578
+ addAttribute(514, 'percentCurrent', 'fan_state', '5.001', false);
579
+ addAttribute(1026, 'measuredValue', 'temperature', '9.001', false);
580
+ addAttribute(1029, 'measuredValue', 'humidity', '9.007', false);
581
+ addAttribute(1024, 'measuredValue', 'illuminance', '9.004', false);
582
+ addAttribute(1030, 'occupancy', 'occupancy', '1.011', false);
583
+ addAttribute(69, 'stateValue', 'contact', '1.002', false);
584
+ addAttribute(47, 'batPercentRemaining', 'battery', '5.001', false);
585
+ addAttribute(144, 'activePower', 'active_power', '14.056', false);
586
+ addAttribute(145, 'cumulativeEnergyImported', 'imported_energy', '13.013', false);
587
+ return { known: true, profile: 'mapped', deviceTypeDisplay, onOff: false, level: false, colorTemperature: false, color: false, mappedTargets };
588
+ }
531
589
  const hasColorTemperatureType = /colortemperature|extendedcolor/.test(typeText);
532
590
  const hasColorType = /extendedcolor/.test(typeText);
533
591
  const hasLevelType = /dimmable|colortemperature|extendedcolor/.test(typeText);
@@ -542,6 +600,7 @@
542
600
  );
543
601
  return {
544
602
  known: true,
603
+ deviceTypeDisplay,
545
604
  colorDetectionVersion: 2,
546
605
  onOff: !!onOffCluster || /onoff|dimmable|colortemperature|extendedcolor/.test(typeText),
547
606
  level: !!levelCluster || hasLevelType,
@@ -578,9 +637,8 @@
578
637
  });
579
638
  };
580
639
 
581
- // Matter counterpart of the Hue device fetch: every commissioned node is
582
- // expanded into its light endpoints (clusters OnOff 6 / Level 8 / Color 768),
583
- // so a bridge (e.g. a Hue Bridge paired via Matter) lists each lamp.
640
+ // Expand every commissioned node into its functional endpoints. Lights keep
641
+ // the legacy light UI; other endpoints select a capability-driven profile.
584
642
  const fetchHueDevices = (term, respond, { forceRefresh = false } = {}) => {
585
643
  const matterServer = getHueServerConfig();
586
644
  if (!matterServer) {
@@ -609,8 +667,10 @@
609
667
  const items = [];
610
668
  results.forEach((entry) => {
611
669
  const device = entry.device;
612
- const lightEps = entry.endpoints.filter((ep) => Array.isArray(ep.clusters) && ep.clusters.some((c) => [6, 8, 768].indexOf(Number(c.id)) !== -1));
613
- const eps = lightEps.length ? lightEps : entry.endpoints.filter((ep) => Number(ep.endpointId) !== 0);
670
+ // Multi-purpose controller: list every functional endpoint. Capability
671
+ // detection below decides whether it uses the unchanged light path or
672
+ // a dedicated/mapped non-light profile.
673
+ const eps = entry.endpoints.filter((ep) => Number(ep.endpointId) !== 0);
614
674
  const many = eps.length > 1;
615
675
  eps.forEach((ep) => {
616
676
  const label = matterEndpointLabel(ep);
@@ -679,7 +739,6 @@
679
739
  const $tabs = $("#tabs");
680
740
  const $pinSectionRow = $("#node-input-enableNodePINS").closest('.form-row');
681
741
  const $pinSelect = $("#node-input-enableNodePINS");
682
- const $pinInfoRow = $pinSectionRow.next('.form-tips');
683
742
  let currentMatterCapabilities = parseMatterCapabilities($matterCapabilitiesInput.val() || node.matterDeviceCapabilities);
684
743
  $tabs.addClass('hue-vertical-tabs');
685
744
  $tabs.tabs(); // Tabs gestione KNX
@@ -935,12 +994,29 @@
935
994
  const supportsTemperature = hasHueDevice && caps.colorTemperature === true;
936
995
  const supportsColor = hasHueDevice && caps.color === true;
937
996
  const hasRichBehaviour = supportsDim && (supportsTemperature || supportsColor);
997
+ const isDoorLock = caps.profile === 'doorLock';
998
+ const isMapped = caps.profile === 'mapped';
999
+ const deviceTypeDisplay = String(caps.deviceTypeDisplay || '').trim();
938
1000
 
939
1001
  setMatterTabVisible('tabs-1', supportsSwitch);
940
1002
  setMatterTabVisible('tabs-2', supportsDim);
941
1003
  setMatterTabVisible('tabs-3', supportsTemperature);
942
1004
  setMatterTabVisible('tabs-4', supportsColor);
1005
+ setMatterTabVisible('tabs-mapped', isMapped);
943
1006
  setMatterTabVisible('tabs-6', hasHueDevice);
1007
+ $('#matter-door-lock-options').toggle(isDoorLock);
1008
+ $('#matter-switch-command-label').text(isDoorLock
1009
+ ? (node._('knxUltimateMatterControllerDevice.door_lock_command') || 'Lock / unlock')
1010
+ : (node._('knxUltimateMatterControllerDevice.control') || 'Control'));
1011
+ $('#matter-switch-status-label').text(isDoorLock
1012
+ ? (node._('knxUltimateMatterControllerDevice.door_lock_status') || 'Lock state')
1013
+ : (node._('knxUltimateMatterControllerDevice.status') || 'Status'));
1014
+ $('#matter-door-lock-command-warning').toggle(isDoorLock && (caps.lockDoor === false || caps.unlockDoor === false));
1015
+ $('#matter-door-lock-pin-required').toggle(isDoorLock && caps.requirePinForRemoteOperation === true);
1016
+ $('#matter-mapped-profile').toggle(isMapped);
1017
+ if (isMapped) renderMappedProfile(caps.mappedTargets || []);
1018
+ $('#matter-selected-device-type-value').text(deviceTypeDisplay);
1019
+ $('#matter-selected-device-type').toggle(hasHueDevice && deviceTypeDisplay !== '');
944
1020
  try { $tabs.tabs('refresh'); } catch (error) { /* empty */ }
945
1021
  ensureVisibleMatterTabSelected();
946
1022
 
@@ -961,6 +1037,40 @@
961
1037
  refreshBehaviourVisibility();
962
1038
  };
963
1039
 
1040
+ const parseMatterMappings = () => {
1041
+ try { return JSON.parse($('#node-input-matterMappings').val() || node.matterMappings || '[]'); } catch (error) { return []; }
1042
+ };
1043
+ const renderMappedProfile = (targets) => {
1044
+ const saved = parseMatterMappings();
1045
+ const $body = $('#matter-mapped-profile-body').empty();
1046
+ targets.forEach((target) => {
1047
+ const key = `${target.direction}:${target.endpointId}:${target.clusterId}:${target.targetKind}:${target.target}`;
1048
+ const previous = saved.find((item) => `${item.direction}:${item.endpointId}:${item.clusterId}:${item.targetKind}:${item.target}` === key) || {};
1049
+ const $row = $('<div class="form-row matter-mapped-row" style="display:flex;align-items:center;"></div>').data('mapping', target);
1050
+ const translated = node._(`knxUltimateMatterControllerDevice.mapped.${target.label}`);
1051
+ $('<label style="width:220px; margin-right:10px;"></label>').text(translated || target.target).appendTo($row);
1052
+ $('<label style="width:25px; margin:0 5px 0 0;">GA</label>').appendTo($row);
1053
+ const $ga = $('<input class="matter-mapped-ga" type="text" placeholder="Ex: 1/1/1" style="width:100px; margin-right:18px;">').val(previous.ga || '').appendTo($row);
1054
+ $('<label style="width:35px; margin:0 5px 0 0;">DPT</label>').appendTo($row);
1055
+ const $dpt = $('<input class="matter-mapped-dpt" type="text" placeholder="DPT" style="width:140px;">').val(previous.dpt || target.dpt || '').appendTo($row);
1056
+ $ga.autocomplete({
1057
+ minLength: 0,
1058
+ source(request, response) {
1059
+ if (!hasKnxServerSelected()) { response([]); return; }
1060
+ $.getJSON(`knxUltimatecsv?nodeID=${resolveKnxServerValue()}&_=${Date.now()}`, (data) => {
1061
+ response($.map(data, (value) => {
1062
+ const searchable = `${value.ga} ${value.devicename} ${value.dpt}`;
1063
+ if (!htmlUtilsfullCSVSearch(searchable, request.term || '')) return null;
1064
+ return { label: `${value.ga} # ${value.devicename} # ${value.dpt}`, value: value.ga, dpt: value.dpt };
1065
+ }));
1066
+ }).fail(() => response([]));
1067
+ },
1068
+ select(event, ui) { if (ui.item.dpt) $dpt.val(ui.item.dpt); }
1069
+ }).focus(function () { $(this).autocomplete('search', $(this).val()); });
1070
+ $body.append($row);
1071
+ });
1072
+ };
1073
+
964
1074
  const updateTabsVisibility = () => {
965
1075
  const knxSelected = hasKnxServerSelected();
966
1076
  const hueDeviceSelected = getHueDeviceValue() !== '';
@@ -984,9 +1094,6 @@
984
1094
  if ($pinSectionRow.length) {
985
1095
  $pinSectionRow.show();
986
1096
  }
987
- if ($pinInfoRow.length) {
988
- $pinInfoRow.show();
989
- }
990
1097
  };
991
1098
  updateTabsVisibility();
992
1099
 
@@ -1325,6 +1432,20 @@
1325
1432
  this.matterNodeId = ($("#node-input-matterNodeId").val() || '').trim();
1326
1433
  this.matterEndpointId = Number($("#node-input-matterEndpointId").val() || 1);
1327
1434
  this.matterDeviceCapabilities = ($("#node-input-matterDeviceCapabilities").val() || '').trim();
1435
+ const mappedMappings = [];
1436
+ const $mappedRows = $('#matter-mapped-profile-body .matter-mapped-row');
1437
+ $mappedRows.each(function () {
1438
+ const base = $(this).data('mapping') || {};
1439
+ const ga = ($(this).find('.matter-mapped-ga').val() || '').trim();
1440
+ const dpt = ($(this).find('.matter-mapped-dpt').val() || '').trim();
1441
+ if (ga && dpt) mappedMappings.push(Object.assign({}, base, { ga, dpt }));
1442
+ });
1443
+ // Preserve saved mappings when the endpoint is offline and its asynchronous
1444
+ // structure could not be rebuilt during an untouched editor round trip.
1445
+ this.matterMappings = $mappedRows.length > 0
1446
+ ? JSON.stringify(mappedMappings)
1447
+ : ($('#node-input-matterMappings').val() || this.matterMappings || '[]');
1448
+ $('#node-input-matterMappings').val(this.matterMappings);
1328
1449
  this.name = this.matterDeviceName || this.name || '';
1329
1450
  $("#node-input-name").val(this.name);
1330
1451
  const parseSavedMatterCapabilities = (raw) => {
@@ -1550,9 +1671,15 @@
1550
1671
  <span class="hue-devices-loading" style="margin-left:6px; display:none; color:#1b7d33;">
1551
1672
  <i class="fa fa-circle-notch fa-spin"></i>
1552
1673
  </span>
1674
+ <span id="matter-selected-device-type" style="display:none; margin-left:10px; color:#666; white-space:nowrap;">
1675
+ <i class="fa fa-microchip"></i>
1676
+ <span data-i18n="knxUltimateMatterControllerDevice.device_type"></span>:
1677
+ <span id="matter-selected-device-type-value" style="font-weight:600;"></span>
1678
+ </span>
1553
1679
  <input type="hidden" id="node-input-matterNodeId" />
1554
1680
  <input type="hidden" id="node-input-matterEndpointId" />
1555
1681
  <input type="hidden" id="node-input-matterDeviceCapabilities" />
1682
+ <input type="hidden" id="node-input-matterMappings" />
1556
1683
  <input type="hidden" id="node-input-name" />
1557
1684
  </div>
1558
1685
 
@@ -1564,12 +1691,18 @@
1564
1691
  <li><a href="#tabs-2"><i class="fa-solid fa-arrow-up-wide-short"></i> <span data-i18n="knxUltimateMatterControllerDevice.tabs.dim"></span></a></li>
1565
1692
  <li><a href="#tabs-3"><i class="fa-solid fa-temperature-quarter"></i> <span data-i18n="knxUltimateMatterControllerDevice.tabs.tunable_white"></span></a></li>
1566
1693
  <li><a href="#tabs-4"><i class="fa-solid fa-palette"></i> <span data-i18n="knxUltimateMatterControllerDevice.tabs.rgb_hsv"></span></a></li>
1694
+ <li><a href="#tabs-mapped"><i class="fa-solid fa-list-check"></i> <span data-i18n="knxUltimateMatterControllerDevice.tabs.mappings"></span></a></li>
1567
1695
  <li><a href="#tabs-6"><i class="fa-solid fa-code-merge"></i> <span data-i18n="knxUltimateMatterControllerDevice.tabs.behaviour"></span></a></li>
1568
1696
  </ul>
1697
+ <div id="tabs-mapped">
1698
+ <div id="matter-mapped-profile" style="display:none;">
1699
+ <div id="matter-mapped-profile-body"></div>
1700
+ </div>
1701
+ </div>
1569
1702
  <div id="tabs-1">
1570
1703
  <p>
1571
1704
  <div class="form-row">
1572
- <label for="node-input-nameLightSwitch" style="width:110px;"><i class="fa fa-play-circle-o"></i> <span data-i18n="knxUltimateMatterControllerDevice.control"></span></label>
1705
+ <label for="node-input-nameLightSwitch" style="width:110px;"><i class="fa fa-play-circle-o"></i> <span id="matter-switch-command-label" data-i18n="knxUltimateMatterControllerDevice.control"></span></label>
1573
1706
 
1574
1707
  <label for="node-input-GALightSwitch" style="width:20px;"><span data-i18n="common.ga"></span></label>
1575
1708
  <input type="text" id="node-input-GALightSwitch" placeholder="Ex: 1/1/1"
@@ -1582,7 +1715,7 @@
1582
1715
  <input type="text" id="node-input-nameLightSwitch" style="width:190px;margin-left: 5px; text-align: left;">
1583
1716
  </div>
1584
1717
  <div class="form-row">
1585
- <label for="node-input-nameLightState" style="width:110px;"><i class="fa fa-question-circle"></i> <span data-i18n="knxUltimateMatterControllerDevice.status"></span></label>
1718
+ <label for="node-input-nameLightState" style="width:110px;"><i class="fa fa-question-circle"></i> <span id="matter-switch-status-label" data-i18n="knxUltimateMatterControllerDevice.status"></span></label>
1586
1719
 
1587
1720
  <label for="node-input-GALightState" style="width:20px;"><span data-i18n="common.ga"></span></label>
1588
1721
  <input type="text" id="node-input-GALightState" placeholder="Ex: 1/1/1"
@@ -1595,6 +1728,14 @@
1595
1728
  data-i18n="knxUltimateMatterControllerDevice.node-input-name"></span></label>
1596
1729
  <input type="text" id="node-input-nameLightState" style="width:190px;margin-left: 5px; text-align: left;">
1597
1730
  </div>
1731
+ <div id="matter-door-lock-options" style="display:none;">
1732
+ <div class="form-row">
1733
+ <label for="node-input-doorLockPin" style="width:260px;"><i class="fa fa-key"></i> <span data-i18n="knxUltimateMatterControllerDevice.door_lock_pin"></span></label>
1734
+ <input type="password" id="node-input-doorLockPin" style="width:210px;" autocomplete="new-password">
1735
+ </div>
1736
+ <div id="matter-door-lock-pin-required" class="form-tips" style="display:none;" data-i18n="knxUltimateMatterControllerDevice.door_lock_pin_required"></div>
1737
+ <div id="matter-door-lock-command-warning" class="form-tips" style="display:none; color:#a94442;" data-i18n="knxUltimateMatterControllerDevice.door_lock_commands_missing"></div>
1738
+ </div>
1598
1739
  </p>
1599
1740
  </div>
1600
1741
  <div id="tabs-2">
@@ -1899,7 +2040,6 @@
1899
2040
  </label>
1900
2041
  <input type="checkbox" id="node-input-updateLocalStateFromKNXWrite" style="display:inline-block; width:auto; vertical-align:top;" />
1901
2042
  </div>
1902
- <div class="form-tips" style="margin-left:260px;" data-i18n="knxUltimateMatterControllerDevice.update_local_state_from_knx_write_hint"></div><br/>
1903
2043
  <div id ="divBehaviourBrightness">
1904
2044
  <div class="form-row">
1905
2045
  <label for="node-input-specifySwitchOnBrightness" style="width:260px;">
@@ -2116,4 +2256,4 @@ _Non-Hue basic effects_
2116
2256
  The Dimming function works in **KNX mode `start` and`stop` ** . To start dimming, send only one "start" KNX telegram. To stop dimming, send a "stop" KNX telegram. Please**remember that** , when you set your wall swiches properties.
2117
2257
 
2118
2258
  <br/>
2119
- </script>
2259
+ </script>
@@ -9,6 +9,7 @@ const dptlib = require('knxultimate').dptlib
9
9
  const hueColorConverter = require('./utils/colorManipulators/hueColorConverter')
10
10
  const { createLightEngine } = require('./utils/lightEngines')
11
11
  const { hueStateToCanonical, matterEventToHuePatch } = require('./utils/lightEngines/matterHueShim')
12
+ const { setupMatterControllerProfile } = require('./utils/matterControllerProfiles')
12
13
 
13
14
  module.exports = function (RED) {
14
15
  function knxUltimateMatterControllerDevice (config) {
@@ -20,6 +21,10 @@ module.exports = function (RED) {
20
21
  node.matterEndpointId = config.matterEndpointId !== undefined ? Number(config.matterEndpointId) : 1
21
22
  node.matterDeviceName = config.matterDeviceName !== undefined ? String(config.matterDeviceName) : ''
22
23
 
24
+ let matterCapabilities = {}
25
+ try { matterCapabilities = JSON.parse(config.matterDeviceCapabilities || '{}') } catch (error) { /* empty */ }
26
+ if (setupMatterControllerProfile(matterCapabilities.profile, RED, node, config)) return
27
+
23
28
  // Matter engine (adapter). The manager is resolved lazily at each write, because
24
29
  // matter-config creates its matterManager asynchronously after the deploy.
25
30
  node.lightEngine = createLightEngine('matter', {
@@ -9,6 +9,8 @@ Jeder Expose KNX to Matter Node stellt **ein KNX-Gerät als Matter-Gerät** bere
9
9
 
10
10
  Das ist die Gegenrichtung zum *Matter Device*-Node: Dort steuert KNX ein Matter-Gerät, hier steuern Matter-Controller KNX.
11
11
 
12
+ Das Ändern des Gerätetyps nach der Kopplung der Bridge verändert die Matter-Endpunktstruktur. Controller können den alten Endpunkt als nicht erreichbar behalten; dann die Bridge zurücksetzen/neu koppeln oder ein neues bereitgestelltes Gerät erstellen.
13
+
12
14
  ## Konfiguration
13
15
 
14
16
  |Feld|Beschreibung|
@@ -15,6 +15,8 @@ Er ersetzt die unveröffentlichten getrennten Matter-Controller-Nodes und behäl
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`. |
18
+ | Türschloss | Eine DPT-`1.xxx`-Befehls-GA ruft bei `true` `lockDoor` und bei `false` `unlockDoor` auf; eine separate Status-GA erhält nur eindeutige Verriegelt/Entriegelt-Zustände. Falls erforderlich, wird die Fernbedienungs-PIN im Credential-Feld gespeichert. Nicht angebotene Befehle werden abgewiesen. |
19
+ | Andere Endpunkte | Steckdosen, Ein/Aus-Aktoren, Jalousien, Thermostate, Lüfter, Umwelt-/Kontakt-/Belegungssensoren sowie Batterie-, Leistungs- und Energieendpunkte verwenden das Mehrzweckprofil. Der Tab **Zuordnungen** enthält nur tatsächlich angebotene Cluster, Attribute und Befehle. |
18
20
  | Lichtsteuerung | Für Licht-Endpunkte wird die vollständige Licht-UI verwendet: relatives DIM (DPT `3.007`), Helligkeit %, RGB/HSV, Tunable White, Einschalt-Helligkeit/-Temperatur, Tag/Nacht-Licht, Min/Max-Dimmlevel und Dimmgeschwindigkeit. Nicht unterstützte Bereiche bleiben ausgeblendet. |
19
21
  | Sensoren | Sensor-Endpunkte zeigen ihre Mess-/Status-GA nur bei Unterstützung: Temperatur, Feuchte, Helligkeit, Präsenz, Kontakt und Batterie. |
20
22
  | Read at startup | Veröffentlicht den gecachten Matter-Wert beim Deploy/Start oder wenn sich das Gerät erneut verbindet. |
@@ -17,10 +17,19 @@
17
17
  "tunable_white": "Einstellbares Weiß",
18
18
  "rgb_hsv": "RGB/HSV",
19
19
  "effects": "Effekte",
20
+ "mappings": "Zuordnungen",
20
21
  "behaviour": "Verhalten"
21
22
  },
22
23
  "control": "Steuerung",
23
24
  "status": "Status",
25
+ "door_lock_command": "Verriegeln / entriegeln",
26
+ "door_lock_status": "Schlossstatus",
27
+ "door_lock_pin": "PIN für Fernbedienung",
28
+ "door_lock_pin_required": "Dieses Schloss meldet, dass für die Fernbedienung eine PIN erforderlich ist.",
29
+ "door_lock_commands_missing": "Dieser Endpunkt bietet nicht beide Befehle zum Verriegeln und Entriegeln an. Fehlende Befehle werden abgewiesen.",
30
+ "mapped": {
31
+ "onoff_command": "Ein/Aus-Befehl", "onoff_state": "Ein/Aus-Status", "cover_updown": "Jalousie auf/ab", "cover_stop": "Jalousie Stopp", "cover_position_command": "Positionsbefehl", "cover_position_state": "Positionsstatus", "heating_command": "Heizsollwert-Befehl", "heating_state": "Heizsollwert-Status", "cooling_command": "Kühlsollwert-Befehl", "cooling_state": "Kühlsollwert-Status", "local_temperature": "Lokale Temperatur", "fan_command": "Lüfterdrehzahl-Befehl", "fan_state": "Lüfterdrehzahl-Status", "temperature": "Temperatur", "humidity": "Luftfeuchte", "illuminance": "Beleuchtungsstärke", "occupancy": "Belegung", "contact": "Binär-/Kontaktstatus", "battery": "Batterie", "active_power": "Wirkleistung", "imported_energy": "Bezogene Energie"
32
+ },
24
33
  "night_lighting": "Nachtbeleuchtung",
25
34
  "no_night_lighting": "Keine Nachtbeleuchtung",
26
35
  "get_current": "Aktuellen Wert holen",
@@ -82,6 +91,7 @@
82
91
  "effect_native_label": "Hue-native Effekte",
83
92
  "matter_controller": "Matter-Controller",
84
93
  "matter_device": "Matter-Gerät",
94
+ "device_type": "Gerätetyp",
85
95
  "matter_section": "Matter-Gerät"
86
96
  },
87
97
  "common": {
@@ -15,10 +15,14 @@ Der Controller kommuniziert mit den Geräten über das **IP-Netzwerk** (WLAN, Et
15
15
 
16
16
  Wenn das Gerät fabrikneu ist und nur Bluetooth-Kommissionierung unterstützt, kopple es zuerst mit der Hersteller-App oder einem anderen Matter-Controller (Alexa, Google Home, Apple Home) und nutze dann dessen Funktion **"mit weiterem Hub teilen"**, um einen neuen Kopplungscode für KNX-Ultimate zu erzeugen. So tritt das Gerät mehreren Fabrics gleichzeitig bei.
17
17
 
18
+ Bevorzuge den QR-Payload (`MT:...`): Er enthält den vollständigen Diskriminator. Der manuelle Code enthält nur den kurzen Diskriminator und kann bei mehreren identischen Modellen im Kopplungsmodus das falsche Gerät auswählen. Immer nur ein Gerät gleichzeitig koppeln.
19
+
18
20
  ## Speicherung
19
21
 
20
22
  Die Fabric-Zugangsdaten und die gekoppelten Geräte werden im Ordner `knxultimatestorage/matter` im Node-RED-Benutzerverzeichnis gespeichert. Das Löschen dieses Ordners entfernt alle Kopplungen.
21
23
 
24
+ Mit **Exportieren** wird eine vollständige Sicherung dieser Controller-Instanz heruntergeladen. Sie enthält Fabric, private Zugangsdaten, Sitzungen und Daten gekoppelter Geräte. **Die Datei wie ein Passwort schützen.** Der Import ersetzt den Matter-Speicher dieser Instanz und startet den Controller kurz neu. Eine Controller-Sicherung kann nicht in eine Bridge importiert werden.
25
+
22
26
  ## Ein Gerät entfernen
23
27
 
24
28
  Nutze den Papierkorb-Button in der Liste der gekoppelten Geräte. Der Controller versucht, das Gerät sauber zu dekommissionieren; ist es nicht erreichbar, wird es trotzdem aus der Fabric entfernt (danach kann ein Werksreset des Geräts nötig sein).
@@ -2,7 +2,6 @@
2
2
  "matter-config": {
3
3
  "properties": {
4
4
  "title": "Matter Controller",
5
- "intro": "Dieser Node ist ein Matter-Controller (mit eigener Fabric). Kopple hier deine Matter-Geräte und verwende dann den \"Matter Device\"-Node, um sie auf KNX-Gruppenadressen abzubilden. Zuerst deployen, dann hierher zurückkehren, um Geräte zu koppeln.",
6
5
  "node-config-input-name": "Name",
7
6
  "fabric_label": "Fabric-Bezeichnung",
8
7
  "pairing_title": "Neues Gerät koppeln",
@@ -24,7 +23,13 @@
24
23
  "pairing_ok": "Gerät erfolgreich gekoppelt!",
25
24
  "rename_button": "Umbenennen",
26
25
  "rename_prompt": "Neuer Gerätename",
27
- "rename_ok": "Gerät umbenannt."
26
+ "rename_ok": "Gerät umbenannt.",
27
+ "storage_title": "Sicherung von Fabric und Inbetriebnahme",
28
+ "storage_export": "Exportieren",
29
+ "storage_import": "Importieren",
30
+ "storage_warning": "Die Sicherung enthält Fabric-Zugangsdaten, private Schlüssel und Daten gekoppelter Geräte. Wie ein Passwort schützen. Der Import ersetzt den Matter-Speicher dieses Controllers und startet ihn kurz neu.",
31
+ "storage_import_confirm": "Matter-Fabric und gekoppelte Geräte dieses Controllers durch die gewählte Sicherung ersetzen?",
32
+ "storage_import_ok": "Matter-Sicherung importiert. Der Controller wurde neu gestartet."
28
33
  }
29
34
  }
30
35
  }
@@ -21,12 +21,16 @@ Dieser Konfigurations-Node ist die **Matter-Bridge selbst**: Er betreibt den Mat
21
21
  2. Das Kopplungs-Panel zeigt den **QR-Code** und den **manuellen Code**: scannen oder in Alexa / Google Home / Apple Home eingeben ("Matter-Gerät hinzufügen").
22
22
  3. Mehrere Controller können mit derselben Bridge gekoppelt werden (Matter Multi-Fabric).
23
23
 
24
+ Um nach dem Ausblenden des QR-Codes einen weiteren Controller hinzuzufügen, den Kopplungsmodus über einen bereits gekoppelten Controller öffnen und dann im neuen Controller ein Matter-Gerät hinzufügen. **Kopplung zurücksetzen** nur verwenden, um alle vorhandenen Controller zu entfernen und neu zu beginnen.
25
+
24
26
  Der Button **Kopplung zurücksetzen** entfernt alle gekoppelten Controller und startet die Kopplungs-Ankündigung neu.
25
27
 
26
28
  ## Identität und Speicherung
27
29
 
28
30
  Die Bridge-Identität ist an diesen Konfigurations-Node gebunden und wird in `knxultimatestorage/matter` im Node-RED-Benutzerverzeichnis gespeichert: Re-Deploys (auch mit geändertem Port oder Namen) erfordern **KEINE** neue Kopplung. Nur das Löschen dieses Konfigurations-Nodes und das Anlegen eines neuen ändert die Identität — in dem Fall die alte Bridge aus der Matter-App entfernen und neu koppeln.
29
31
 
32
+ Mit **Exportieren** wird eine vollständige Sicherung dieser Bridge-Instanz heruntergeladen, einschließlich Fabrics, privater Zugangsdaten, Sitzungen und Kopplungsdaten. **Die Datei wie ein Passwort schützen.** Der Import ersetzt den Speicher dieser Instanz und startet die Bridge kurz neu. Eine Bridge-Sicherung kann nicht in einen Controller importiert werden.
33
+
30
34
  ## Hinweise
31
35
 
32
36
  - Der Node-RED-Host muss **IPv6 link-local** aktiviert haben (Standard-Matter-Anforderung) und von den Controllern im lokalen Netzwerk erreichbar sein.
@@ -16,7 +16,10 @@
16
16
  "deploy_first": "Bitte zuerst diesen Konfigurations-Node deployen und dann auf Aktualisieren klicken.",
17
17
  "reset_button": "Kopplung zurücksetzen",
18
18
  "reset_confirm": "ALLE gekoppelten Controller entfernen und die Kopplung neu starten? Die Geräte verschwinden aus Alexa/Google/Apple.",
19
- "reset_ok": "Kopplung zurückgesetzt. Die Bridge wartet wieder auf Kopplung."
19
+ "reset_ok": "Kopplung zurückgesetzt. Die Bridge wartet wieder auf Kopplung.",
20
+ "storage_title": "Sicherung von Fabric und Kopplung", "storage_export": "Exportieren", "storage_import": "Importieren",
21
+ "storage_warning": "Die Sicherung enthält Fabric-Zugangsdaten, private Schlüssel und Kopplungsdaten. Wie ein Passwort schützen. Der Import ersetzt den Matter-Speicher dieser Bridge und startet sie kurz neu.",
22
+ "storage_import_confirm": "Matter-Fabrics und gekoppelte Controller dieser Bridge durch die gewählte Sicherung ersetzen?", "storage_import_ok": "Matter-Sicherung importiert. Die Bridge wurde neu gestartet."
20
23
  }
21
24
  }
22
25
  }
@@ -9,6 +9,8 @@ Each Expose KNX to Matter node exposes **one KNX device as a Matter device**: th
9
9
 
10
10
  This is the opposite direction of the *Matter Device* node: there KNX controls a Matter device, here the Matter controllers control KNX.
11
11
 
12
+ Changing the device type after the bridge has been paired changes the Matter endpoint structure. Controllers may keep the old endpoint as unreachable; in that case reset/re-pair the bridge or create a new exposed device.
13
+
12
14
  ## Configuration
13
15
 
14
16
  |Field|Description|