node-red-contrib-knx-ultimate 6.0.3 → 6.0.5
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/nodes/knxUltimateMatterBridge.html +125 -88
- package/nodes/knxUltimateMatterBridge.js +70 -15
- package/nodes/locales/de/knxUltimateMatterBridge.html +2 -2
- package/nodes/locales/de/knxUltimateMatterBridge.json +10 -3
- package/nodes/locales/en/knxUltimateMatterBridge.html +2 -2
- package/nodes/locales/en/knxUltimateMatterBridge.json +10 -3
- package/nodes/locales/es/knxUltimateMatterBridge.html +2 -2
- package/nodes/locales/es/knxUltimateMatterBridge.json +10 -3
- package/nodes/locales/fr/knxUltimateMatterBridge.html +2 -2
- package/nodes/locales/fr/knxUltimateMatterBridge.json +10 -3
- package/nodes/locales/it/knxUltimateMatterBridge.html +2 -2
- package/nodes/locales/it/knxUltimateMatterBridge.json +10 -3
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +2 -2
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +10 -3
- 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/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 6.0.5** - July 2026<br/>
|
|
10
|
+
|
|
11
|
+
- **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
|
+
- **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
|
+
- **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/>
|
|
14
|
+
- **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/>
|
|
15
|
+
- **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/>
|
|
16
|
+
- **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/>
|
|
17
|
+
- **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/>
|
|
18
|
+
- **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/>
|
|
19
|
+
- **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/>
|
|
20
|
+
|
|
21
|
+
**Version 6.0.4** - July 2026<br/>
|
|
22
|
+
|
|
23
|
+
- **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/>
|
|
24
|
+
|
|
9
25
|
**Version 6.0.3** - July 2026<br/>
|
|
10
26
|
|
|
11
27
|
- **Expose KNX to Matter (BETA) — Alexa cover percentage workaround**: covers/shutters can optionally be exposed to Matter controllers as dimmable lights when Alexa does not send percentage commands to a native Window Covering endpoint. Brightness represents openness (`100%` open, `0%` closed), while On/Off opens or closes the cover. Commands and status remain mapped through the existing KNX cover position GA/DPT, including position inversion and loop-protected KNX-to-Matter feedback. The standard Matter Window Covering endpoint remains the default.<br/>
|
|
@@ -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' },
|
|
@@ -105,6 +109,9 @@
|
|
|
105
109
|
try { RED.sidebar.show('help'); } catch (error) { /* empty */ }
|
|
106
110
|
const node = this;
|
|
107
111
|
|
|
112
|
+
// Use the same jQuery UI tab structure as the main KNXUltimate node.
|
|
113
|
+
$('#mb-tabs').tabs();
|
|
114
|
+
|
|
108
115
|
// Resolves the KNX gateway at query time: the selected one, or - as a
|
|
109
116
|
// fallback - the first knxUltimate-config available, so the GA list works
|
|
110
117
|
// even before the user picks the gateway on this device node.
|
|
@@ -143,7 +150,8 @@
|
|
|
143
150
|
// on one or more rows (same pattern used across the rest of the KNX-Ultimate nodes).
|
|
144
151
|
const $fields = $('#mb-ga-fields').css({ display: 'flex', flexWrap: 'wrap', alignItems: 'flex-end', gap: '10px' });
|
|
145
152
|
const $invertRow = $('#mb-invert-row');
|
|
146
|
-
const $advancedRows = $('#mb-
|
|
153
|
+
const $advancedRows = $('#mb-compatibility-options');
|
|
154
|
+
const $commandAdvancedRows = $('.mb-command-advanced-row');
|
|
147
155
|
const $dimmerAdvancedRows = $('.mb-dimmer-advanced-row');
|
|
148
156
|
const $coverAdvancedRows = $('.mb-cover-advanced-row');
|
|
149
157
|
const gaInputs = {};
|
|
@@ -217,7 +225,11 @@
|
|
|
217
225
|
$invertRow.toggle(selected === 'windowcovering');
|
|
218
226
|
const hasLevel = selected === 'dimmablelight' || selected === 'rgblight' || selected === 'colortemperaturelight';
|
|
219
227
|
const isCover = selected === 'windowcovering';
|
|
220
|
-
|
|
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);
|
|
221
233
|
$dimmerAdvancedRows.toggle(hasLevel);
|
|
222
234
|
$coverAdvancedRows.toggle(isCover);
|
|
223
235
|
}
|
|
@@ -229,7 +241,9 @@
|
|
|
229
241
|
node._matterGaInputs = gaInputs;
|
|
230
242
|
|
|
231
243
|
$('#node-input-invertPosition').prop('checked', node.invertPosition === true);
|
|
232
|
-
$('#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);
|
|
233
247
|
$('#node-input-turnOnBehavior').val(node.turnOnBehavior || 'ignoreLevelAfterOn');
|
|
234
248
|
$('#node-input-ignoreLevelAfterOnMs').val(node.ignoreLevelAfterOnMs === undefined ? 800 : node.ignoreLevelAfterOnMs);
|
|
235
249
|
$('#node-input-coverUpdateMode').val(node.coverUpdateMode || 'optimistic');
|
|
@@ -243,7 +257,7 @@
|
|
|
243
257
|
const selected = $('#node-input-deviceType').val();
|
|
244
258
|
node.deviceType = selected;
|
|
245
259
|
node.invertPosition = $('#node-input-invertPosition').is(':checked');
|
|
246
|
-
node.
|
|
260
|
+
node.coverSwapOpenClose = $('#node-input-coverSwapOpenClose').is(':checked');
|
|
247
261
|
const active = TYPE_FIELDS[selected] || [];
|
|
248
262
|
const gaInputs = node._matterGaInputs || {};
|
|
249
263
|
// Clear every GA/DPT, then write only the ones of the selected type, reading
|
|
@@ -261,6 +275,11 @@
|
|
|
261
275
|
}
|
|
262
276
|
});
|
|
263
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)));
|
|
264
283
|
this.turnOnBehavior = $('#node-input-turnOnBehavior').val() || 'ignoreLevelAfterOn';
|
|
265
284
|
this.ignoreLevelAfterOnMs = Number($('#node-input-ignoreLevelAfterOnMs').val()) || 0;
|
|
266
285
|
this.coverUpdateMode = $('#node-input-coverUpdateMode').val() || 'optimistic';
|
|
@@ -311,90 +330,107 @@
|
|
|
311
330
|
<select id="node-input-deviceType" style="width:260px;"></select>
|
|
312
331
|
</div>
|
|
313
332
|
|
|
314
|
-
<div
|
|
315
|
-
<
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
<div class="form-row mb-dimmer-advanced-row" style="display:none; margin-top:8px;">
|
|
336
|
-
<label for="node-input-turnOnBehavior" style="width:260px;">
|
|
337
|
-
<i class="fa fa-lightbulb-o"></i> <span data-i18n="knxUltimateMatterBridge.advanced.turn_on_behavior"></span>
|
|
338
|
-
</label>
|
|
339
|
-
<select id="node-input-turnOnBehavior" style="width:280px;">
|
|
340
|
-
<option value="ignoreLevelAfterOn" data-i18n="knxUltimateMatterBridge.advanced.turn_on_ignore_level"></option>
|
|
341
|
-
<option value="forwardAll" data-i18n="knxUltimateMatterBridge.advanced.turn_on_forward_all"></option>
|
|
342
|
-
</select>
|
|
343
|
-
</div>
|
|
344
|
-
|
|
345
|
-
<div class="form-row mb-dimmer-advanced-row" style="display:none;">
|
|
346
|
-
<label for="node-input-ignoreLevelAfterOnMs" style="width:260px;">
|
|
347
|
-
<i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateMatterBridge.advanced.ignore_level_ms"></span>
|
|
348
|
-
</label>
|
|
349
|
-
<input type="number" id="node-input-ignoreLevelAfterOnMs" min="0" step="100" style="width:120px;">
|
|
350
|
-
</div>
|
|
351
|
-
|
|
352
|
-
<div class="form-row mb-cover-advanced-row" style="display:none; margin-top:8px;">
|
|
353
|
-
<label for="node-input-coverExposeAsDimmableLight" style="width:auto;">
|
|
354
|
-
<input type="checkbox" id="node-input-coverExposeAsDimmableLight" style="width:auto; display:inline-block; vertical-align:middle;">
|
|
355
|
-
<span data-i18n="knxUltimateMatterBridge.advanced.cover_expose_as_light"></span>
|
|
356
|
-
</label>
|
|
357
|
-
<div class="form-tips" style="margin-top:5px;" data-i18n="knxUltimateMatterBridge.advanced.cover_expose_as_light_help"></div>
|
|
333
|
+
<div id="mb-tabs" style="margin-top:12px;">
|
|
334
|
+
<ul>
|
|
335
|
+
<li><a href="#mb-tab-mappings"><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateMatterBridge.tabs.mappings"></span></a></li>
|
|
336
|
+
<li><a href="#mb-tab-advanced"><i class="fa fa-sliders"></i> <span data-i18n="knxUltimateMatterBridge.tabs.advanced"></span></a></li>
|
|
337
|
+
</ul>
|
|
338
|
+
|
|
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
|
+
<div id="mb-ga-fields"></div>
|
|
346
|
+
|
|
347
|
+
<div class="form-row" id="mb-invert-row" style="display:none;">
|
|
348
|
+
<label for="node-input-invertPosition" style="width:auto;">
|
|
349
|
+
<i class="fa fa-exchange"></i>
|
|
350
|
+
<span data-i18n="knxUltimateMatterBridge.fields.invert"></span>
|
|
351
|
+
<input type="checkbox" id="node-input-invertPosition" style="width:auto; display:inline-block; vertical-align:middle; margin-left:6px;">
|
|
352
|
+
</label>
|
|
353
|
+
</div>
|
|
358
354
|
</div>
|
|
359
355
|
|
|
360
|
-
<div
|
|
361
|
-
<
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
356
|
+
<div id="mb-tab-advanced" style="padding-top:12px;">
|
|
357
|
+
<div id="mb-compatibility-options" style="display:none;">
|
|
358
|
+
<div class="form-row mb-command-advanced-row" style="display:none; margin-top:8px;">
|
|
359
|
+
<label for="node-input-commandDedupMs" style="width:260px;">
|
|
360
|
+
<i class="fa fa-clone"></i> <span data-i18n="knxUltimateMatterBridge.advanced.alexa_dedup_ms"></span>
|
|
361
|
+
</label>
|
|
362
|
+
<input type="number" id="node-input-commandDedupMs" min="0" step="100" style="width:120px;">
|
|
363
|
+
</div>
|
|
364
|
+
|
|
365
|
+
<div class="form-row mb-dimmer-advanced-row" style="display:none; margin-top:8px;">
|
|
366
|
+
<label for="node-input-turnOnBehavior" style="width:260px;">
|
|
367
|
+
<i class="fa fa-lightbulb-o"></i> <span data-i18n="knxUltimateMatterBridge.advanced.turn_on_behavior"></span>
|
|
368
|
+
</label>
|
|
369
|
+
<select id="node-input-turnOnBehavior" style="width:280px;">
|
|
370
|
+
<option value="ignoreLevelAfterOn" data-i18n="knxUltimateMatterBridge.advanced.turn_on_ignore_level"></option>
|
|
371
|
+
<option value="forwardAll" data-i18n="knxUltimateMatterBridge.advanced.turn_on_forward_all"></option>
|
|
372
|
+
</select>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<div class="form-row mb-dimmer-advanced-row" style="display:none;">
|
|
376
|
+
<label for="node-input-ignoreLevelAfterOnMs" style="width:260px;">
|
|
377
|
+
<i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateMatterBridge.advanced.ignore_level_ms"></span>
|
|
378
|
+
</label>
|
|
379
|
+
<input type="number" id="node-input-ignoreLevelAfterOnMs" min="0" step="100" style="width:120px;">
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<div class="form-row mb-cover-advanced-row" style="display:none; margin-top:8px;">
|
|
383
|
+
<label for="node-input-coverSwapOpenClose" style="width:260px;">
|
|
384
|
+
<i class="fa fa-exchange"></i> <span data-i18n="knxUltimateMatterBridge.advanced.cover_swap_open_close"></span>
|
|
385
|
+
</label>
|
|
386
|
+
<input type="checkbox" id="node-input-coverSwapOpenClose" style="width:auto; vertical-align:middle;">
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
<div class="form-row mb-cover-advanced-row" style="display:none;">
|
|
390
|
+
<label for="node-input-coverSliderDebounceMs" style="width:260px;">
|
|
391
|
+
<i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateMatterBridge.advanced.cover_slider_debounce"></span>
|
|
392
|
+
</label>
|
|
393
|
+
<input type="number" id="node-input-coverSliderDebounceMs" min="0" max="5000" step="50" style="width:120px;">
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<div class="form-row mb-cover-advanced-row" style="display:none; margin-top:8px;">
|
|
397
|
+
<label for="node-input-coverUpdateMode" style="width:260px;">
|
|
398
|
+
<i class="fa fa-window-maximize"></i> <span data-i18n="knxUltimateMatterBridge.advanced.cover_update_mode"></span>
|
|
399
|
+
</label>
|
|
400
|
+
<select id="node-input-coverUpdateMode" style="width:280px;">
|
|
401
|
+
<option value="optimistic" data-i18n="knxUltimateMatterBridge.advanced.cover_optimistic"></option>
|
|
402
|
+
<option value="waitStatus" data-i18n="knxUltimateMatterBridge.advanced.cover_wait_status"></option>
|
|
403
|
+
</select>
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
<div class="form-row mb-cover-advanced-row" style="display:none;">
|
|
407
|
+
<label for="node-input-coverStatusTimeoutMs" style="width:260px;">
|
|
408
|
+
<i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateMatterBridge.advanced.cover_status_timeout"></span>
|
|
409
|
+
</label>
|
|
410
|
+
<input type="number" id="node-input-coverStatusTimeoutMs" min="0" step="500" style="width:120px;">
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
<div class="form-row">
|
|
415
|
+
<label for="node-input-readStatusAtStartup" style="width:260px;">
|
|
416
|
+
<i class="fa fa-question-circle"></i> <span data-i18n="knxUltimateMatterBridge.read_status_startup"></span>
|
|
417
|
+
</label>
|
|
418
|
+
<select id="node-input-readStatusAtStartup" style="width:120px;">
|
|
419
|
+
<option value="yes" data-i18n="knxUltimateMatterBridge.opt_yes"></option>
|
|
420
|
+
<option value="no" data-i18n="knxUltimateMatterBridge.opt_no"></option>
|
|
421
|
+
</select>
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
<div class="form-row">
|
|
425
|
+
<label for="node-input-enableNodePINS" style="width:260px;">
|
|
426
|
+
<i class="fa fa-code"></i> <span data-i18n="knxUltimateMatterBridge.node_pins"></span>
|
|
427
|
+
</label>
|
|
428
|
+
<select id="node-input-enableNodePINS" style="width:220px;">
|
|
429
|
+
<option value="no" data-i18n="knxUltimateMatterBridge.node_pins_hide"></option>
|
|
430
|
+
<option value="yes" data-i18n="knxUltimateMatterBridge.node_pins_show"></option>
|
|
431
|
+
</select>
|
|
432
|
+
</div>
|
|
368
433
|
</div>
|
|
369
|
-
|
|
370
|
-
<div class="form-row mb-cover-advanced-row" style="display:none;">
|
|
371
|
-
<label for="node-input-coverStatusTimeoutMs" style="width:260px;">
|
|
372
|
-
<i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateMatterBridge.advanced.cover_status_timeout"></span>
|
|
373
|
-
</label>
|
|
374
|
-
<input type="number" id="node-input-coverStatusTimeoutMs" min="0" step="500" style="width:120px;">
|
|
375
|
-
</div>
|
|
376
|
-
</details>
|
|
377
|
-
|
|
378
|
-
<hr/>
|
|
379
|
-
|
|
380
|
-
<div class="form-row">
|
|
381
|
-
<label for="node-input-readStatusAtStartup" style="width:260px;">
|
|
382
|
-
<i class="fa fa-question-circle"></i> <span data-i18n="knxUltimateMatterBridge.read_status_startup"></span>
|
|
383
|
-
</label>
|
|
384
|
-
<select id="node-input-readStatusAtStartup" style="width:120px;">
|
|
385
|
-
<option value="yes" data-i18n="knxUltimateMatterBridge.opt_yes"></option>
|
|
386
|
-
<option value="no" data-i18n="knxUltimateMatterBridge.opt_no"></option>
|
|
387
|
-
</select>
|
|
388
|
-
</div>
|
|
389
|
-
|
|
390
|
-
<div class="form-row">
|
|
391
|
-
<label for="node-input-enableNodePINS" style="width:260px;">
|
|
392
|
-
<i class="fa fa-code"></i> <span data-i18n="knxUltimateMatterBridge.node_pins"></span>
|
|
393
|
-
</label>
|
|
394
|
-
<select id="node-input-enableNodePINS" style="width:220px;">
|
|
395
|
-
<option value="no" data-i18n="knxUltimateMatterBridge.node_pins_hide"></option>
|
|
396
|
-
<option value="yes" data-i18n="knxUltimateMatterBridge.node_pins_show"></option>
|
|
397
|
-
</select>
|
|
398
434
|
</div>
|
|
399
435
|
|
|
400
436
|
<br/>
|
|
@@ -416,8 +452,9 @@ Point it to a **Matter Bridge** configuration node (the actual bridge, paired on
|
|
|
416
452
|
The advanced compatibility options are intentionally type-specific:
|
|
417
453
|
|
|
418
454
|
- dimmable devices can ignore the brightness command that some controllers send immediately after an On command
|
|
455
|
+
- covers can swap Open/Close (including the percentage direction) for controllers such as Alexa that interpret them in reverse
|
|
456
|
+
- 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
|
|
419
457
|
- covers can optimistically update the Matter position after a command, then correct it when the KNX status GA reports the real position
|
|
420
|
-
- 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
|
|
421
458
|
|
|
422
459
|
The **KNX gateway is optional**: without it, enable the node PINs and drive the device from the flow.
|
|
423
460
|
|
|
@@ -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)
|
|
@@ -28,7 +28,7 @@ Das ist die Gegenrichtung zum *Matter Device*-Node: Dort steuert KNX ein Matter-
|
|
|
28
28
|
| 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
29
|
| Tunable-White-Licht | + Farbtemperatur Befehls-/Status-GA in Kelvin (DPT 7.600) |
|
|
30
30
|
| 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) |
|
|
31
|
+
| 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
32
|
| Ventilator / Lüftung | Geschwindigkeit % Befehls-/Status-GA (DPT 5.001) |
|
|
33
33
|
| Sensoren (Temperatur, Feuchte, Licht, Präsenz, Kontakt) | Je eine Status-GA |
|
|
34
34
|
| Rauch-/CO-Melder | Rauchalarm Status-GA + optionale CO-Alarm Status-GA (DPT 1.005): kritische Benachrichtigungen auf dem Telefon |
|
|
@@ -41,7 +41,7 @@ Das ist die Gegenrichtung zum *Matter Device*-Node: Dort steuert KNX ein Matter-
|
|
|
41
41
|
|
|
42
42
|
## Erweiterte Kompatibilität
|
|
43
43
|
|
|
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.
|
|
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. 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
45
|
|
|
46
46
|
## Node-PINs
|
|
47
47
|
|
|
@@ -20,17 +20,22 @@
|
|
|
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
|
"cover_status_timeout": "Timeout für Positionsstatus (ms)"
|
|
33
34
|
},
|
|
35
|
+
"tabs": {
|
|
36
|
+
"mappings": "KNX-Zuordnungen",
|
|
37
|
+
"advanced": "Erweiterte Optionen"
|
|
38
|
+
},
|
|
34
39
|
"functions": {
|
|
35
40
|
"fn_onoff_cmd": "Ein/Aus Befehls-GA",
|
|
36
41
|
"fn_onoff_status": "Ein/Aus Status-GA",
|
|
@@ -44,6 +49,8 @@
|
|
|
44
49
|
"fn_currenttemp": "GA aktuelle Temperatur",
|
|
45
50
|
"fn_setpoint_cmd": "Sollwert Befehls-GA",
|
|
46
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",
|
|
47
54
|
"fn_rgb_cmd": "RGB-Farbe Befehls-GA (DPT 232.600)",
|
|
48
55
|
"fn_rgb_status": "RGB-Farbe Status-GA (DPT 232.600)",
|
|
49
56
|
"fn_ct_cmd": "Farbtemperatur Befehls-GA (Kelvin)",
|
|
@@ -28,7 +28,7 @@ This is the opposite direction of the *Matter Device* node: there KNX controls a
|
|
|
28
28
|
| RGB color light | + RGB color command/status GA (DPT 232.600). The Matter color (hue/saturation or XY, from the app color wheel) is converted to/from the KNX RGB triplet |
|
|
29
29
|
| Tunable white light | + Color temperature command/status GA in Kelvin (DPT 7.600) |
|
|
30
30
|
| Cover / Shutter | Up/Down (DPT 1.008), Stop (DPT 1.017), Position % command/status (DPT 5.001), optional position inversion |
|
|
31
|
-
| Thermostat (heating) | Current temperature GA, Setpoint command/status GA (DPT 9.001) |
|
|
31
|
+
| Thermostat (heating and/or cooling) | Current temperature GA, Setpoint command/status GA (DPT 9.001). Optionally add a Cooling setpoint command/status GA to also expose a Cool mode (dual-setpoint) |
|
|
32
32
|
| Fan / Ventilation | Fan speed % command/status GA (DPT 5.001) |
|
|
33
33
|
| Sensors (temperature, humidity, light, occupancy, contact) | One status GA each |
|
|
34
34
|
| Smoke/CO alarm | Smoke alarm status GA + optional CO alarm status GA (DPT 1.005): critical notifications on the phone |
|
|
@@ -41,7 +41,7 @@ This is the opposite direction of the *Matter Device* node: there KNX controls a
|
|
|
41
41
|
|
|
42
42
|
## Advanced compatibility
|
|
43
43
|
|
|
44
|
-
These options are hidden unless they apply to the selected type. Dimmable devices can ignore the brightness command that some controllers send immediately after `On`. Covers can optimistically update the Matter position after a command, then correct it when the KNX status GA reports the real position.
|
|
44
|
+
These options are hidden unless they apply to the selected type. Dimmable devices can ignore the brightness command that some controllers send immediately after `On`. For covers, **Swap Open / Close** reverses both the binary KNX command and percentage direction. **Cover slider debounce** coalesces rapid intermediate targets before writing to KNX: `0` uses adaptive windows (400 ms for the first command, 150 ms for subsequent commands); `1`–`5000` forces one fixed window. Covers can also optimistically update the Matter position after a command, then correct it when the KNX status GA reports the real position.
|
|
45
45
|
|
|
46
46
|
## Node PINs
|
|
47
47
|
|
|
@@ -20,17 +20,22 @@
|
|
|
20
20
|
"advanced": {
|
|
21
21
|
"title": "Advanced compatibility",
|
|
22
22
|
"help": " Use these options only when a Matter controller or a KNX actuator needs special handling.",
|
|
23
|
+
"alexa_dedup_ms": "Alexa duplicate-command window (ms, 0 = off)",
|
|
23
24
|
"turn_on_behavior": "Turn-on behavior",
|
|
24
25
|
"turn_on_ignore_level": "Ignore brightness sent immediately after On",
|
|
25
26
|
"turn_on_forward_all": "Forward every brightness command",
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
"ignore_level_ms": "Ignore brightness after On (ms)",
|
|
28
|
+
"cover_swap_open_close": "Swap cover Open / Close",
|
|
29
|
+
"cover_slider_debounce": "Cover slider debounce (ms, 0 = adaptive)",
|
|
29
30
|
"cover_update_mode": "Cover state update",
|
|
30
31
|
"cover_optimistic": "Optimistically update Matter position",
|
|
31
32
|
"cover_wait_status": "Wait for KNX status only",
|
|
32
33
|
"cover_status_timeout": "Cover status timeout (ms)"
|
|
33
34
|
},
|
|
35
|
+
"tabs": {
|
|
36
|
+
"mappings": "KNX mappings",
|
|
37
|
+
"advanced": "Advanced options"
|
|
38
|
+
},
|
|
34
39
|
"functions": {
|
|
35
40
|
"fn_onoff_cmd": "On/Off command GA",
|
|
36
41
|
"fn_onoff_status": "On/Off status GA",
|
|
@@ -44,6 +49,8 @@
|
|
|
44
49
|
"fn_currenttemp": "Current temperature GA",
|
|
45
50
|
"fn_setpoint_cmd": "Setpoint command GA",
|
|
46
51
|
"fn_setpoint_status": "Setpoint status GA",
|
|
52
|
+
"fn_coolingsetpoint_cmd": "Cooling setpoint command GA",
|
|
53
|
+
"fn_coolingsetpoint_status": "Cooling setpoint status GA",
|
|
47
54
|
"fn_rgb_cmd": "RGB color command GA (DPT 232.600)",
|
|
48
55
|
"fn_rgb_status": "RGB color status GA (DPT 232.600)",
|
|
49
56
|
"fn_ct_cmd": "Color temperature command GA (Kelvin)",
|
|
@@ -28,7 +28,7 @@ Es la dirección opuesta al nodo *Matter Device*: allí KNX controla un disposit
|
|
|
28
28
|
| Luz RGB (color) | + GA comando/estado color RGB (DPT 232.600). El color Matter (hue/saturation o XY, de la rueda de color de la app) se convierte desde/hacia la terna RGB KNX |
|
|
29
29
|
| Luz blanco dinámico | + GA comando/estado temperatura de color en Kelvin (DPT 7.600) |
|
|
30
30
|
| Persiana / Cortina | Subir/Bajar (DPT 1.008), Stop (DPT 1.017), posición % comando/estado (DPT 5.001), inversión de posición opcional |
|
|
31
|
-
| Termostato (calefacción) | GA temperatura actual, GA comando/estado consigna (DPT 9.001) |
|
|
31
|
+
| Termostato (calefacción y/o refrigeración) | GA temperatura actual, GA comando/estado consigna (DPT 9.001). Añadiendo también la GA comando/estado consigna de refrigeración se expone también el modo Cool (doble consigna) |
|
|
32
32
|
| Ventilador / VMC | GA comando/estado velocidad % (DPT 5.001) |
|
|
33
33
|
| Sensores (temperatura, humedad, luz, presencia, contacto) | Una GA de estado cada uno |
|
|
34
34
|
| Alarma humo/CO | GA estado alarma de humo + GA estado alarma CO opcional (DPT 1.005): notificaciones críticas en el teléfono |
|
|
@@ -41,7 +41,7 @@ Es la dirección opuesta al nodo *Matter Device*: allí KNX controla un disposit
|
|
|
41
41
|
|
|
42
42
|
## Compatibilidad avanzada
|
|
43
43
|
|
|
44
|
-
Estas opciones solo aparecen cuando tienen sentido para el tipo seleccionado. Los dispositivos regulables pueden ignorar el
|
|
44
|
+
Estas opciones solo aparecen cuando tienen sentido para el tipo seleccionado. Los dispositivos regulables pueden ignorar el brillo enviado justo después de `On`. Para persianas, **Intercambiar Abrir / Cerrar** invierte el comando KNX binario y la dirección porcentual. **Antirrebote del deslizador** agrupa objetivos intermedios rápidos antes de escribir en KNX: `0` usa ventanas adaptativas (400 ms para el primer comando y 150 ms para los siguientes); `1`–`5000` fuerza una ventana fija. La posición Matter también puede actualizarse de forma optimista y corregirse después mediante la GA de estado KNX.
|
|
45
45
|
|
|
46
46
|
## PINes del nodo
|
|
47
47
|
|