node-red-contrib-knx-ultimate 6.0.4 → 6.0.6
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 +16 -0
- 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 +39 -21
- package/nodes/knxUltimateMatterBridge.js +70 -15
- package/nodes/knxUltimateMatterControllerDevice.html +2 -7
- package/nodes/locales/de/knxUltimateMatterBridge.html +4 -2
- package/nodes/locales/de/knxUltimateMatterBridge.json +13 -10
- 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 +4 -2
- package/nodes/locales/en/knxUltimateMatterBridge.json +13 -10
- 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 +4 -2
- package/nodes/locales/es/knxUltimateMatterBridge.json +13 -10
- 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 +4 -2
- package/nodes/locales/fr/knxUltimateMatterBridge.json +13 -10
- 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 +4 -2
- package/nodes/locales/it/knxUltimateMatterBridge.json +13 -10
- 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 +4 -2
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +13 -10
- 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/matterBridgeDeviceFactory.mjs +244 -130
- package/nodes/utils/matterBridgeEngine.mjs +27 -22
- package/nodes/utils/matterEngine.mjs +13 -7
- package/nodes/utils/matterEnvironmentLock.mjs +32 -0
- package/nodes/utils/matterStorageBackup.js +100 -0
- package/package.json +1 -1
- package/resources/htmlUtils.js +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 6.0.6** - July 2026<br/>
|
|
10
|
+
|
|
11
|
+
- **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/>
|
|
12
|
+
- **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/>
|
|
13
|
+
- **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/>
|
|
14
|
+
- **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/>
|
|
15
|
+
- **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/>
|
|
16
|
+
- **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/>
|
|
17
|
+
- **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/>
|
|
18
|
+
- **Expose KNX to Matter (BETA) — raw color commands**: RGB and tunable-white lights now capture color commands at the same "raw" command boundary already used for On/Off, dimming and covers - a repeated color command (e.g. the same color sent twice) is forwarded on the node's optional output PIN every time, not just on the first. Only possible for ColorControl because it is command-based (`MoveToHue`, `MoveToSaturation`, `MoveToHueAndSaturation`, `MoveToColor`, `MoveToColorTemperature`); Thermostat setpoints and Fan speed remain attribute-write only in the Matter spec, so they still report only on actual value changes - there is no lower-level hook matter.js exposes for those.<br/>
|
|
19
|
+
- **Expose KNX to Matter (BETA) — fixed WindowCovering percentage commands**: `GoToLiftPercentage` commands (e.g. "Alexa, set the cover to 50%") were being misread as plain Open/Close commands, discarding the requested percentage. matter.js always resolves the command's `direction` parameter to a concrete Open/Close before calling our handler — it is not a usable signal — while `targetPercent100ths` is always populated. The node now classifies purely on the target position (0/10000 → Up/Down GA, anything else → Position GA), fixing intermediate cover positions from any Matter controller. Thanks to [@till69](https://github.com/till69) for tracing this down in [discussion #516](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/discussions/516).<br/>
|
|
20
|
+
- **Expose KNX to Matter (BETA) — removed the "Expose as dimmable light" Alexa workaround**: now that the actual cause of Alexa's broken cover percentages is fixed (see above), the v6.0.3 workaround that exposed covers as dimmable lights is no longer needed and has been removed, including its editor checkbox. **Breaking for the small number of BETA users who enabled it**: existing flows with the option turned on simply revert to a normal Matter Window Covering device on next deploy - you may need to remove/re-add the accessory in your controller's app.<br/>
|
|
21
|
+
- **Expose KNX to Matter (BETA) — heating/cooling thermostats**: the bridged Thermostat can now expose a **Cooling setpoint** in addition to the existing heating one. Add a *Cooling setpoint command/status GA* on the device to automatically upgrade the Matter endpoint to a dual-mode (Heat/Cool) thermostat; leaving it empty keeps the previous heating-only behavior unchanged. Mode (Heat/Cool) switches from the controller are accepted but not yet mirrored back to KNX.<br/>
|
|
22
|
+
- **Expose KNX to Matter (BETA) — stable per-device identity**: bridged devices now also set the optional `UniqueID` of `BridgedDeviceBasicInformation` (matching the existing serial number), improving cross-reset device identity for controllers that rely on it (e.g. Apple Home) alongside the endpoint number.<br/>
|
|
23
|
+
- **Matter Bridge/Controller — safer shared storage initialization**: the `storage.path` write on the process-wide matter.js `Environment.default`, followed by the `ServerNode`/`CommissioningController` creation, is now serialized through a small FIFO lock (`matterEnvironmentLock.mjs`) shared by both engines, removing a theoretical race between a concurrently starting Matter Bridge and Matter Controller.<br/>
|
|
24
|
+
|
|
9
25
|
**Version 6.0.4** - July 2026<br/>
|
|
10
26
|
|
|
11
27
|
- **Expose KNX to Matter (BETA) — clearer editor layout**: reorganized the device editor into dedicated **KNX mappings** and **Advanced options** tabs, following the established KNXUltimate node UI. Type-specific compatibility settings are easier to find, and checkbox labels now include icons with the checkbox positioned after the text. All saved fields and runtime behavior remain unchanged.<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) {
|
|
@@ -50,7 +50,9 @@
|
|
|
50
50
|
thermostat: [
|
|
51
51
|
{ key: 'CurrentTemp', labelKey: 'fn_currenttemp', dptPrefix: '9.', fallbackDpt: '9.001' },
|
|
52
52
|
{ key: 'Setpoint', labelKey: 'fn_setpoint_cmd', dptPrefix: '9.', fallbackDpt: '9.001' },
|
|
53
|
-
{ key: 'SetpointStatus', labelKey: 'fn_setpoint_status', dptPrefix: '9.', fallbackDpt: '9.001' }
|
|
53
|
+
{ key: 'SetpointStatus', labelKey: 'fn_setpoint_status', dptPrefix: '9.', fallbackDpt: '9.001' },
|
|
54
|
+
{ key: 'CoolingSetpoint', labelKey: 'fn_coolingsetpoint_cmd', dptPrefix: '9.', fallbackDpt: '9.001' },
|
|
55
|
+
{ key: 'CoolingSetpointStatus', labelKey: 'fn_coolingsetpoint_status', dptPrefix: '9.', fallbackDpt: '9.001' }
|
|
54
56
|
],
|
|
55
57
|
smokecoalarm: [
|
|
56
58
|
{ key: 'Smoke', labelKey: 'fn_smoke_ga', dptPrefix: '1.', fallbackDpt: '1.005' },
|
|
@@ -75,7 +77,9 @@
|
|
|
75
77
|
name: { value: '' },
|
|
76
78
|
deviceType: { value: 'onofflight' },
|
|
77
79
|
invertPosition: { value: false },
|
|
78
|
-
|
|
80
|
+
coverSwapOpenClose: { value: false },
|
|
81
|
+
coverSliderDebounceMs: { value: 0 },
|
|
82
|
+
commandDedupMs: { value: 1200 },
|
|
79
83
|
turnOnBehavior: { value: 'ignoreLevelAfterOn' },
|
|
80
84
|
ignoreLevelAfterOnMs: { value: 800 },
|
|
81
85
|
coverUpdateMode: { value: 'optimistic' },
|
|
@@ -96,7 +100,7 @@
|
|
|
96
100
|
defaults: gaDefaults,
|
|
97
101
|
inputs: 0,
|
|
98
102
|
outputs: 0,
|
|
99
|
-
icon: 'node-matter-icon.svg',
|
|
103
|
+
icon: 'node-matter-bridge-icon.svg',
|
|
100
104
|
label() {
|
|
101
105
|
return this.name || 'Expose KNX to Matter (BETA)';
|
|
102
106
|
},
|
|
@@ -147,6 +151,7 @@
|
|
|
147
151
|
const $fields = $('#mb-ga-fields').css({ display: 'flex', flexWrap: 'wrap', alignItems: 'flex-end', gap: '10px' });
|
|
148
152
|
const $invertRow = $('#mb-invert-row');
|
|
149
153
|
const $advancedRows = $('#mb-compatibility-options');
|
|
154
|
+
const $commandAdvancedRows = $('.mb-command-advanced-row');
|
|
150
155
|
const $dimmerAdvancedRows = $('.mb-dimmer-advanced-row');
|
|
151
156
|
const $coverAdvancedRows = $('.mb-cover-advanced-row');
|
|
152
157
|
const gaInputs = {};
|
|
@@ -220,7 +225,11 @@
|
|
|
220
225
|
$invertRow.toggle(selected === 'windowcovering');
|
|
221
226
|
const hasLevel = selected === 'dimmablelight' || selected === 'rgblight' || selected === 'colortemperaturelight';
|
|
222
227
|
const isCover = selected === 'windowcovering';
|
|
223
|
-
|
|
228
|
+
// The Alexa duplicate-command dedup applies to any device that emits On/Off or
|
|
229
|
+
// level commands (plain on/off lights and plugs included), not just dimmers.
|
|
230
|
+
const hasCommand = selected === 'onofflight' || selected === 'onoffplug' || hasLevel;
|
|
231
|
+
$advancedRows.toggle(hasCommand || isCover);
|
|
232
|
+
$commandAdvancedRows.toggle(hasCommand);
|
|
224
233
|
$dimmerAdvancedRows.toggle(hasLevel);
|
|
225
234
|
$coverAdvancedRows.toggle(isCover);
|
|
226
235
|
}
|
|
@@ -232,7 +241,9 @@
|
|
|
232
241
|
node._matterGaInputs = gaInputs;
|
|
233
242
|
|
|
234
243
|
$('#node-input-invertPosition').prop('checked', node.invertPosition === true);
|
|
235
|
-
$('#node-input-
|
|
244
|
+
$('#node-input-coverSwapOpenClose').prop('checked', node.coverSwapOpenClose === true);
|
|
245
|
+
$('#node-input-coverSliderDebounceMs').val(node.coverSliderDebounceMs === undefined ? 0 : node.coverSliderDebounceMs);
|
|
246
|
+
$('#node-input-commandDedupMs').val(node.commandDedupMs === undefined ? 1200 : node.commandDedupMs);
|
|
236
247
|
$('#node-input-turnOnBehavior').val(node.turnOnBehavior || 'ignoreLevelAfterOn');
|
|
237
248
|
$('#node-input-ignoreLevelAfterOnMs').val(node.ignoreLevelAfterOnMs === undefined ? 800 : node.ignoreLevelAfterOnMs);
|
|
238
249
|
$('#node-input-coverUpdateMode').val(node.coverUpdateMode || 'optimistic');
|
|
@@ -246,7 +257,7 @@
|
|
|
246
257
|
const selected = $('#node-input-deviceType').val();
|
|
247
258
|
node.deviceType = selected;
|
|
248
259
|
node.invertPosition = $('#node-input-invertPosition').is(':checked');
|
|
249
|
-
node.
|
|
260
|
+
node.coverSwapOpenClose = $('#node-input-coverSwapOpenClose').is(':checked');
|
|
250
261
|
const active = TYPE_FIELDS[selected] || [];
|
|
251
262
|
const gaInputs = node._matterGaInputs || {};
|
|
252
263
|
// Clear every GA/DPT, then write only the ones of the selected type, reading
|
|
@@ -264,6 +275,11 @@
|
|
|
264
275
|
}
|
|
265
276
|
});
|
|
266
277
|
delete node._matterGaInputs;
|
|
278
|
+
// Keep an explicit 0 (dedup disabled); treat only an empty/invalid field as the default.
|
|
279
|
+
const dedupRaw = $('#node-input-commandDedupMs').val();
|
|
280
|
+
this.commandDedupMs = (dedupRaw === '' || dedupRaw === null || isNaN(Number(dedupRaw))) ? 1200 : Math.max(0, Number(dedupRaw));
|
|
281
|
+
const coverDebounceRaw = $('#node-input-coverSliderDebounceMs').val();
|
|
282
|
+
this.coverSliderDebounceMs = (coverDebounceRaw === '' || coverDebounceRaw === null || isNaN(Number(coverDebounceRaw))) ? 0 : Math.min(5000, Math.max(0, Number(coverDebounceRaw)));
|
|
267
283
|
this.turnOnBehavior = $('#node-input-turnOnBehavior').val() || 'ignoreLevelAfterOn';
|
|
268
284
|
this.ignoreLevelAfterOnMs = Number($('#node-input-ignoreLevelAfterOnMs').val()) || 0;
|
|
269
285
|
this.coverUpdateMode = $('#node-input-coverUpdateMode').val() || 'optimistic';
|
|
@@ -321,11 +337,6 @@
|
|
|
321
337
|
</ul>
|
|
322
338
|
|
|
323
339
|
<div id="mb-tab-mappings" style="padding-top:12px;">
|
|
324
|
-
<div class="form-tips" style="display:block; width:100%; max-width:none; box-sizing:border-box; margin-bottom:12px; border-left:4px solid #d9822b;">
|
|
325
|
-
<i class="fa fa-triangle-exclamation"></i>
|
|
326
|
-
<span data-i18n="knxUltimateMatterBridge.type_change_warning"></span>
|
|
327
|
-
</div>
|
|
328
|
-
|
|
329
340
|
<div id="mb-ga-fields"></div>
|
|
330
341
|
|
|
331
342
|
<div class="form-row" id="mb-invert-row" style="display:none;">
|
|
@@ -339,10 +350,11 @@
|
|
|
339
350
|
|
|
340
351
|
<div id="mb-tab-advanced" style="padding-top:12px;">
|
|
341
352
|
<div id="mb-compatibility-options" style="display:none;">
|
|
342
|
-
<div class="form-
|
|
343
|
-
<
|
|
344
|
-
|
|
345
|
-
|
|
353
|
+
<div class="form-row mb-command-advanced-row" style="display:none; margin-top:8px;">
|
|
354
|
+
<label for="node-input-commandDedupMs" style="width:260px;">
|
|
355
|
+
<i class="fa fa-clone"></i> <span data-i18n="knxUltimateMatterBridge.advanced.alexa_dedup_ms"></span>
|
|
356
|
+
</label>
|
|
357
|
+
<input type="number" id="node-input-commandDedupMs" min="0" step="100" style="width:120px;">
|
|
346
358
|
</div>
|
|
347
359
|
|
|
348
360
|
<div class="form-row mb-dimmer-advanced-row" style="display:none; margin-top:8px;">
|
|
@@ -363,12 +375,17 @@
|
|
|
363
375
|
</div>
|
|
364
376
|
|
|
365
377
|
<div class="form-row mb-cover-advanced-row" style="display:none; margin-top:8px;">
|
|
366
|
-
<label for="node-input-
|
|
367
|
-
<i class="fa fa-
|
|
368
|
-
|
|
369
|
-
|
|
378
|
+
<label for="node-input-coverSwapOpenClose" style="width:260px;">
|
|
379
|
+
<i class="fa fa-exchange"></i> <span data-i18n="knxUltimateMatterBridge.advanced.cover_swap_open_close"></span>
|
|
380
|
+
</label>
|
|
381
|
+
<input type="checkbox" id="node-input-coverSwapOpenClose" style="width:auto; vertical-align:middle;">
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<div class="form-row mb-cover-advanced-row" style="display:none;">
|
|
385
|
+
<label for="node-input-coverSliderDebounceMs" style="width:260px;">
|
|
386
|
+
<i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateMatterBridge.advanced.cover_slider_debounce"></span>
|
|
370
387
|
</label>
|
|
371
|
-
<
|
|
388
|
+
<input type="number" id="node-input-coverSliderDebounceMs" min="0" max="5000" step="50" style="width:120px;">
|
|
372
389
|
</div>
|
|
373
390
|
|
|
374
391
|
<div class="form-row mb-cover-advanced-row" style="display:none; margin-top:8px;">
|
|
@@ -430,8 +447,9 @@ Point it to a **Matter Bridge** configuration node (the actual bridge, paired on
|
|
|
430
447
|
The advanced compatibility options are intentionally type-specific:
|
|
431
448
|
|
|
432
449
|
- dimmable devices can ignore the brightness command that some controllers send immediately after an On command
|
|
450
|
+
- covers can swap Open/Close (including the percentage direction) for controllers such as Alexa that interpret them in reverse
|
|
451
|
+
- cover slider debounce coalesces rapid intermediate targets before writing to KNX: `0` uses adaptive 400/150 ms windows, while `1`–`5000` forces a fixed window
|
|
433
452
|
- covers can optimistically update the Matter position after a command, then correct it when the KNX status GA reports the real position
|
|
434
|
-
- if Alexa cannot set a cover percentage, a cover can be exposed as a dimmable light: brightness means openness, while the configured KNX cover GAs and DPTs remain unchanged
|
|
435
453
|
|
|
436
454
|
The **KNX gateway is optional**: without it, enable the node PINs and drive the device from the flow.
|
|
437
455
|
|
|
@@ -51,7 +51,9 @@ module.exports = function (RED) {
|
|
|
51
51
|
thermostat: [
|
|
52
52
|
{ fn: 'currenttemp', direction: 'status', ga: 'gaCurrentTemp', dpt: 'dptCurrentTemp', fallbackDpt: '9.001' },
|
|
53
53
|
{ fn: 'setpoint', direction: 'command', ga: 'gaSetpoint', dpt: 'dptSetpoint', fallbackDpt: '9.001' },
|
|
54
|
-
{ fn: 'setpoint', direction: 'status', ga: 'gaSetpointStatus', dpt: 'dptSetpointStatus', fallbackDpt: '9.001' }
|
|
54
|
+
{ fn: 'setpoint', direction: 'status', ga: 'gaSetpointStatus', dpt: 'dptSetpointStatus', fallbackDpt: '9.001' },
|
|
55
|
+
{ fn: 'coolingsetpoint', direction: 'command', ga: 'gaCoolingSetpoint', dpt: 'dptCoolingSetpoint', fallbackDpt: '9.001' },
|
|
56
|
+
{ fn: 'coolingsetpoint', direction: 'status', ga: 'gaCoolingSetpointStatus', dpt: 'dptCoolingSetpointStatus', fallbackDpt: '9.001' }
|
|
55
57
|
],
|
|
56
58
|
smokecoalarm: [
|
|
57
59
|
{ fn: 'smoke', direction: 'status', ga: 'gaSmoke', dpt: 'dptSmoke', fallbackDpt: '1.005' },
|
|
@@ -77,10 +79,17 @@ module.exports = function (RED) {
|
|
|
77
79
|
// The Matter device id is the (stable) Node-RED node id, so the endpoint survives re-deploys.
|
|
78
80
|
node.matterDeviceId = node.id
|
|
79
81
|
node.invertPosition = config.invertPosition === true || config.invertPosition === 'true'
|
|
80
|
-
node.
|
|
82
|
+
node.coverSwapOpenClose = config.coverSwapOpenClose === true || config.coverSwapOpenClose === 'true'
|
|
83
|
+
node.coverSliderDebounceMs = Number(config.coverSliderDebounceMs)
|
|
84
|
+
if (!Number.isFinite(node.coverSliderDebounceMs) || node.coverSliderDebounceMs < 0) node.coverSliderDebounceMs = 0
|
|
85
|
+
node.coverSliderDebounceMs = Math.min(5000, node.coverSliderDebounceMs)
|
|
81
86
|
node.turnOnBehavior = config.turnOnBehavior || 'ignoreLevelAfterOn'
|
|
82
87
|
node.ignoreLevelAfterOnMs = Number(config.ignoreLevelAfterOnMs)
|
|
83
88
|
if (!Number.isFinite(node.ignoreLevelAfterOnMs) || node.ignoreLevelAfterOnMs < 0) node.ignoreLevelAfterOnMs = 800
|
|
89
|
+
// Alexa duplicate-command dedup window (ms). 0 disables it (raw forwarding);
|
|
90
|
+
// an empty/invalid value falls back to the 1200ms default. See the device factory.
|
|
91
|
+
node.commandDedupMs = Number(config.commandDedupMs)
|
|
92
|
+
if (!Number.isFinite(node.commandDedupMs) || node.commandDedupMs < 0) node.commandDedupMs = 1200
|
|
84
93
|
node.coverUpdateMode = config.coverUpdateMode || 'optimistic'
|
|
85
94
|
node.coverStatusTimeoutMs = Number(config.coverStatusTimeoutMs)
|
|
86
95
|
if (!Number.isFinite(node.coverStatusTimeoutMs) || node.coverStatusTimeoutMs < 0) node.coverStatusTimeoutMs = 3000
|
|
@@ -110,6 +119,15 @@ module.exports = function (RED) {
|
|
|
110
119
|
let initialReadTimer = null
|
|
111
120
|
let suppressLevelCommandsUntil = 0
|
|
112
121
|
let coverStatusTimer = null
|
|
122
|
+
let coverArrivalTimer = null
|
|
123
|
+
// Generous safety net, independent from the (short, configurable) coverStatusTimeoutMs
|
|
124
|
+
// warning below: real KNX cover actuators are not guaranteed to report back EXACTLY the
|
|
125
|
+
// requested percent100ths (positioning tolerance, drift...), and matter.js's
|
|
126
|
+
// WindowCoveringServer only clears OperationalStatus (Opening/Closing) back to Stopped
|
|
127
|
+
// once currentPosition === targetPosition exactly. Without this, a controller could show
|
|
128
|
+
// the cover as "still moving" forever after a movement that in reality completed close
|
|
129
|
+
// enough. Not (yet) user-configurable - ask if you want it exposed in the editor.
|
|
130
|
+
const COVER_ARRIVAL_FALLBACK_MS = Number(config.coverArrivalFallbackMs) > 0 ? Number(config.coverArrivalFallbackMs) : 45000
|
|
113
131
|
|
|
114
132
|
const formatTs = (date) => {
|
|
115
133
|
const d = date instanceof Date ? date : new Date(date)
|
|
@@ -157,6 +175,13 @@ module.exports = function (RED) {
|
|
|
157
175
|
}
|
|
158
176
|
}
|
|
159
177
|
|
|
178
|
+
const clearCoverArrivalTimer = () => {
|
|
179
|
+
if (coverArrivalTimer !== null) {
|
|
180
|
+
clearTimeout(coverArrivalTimer)
|
|
181
|
+
coverArrivalTimer = null
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
160
185
|
// Precompute the routing tables for this single device:
|
|
161
186
|
// statusRoutes: GA -> [{ fn, dpt }] (KNX -> Matter)
|
|
162
187
|
// commandRoutes: fn -> { ga, dpt } (Matter -> KNX)
|
|
@@ -181,20 +206,42 @@ module.exports = function (RED) {
|
|
|
181
206
|
return false
|
|
182
207
|
}
|
|
183
208
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
209
|
+
// Matter-side feedback for a movement command just sent to KNX.
|
|
210
|
+
// - 'optimistic' (unchanged): reflect the requested position to Matter immediately; real
|
|
211
|
+
// KNX status, if configured, corrects it afterwards via handleSend below.
|
|
212
|
+
// - 'waitStatus': wait for the real KNX status GA. If it doesn't arrive within
|
|
213
|
+
// coverStatusTimeoutMs, just warn in the node status (as before). If NOTHING arrives
|
|
214
|
+
// within the much longer COVER_ARRIVAL_FALLBACK_MS either, fall back to reflecting the
|
|
215
|
+
// requested position anyway, so a real actuator that never reports back the exact
|
|
216
|
+
// target percent100ths doesn't leave the Matter device stuck "moving" forever.
|
|
217
|
+
const applyCoverPositionFeedback = (command) => {
|
|
218
|
+
if (node.deviceType !== 'windowcovering' || !node.serverMatterBridge) return
|
|
219
|
+
let requestedPosition
|
|
220
|
+
if (command.fn === 'position') requestedPosition = command.value
|
|
221
|
+
if (command.fn === 'updown') requestedPosition = command.value === true ? 100 : 0
|
|
222
|
+
if (requestedPosition === undefined) return
|
|
223
|
+
|
|
191
224
|
clearCoverStatusTimer()
|
|
192
|
-
|
|
225
|
+
clearCoverArrivalTimer()
|
|
226
|
+
|
|
227
|
+
if (node.coverUpdateMode === 'optimistic') {
|
|
228
|
+
node.serverMatterBridge.setDeviceState(node.matterDeviceId, 'position', requestedPosition)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!hasStatusRouteFor('position')) return // no status GA at all: nothing to wait for or fall back from; the flow (if any) owns feedback
|
|
233
|
+
|
|
234
|
+
if (node.coverStatusTimeoutMs > 0) {
|
|
193
235
|
coverStatusTimer = setTimeout(() => {
|
|
194
236
|
coverStatusTimer = null
|
|
195
|
-
node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'Waiting for KNX cover status', payload:
|
|
237
|
+
node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'Waiting for KNX cover status', payload: requestedPosition })
|
|
196
238
|
}, node.coverStatusTimeoutMs)
|
|
197
239
|
}
|
|
240
|
+
coverArrivalTimer = setTimeout(() => {
|
|
241
|
+
coverArrivalTimer = null
|
|
242
|
+
node.serverMatterBridge.setDeviceState(node.matterDeviceId, 'position', requestedPosition)
|
|
243
|
+
node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'No exact KNX confirmation in time, assuming arrived at', payload: requestedPosition })
|
|
244
|
+
}, COVER_ARRIVAL_FALLBACK_MS)
|
|
198
245
|
}
|
|
199
246
|
node.knxUltimateAcceptedGAs = Array.from(statusRoutes.keys())
|
|
200
247
|
|
|
@@ -204,8 +251,15 @@ module.exports = function (RED) {
|
|
|
204
251
|
type: node.deviceType,
|
|
205
252
|
name: node.name,
|
|
206
253
|
invertPosition: node.invertPosition === true,
|
|
207
|
-
|
|
208
|
-
|
|
254
|
+
coverSwapOpenClose: node.coverSwapOpenClose === true,
|
|
255
|
+
coverSliderDebounceMs: node.coverSliderDebounceMs,
|
|
256
|
+
commandDedupMs: node.commandDedupMs,
|
|
257
|
+
coverUpdateMode: node.coverUpdateMode,
|
|
258
|
+
// Thermostat only: whether a heating/cooling setpoint GA (command or status) was
|
|
259
|
+
// configured, so the bridge engine can build a Heating-only, Cooling-only or dual
|
|
260
|
+
// ThermostatServer without a dedicated "mode" dropdown in the editor.
|
|
261
|
+
hasHeatingSetpoint: devConfigValue('gaSetpoint') !== '' || devConfigValue('gaSetpointStatus') !== '',
|
|
262
|
+
hasCoolingSetpoint: devConfigValue('gaCoolingSetpoint') !== '' || devConfigValue('gaCoolingSetpointStatus') !== ''
|
|
209
263
|
})
|
|
210
264
|
|
|
211
265
|
// Reflects the bridge (config node) status on this device node.
|
|
@@ -248,7 +302,7 @@ module.exports = function (RED) {
|
|
|
248
302
|
}
|
|
249
303
|
// Confirm the requested position to Matter even when this is a flow-only
|
|
250
304
|
// device and no KNX command GA is configured.
|
|
251
|
-
|
|
305
|
+
applyCoverPositionFeedback(command)
|
|
252
306
|
const route = commandRoutes.get(command.fn)
|
|
253
307
|
if (route === undefined) {
|
|
254
308
|
if (node.enableNodePINS) return // Flow-only device: the flow already got the command
|
|
@@ -286,7 +340,7 @@ module.exports = function (RED) {
|
|
|
286
340
|
const value = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(route.dpt))
|
|
287
341
|
if (value === undefined || value === null) return
|
|
288
342
|
node.serverMatterBridge.setDeviceState(node.matterDeviceId, route.fn, value)
|
|
289
|
-
if (node.deviceType === 'windowcovering' && route.fn === 'position') clearCoverStatusTimer()
|
|
343
|
+
if (node.deviceType === 'windowcovering' && route.fn === 'position') { clearCoverStatusTimer(); clearCoverArrivalTimer() }
|
|
290
344
|
node.setNodeStatus({ fill: 'blue', shape: 'dot', text: `KNX->Matter ${route.fn}`, payload: value })
|
|
291
345
|
} catch (error) {
|
|
292
346
|
node.setNodeStatus({ fill: 'red', shape: 'dot', text: `KNX->Matter error ${error.message}` })
|
|
@@ -358,6 +412,7 @@ module.exports = function (RED) {
|
|
|
358
412
|
try {
|
|
359
413
|
if (initialReadTimer !== null) clearTimeout(initialReadTimer)
|
|
360
414
|
clearCoverStatusTimer()
|
|
415
|
+
clearCoverArrivalTimer()
|
|
361
416
|
} catch (error) { /* empty */ }
|
|
362
417
|
try {
|
|
363
418
|
if (node.serverKNX) node.serverKNX.removeClient(node)
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
},
|
|
126
126
|
inputs: 0,
|
|
127
127
|
outputs: 0,
|
|
128
|
-
icon: "node-matter-icon.svg",
|
|
128
|
+
icon: "node-matter-controller-icon.svg",
|
|
129
129
|
label: function () {
|
|
130
130
|
return this.name || this.matterDeviceName || "Control Matter from KNX (BETA)";
|
|
131
131
|
},
|
|
@@ -679,7 +679,6 @@
|
|
|
679
679
|
const $tabs = $("#tabs");
|
|
680
680
|
const $pinSectionRow = $("#node-input-enableNodePINS").closest('.form-row');
|
|
681
681
|
const $pinSelect = $("#node-input-enableNodePINS");
|
|
682
|
-
const $pinInfoRow = $pinSectionRow.next('.form-tips');
|
|
683
682
|
let currentMatterCapabilities = parseMatterCapabilities($matterCapabilitiesInput.val() || node.matterDeviceCapabilities);
|
|
684
683
|
$tabs.addClass('hue-vertical-tabs');
|
|
685
684
|
$tabs.tabs(); // Tabs gestione KNX
|
|
@@ -984,9 +983,6 @@
|
|
|
984
983
|
if ($pinSectionRow.length) {
|
|
985
984
|
$pinSectionRow.show();
|
|
986
985
|
}
|
|
987
|
-
if ($pinInfoRow.length) {
|
|
988
|
-
$pinInfoRow.show();
|
|
989
|
-
}
|
|
990
986
|
};
|
|
991
987
|
updateTabsVisibility();
|
|
992
988
|
|
|
@@ -1899,7 +1895,6 @@
|
|
|
1899
1895
|
</label>
|
|
1900
1896
|
<input type="checkbox" id="node-input-updateLocalStateFromKNXWrite" style="display:inline-block; width:auto; vertical-align:top;" />
|
|
1901
1897
|
</div>
|
|
1902
|
-
<div class="form-tips" style="margin-left:260px;" data-i18n="knxUltimateMatterControllerDevice.update_local_state_from_knx_write_hint"></div><br/>
|
|
1903
1898
|
<div id ="divBehaviourBrightness">
|
|
1904
1899
|
<div class="form-row">
|
|
1905
1900
|
<label for="node-input-specifySwitchOnBrightness" style="width:260px;">
|
|
@@ -2116,4 +2111,4 @@ _Non-Hue basic effects_
|
|
|
2116
2111
|
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
2112
|
|
|
2118
2113
|
<br/>
|
|
2119
|
-
</script>
|
|
2114
|
+
</script>
|
|
@@ -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|
|
|
@@ -28,7 +30,7 @@ Das ist die Gegenrichtung zum *Matter Device*-Node: Dort steuert KNX ein Matter-
|
|
|
28
30
|
| RGB-Farblicht | + RGB-Farbe Befehls-/Status-GA (DPT 232.600). Die Matter-Farbe (Hue/Saturation oder XY, vom Farbrad der App) wird von/zur KNX-RGB-Triplette konvertiert |
|
|
29
31
|
| Tunable-White-Licht | + Farbtemperatur Befehls-/Status-GA in Kelvin (DPT 7.600) |
|
|
30
32
|
| Rollladen / Jalousie | Auf/Ab (DPT 1.008), Stopp (DPT 1.017), Position % Befehl/Status (DPT 5.001), optionale Positionsumkehr |
|
|
31
|
-
| Thermostat (Heizung) | GA aktuelle Temperatur, Sollwert Befehls-/Status-GA (DPT 9.001) |
|
|
33
|
+
| Thermostat (Heizung und/oder Kühlung) | GA aktuelle Temperatur, Sollwert Befehls-/Status-GA (DPT 9.001). Mit zusätzlichem Kühl-Sollwert Befehls-/Status-GA wird auch der Kühlmodus (Doppel-Sollwert) bereitgestellt |
|
|
32
34
|
| Ventilator / Lüftung | Geschwindigkeit % Befehls-/Status-GA (DPT 5.001) |
|
|
33
35
|
| Sensoren (Temperatur, Feuchte, Licht, Präsenz, Kontakt) | Je eine Status-GA |
|
|
34
36
|
| Rauch-/CO-Melder | Rauchalarm Status-GA + optionale CO-Alarm Status-GA (DPT 1.005): kritische Benachrichtigungen auf dem Telefon |
|
|
@@ -41,7 +43,7 @@ Das ist die Gegenrichtung zum *Matter Device*-Node: Dort steuert KNX ein Matter-
|
|
|
41
43
|
|
|
42
44
|
## Erweiterte Kompatibilität
|
|
43
45
|
|
|
44
|
-
Diese Optionen werden nur angezeigt, wenn sie zum gewählten Typ passen. Dimmbare Geräte können den Helligkeitsbefehl ignorieren, den manche Controller direkt nach `Ein` senden.
|
|
46
|
+
Diese Optionen werden nur angezeigt, wenn sie zum gewählten Typ passen. Dimmbare Geräte können den Helligkeitsbefehl ignorieren, den manche Controller direkt nach `Ein` senden. Bei Abdeckungen kehrt **Öffnen / Schließen tauschen** sowohl den binären KNX-Befehl als auch die Prozentrichtung um. **Abdeckungsregler-Entprellung** fasst schnelle Zwischenziele vor dem KNX-Schreiben zusammen: `0` nutzt adaptive Fenster (400 ms für den ersten, 150 ms für weitere Befehle); `1`–`5000` erzwingt ein festes Fenster. Die Matter-Position kann außerdem optimistisch aktualisiert und später durch die KNX-Status-GA korrigiert werden.
|
|
45
47
|
|
|
46
48
|
## Node-PINs
|
|
47
49
|
|
|
@@ -17,24 +17,25 @@
|
|
|
17
17
|
"name_placeholder": "z.B. Küchenlicht",
|
|
18
18
|
"invert": "Position % invertieren"
|
|
19
19
|
},
|
|
20
|
-
|
|
20
|
+
"advanced": {
|
|
21
21
|
"title": "Erweiterte Kompatibilität",
|
|
22
22
|
"help": " Verwende diese Optionen nur, wenn ein Matter-Controller oder ein KNX-Aktor eine besondere Behandlung benötigt.",
|
|
23
|
+
"alexa_dedup_ms": "Alexa-Duplikat-Fenster (ms, 0 = aus)",
|
|
23
24
|
"turn_on_behavior": "Einschaltverhalten",
|
|
24
25
|
"turn_on_ignore_level": "Helligkeit direkt nach Ein ignorieren",
|
|
25
26
|
"turn_on_forward_all": "Jeden Helligkeitsbefehl weiterleiten",
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
"ignore_level_ms": "Helligkeit nach Ein ignorieren (ms)",
|
|
28
|
+
"cover_swap_open_close": "Abdeckung Öffnen / Schließen tauschen",
|
|
29
|
+
"cover_slider_debounce": "Abdeckungsregler-Entprellung (ms, 0 = adaptiv)",
|
|
29
30
|
"cover_update_mode": "Jalousie-/Rollladenstatus",
|
|
30
31
|
"cover_optimistic": "Matter-Position optimistisch aktualisieren",
|
|
31
32
|
"cover_wait_status": "Nur auf KNX-Status warten",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
"cover_status_timeout": "Timeout für Positionsstatus (ms)"
|
|
34
|
+
},
|
|
35
|
+
"tabs": {
|
|
36
|
+
"mappings": "KNX-Zuordnungen",
|
|
37
|
+
"advanced": "Erweiterte Optionen"
|
|
38
|
+
},
|
|
38
39
|
"functions": {
|
|
39
40
|
"fn_onoff_cmd": "Ein/Aus Befehls-GA",
|
|
40
41
|
"fn_onoff_status": "Ein/Aus Status-GA",
|
|
@@ -48,6 +49,8 @@
|
|
|
48
49
|
"fn_currenttemp": "GA aktuelle Temperatur",
|
|
49
50
|
"fn_setpoint_cmd": "Sollwert Befehls-GA",
|
|
50
51
|
"fn_setpoint_status": "Sollwert Status-GA",
|
|
52
|
+
"fn_coolingsetpoint_cmd": "Kühl-Sollwert Befehls-GA",
|
|
53
|
+
"fn_coolingsetpoint_status": "Kühl-Sollwert Status-GA",
|
|
51
54
|
"fn_rgb_cmd": "RGB-Farbe Befehls-GA (DPT 232.600)",
|
|
52
55
|
"fn_rgb_status": "RGB-Farbe Status-GA (DPT 232.600)",
|
|
53
56
|
"fn_ct_cmd": "Farbtemperatur Befehls-GA (Kelvin)",
|
|
@@ -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
|
}
|