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.
- package/CHANGELOG.md +8 -2
- package/README.md +1 -1
- package/nodes/commonFunctions.js +33 -0
- package/nodes/icons/node-matter-bridge-icon.svg +6 -0
- package/nodes/icons/node-matter-controller-icon.svg +6 -0
- package/nodes/knxUltimate-config.js +5 -1
- package/nodes/knxUltimateMatterBridge.html +1 -6
- package/nodes/knxUltimateMatterControllerDevice.html +156 -16
- package/nodes/knxUltimateMatterControllerDevice.js +5 -0
- package/nodes/locales/de/knxUltimateMatterBridge.html +2 -0
- package/nodes/locales/de/knxUltimateMatterControllerDevice.html +2 -0
- package/nodes/locales/de/knxUltimateMatterControllerDevice.json +10 -0
- package/nodes/locales/de/matter-config.html +4 -0
- package/nodes/locales/de/matter-config.json +7 -2
- package/nodes/locales/de/matterbridge-config.html +4 -0
- package/nodes/locales/de/matterbridge-config.json +4 -1
- package/nodes/locales/en/knxUltimateMatterBridge.html +2 -0
- package/nodes/locales/en/knxUltimateMatterControllerDevice.html +2 -0
- package/nodes/locales/en/knxUltimateMatterControllerDevice.json +10 -0
- package/nodes/locales/en/matter-config.html +4 -0
- package/nodes/locales/en/matter-config.json +7 -2
- package/nodes/locales/en/matterbridge-config.html +4 -0
- package/nodes/locales/en/matterbridge-config.json +4 -1
- package/nodes/locales/es/knxUltimateMatterBridge.html +2 -0
- package/nodes/locales/es/knxUltimateMatterControllerDevice.html +2 -0
- package/nodes/locales/es/knxUltimateMatterControllerDevice.json +10 -0
- package/nodes/locales/es/matter-config.html +4 -0
- package/nodes/locales/es/matter-config.json +7 -2
- package/nodes/locales/es/matterbridge-config.html +4 -0
- package/nodes/locales/es/matterbridge-config.json +4 -1
- package/nodes/locales/fr/knxUltimateMatterBridge.html +2 -0
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +2 -0
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +10 -0
- package/nodes/locales/fr/matter-config.html +4 -0
- package/nodes/locales/fr/matter-config.json +7 -2
- package/nodes/locales/fr/matterbridge-config.html +4 -0
- package/nodes/locales/fr/matterbridge-config.json +4 -1
- package/nodes/locales/it/knxUltimateMatterBridge.html +2 -0
- package/nodes/locales/it/knxUltimateMatterControllerDevice.html +2 -0
- package/nodes/locales/it/knxUltimateMatterControllerDevice.json +10 -0
- package/nodes/locales/it/matter-config.html +4 -0
- package/nodes/locales/it/matter-config.json +7 -2
- package/nodes/locales/it/matterbridge-config.html +4 -0
- package/nodes/locales/it/matterbridge-config.json +4 -1
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +2 -0
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +10 -0
- package/nodes/locales/zh-CN/matter-config.html +4 -0
- package/nodes/locales/zh-CN/matter-config.json +7 -2
- package/nodes/locales/zh-CN/matterbridge-config.html +4 -0
- package/nodes/locales/zh-CN/matterbridge-config.json +4 -1
- package/nodes/matter-config.html +59 -18
- package/nodes/matter-config.js +33 -0
- package/nodes/matterbridge-config.html +73 -3
- package/nodes/matterbridge-config.js +38 -0
- package/nodes/utils/matterControllerProfiles/doorLock.js +210 -0
- package/nodes/utils/matterControllerProfiles/index.js +21 -0
- package/nodes/utils/matterControllerProfiles/mappedEndpoint.js +148 -0
- package/nodes/utils/matterStorageBackup.js +100 -0
- package/package.json +6 -2
- 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.
|
|
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/>
|
package/nodes/commonFunctions.js
CHANGED
|
@@ -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') || '
|
|
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
|
-
//
|
|
582
|
-
//
|
|
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
|
-
|
|
613
|
-
|
|
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|
|