node-red-contrib-knx-ultimate 6.0.12 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +12 -2
  2. package/examples/Matter Controller - Semantic Flow Input.json +248 -0
  3. package/nodes/knxUltimateMatterBridge.html +144 -11
  4. package/nodes/knxUltimateMatterControllerDevice.html +298 -13
  5. package/nodes/knxUltimateMatterControllerDevice.js +37 -0
  6. package/nodes/locales/de/knxUltimateMatterBridge.html +1 -1
  7. package/nodes/locales/de/knxUltimateMatterBridge.json +14 -0
  8. package/nodes/locales/de/knxUltimateMatterControllerDevice.html +13 -1
  9. package/nodes/locales/de/knxUltimateMatterControllerDevice.json +23 -0
  10. package/nodes/locales/de/matter-config.html +11 -1
  11. package/nodes/locales/de/matter-config.json +12 -0
  12. package/nodes/locales/en/knxUltimateMatterBridge.html +1 -1
  13. package/nodes/locales/en/knxUltimateMatterBridge.json +14 -0
  14. package/nodes/locales/en/knxUltimateMatterControllerDevice.html +13 -1
  15. package/nodes/locales/en/knxUltimateMatterControllerDevice.json +23 -0
  16. package/nodes/locales/en/matter-config.html +11 -1
  17. package/nodes/locales/en/matter-config.json +12 -0
  18. package/nodes/locales/es/knxUltimateMatterBridge.html +1 -1
  19. package/nodes/locales/es/knxUltimateMatterBridge.json +14 -0
  20. package/nodes/locales/es/knxUltimateMatterControllerDevice.html +13 -1
  21. package/nodes/locales/es/knxUltimateMatterControllerDevice.json +23 -0
  22. package/nodes/locales/es/matter-config.html +11 -1
  23. package/nodes/locales/es/matter-config.json +12 -0
  24. package/nodes/locales/fr/knxUltimateMatterBridge.html +1 -1
  25. package/nodes/locales/fr/knxUltimateMatterBridge.json +14 -0
  26. package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +13 -1
  27. package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +31 -0
  28. package/nodes/locales/fr/matter-config.html +11 -1
  29. package/nodes/locales/fr/matter-config.json +12 -0
  30. package/nodes/locales/it/knxUltimateMatterBridge.html +1 -1
  31. package/nodes/locales/it/knxUltimateMatterBridge.json +14 -0
  32. package/nodes/locales/it/knxUltimateMatterControllerDevice.html +13 -1
  33. package/nodes/locales/it/knxUltimateMatterControllerDevice.json +23 -0
  34. package/nodes/locales/it/matter-config.html +11 -1
  35. package/nodes/locales/it/matter-config.json +12 -0
  36. package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +1 -1
  37. package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +14 -0
  38. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +13 -1
  39. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +31 -0
  40. package/nodes/locales/zh-CN/matter-config.html +11 -1
  41. package/nodes/locales/zh-CN/matter-config.json +12 -0
  42. package/nodes/matter-config.html +215 -23
  43. package/nodes/matter-config.js +48 -2
  44. package/nodes/utils/matterControllerCommandGate.js +47 -0
  45. package/nodes/utils/matterControllerProfiles/doorLock.js +19 -4
  46. package/nodes/utils/matterControllerProfiles/fan.js +0 -1
  47. package/nodes/utils/matterControllerProfiles/mappedEndpoint.js +32 -15
  48. package/nodes/utils/matterControllerProfiles/switch.js +0 -1
  49. package/nodes/utils/matterControllerProfiles/thermostat.js +0 -1
  50. package/nodes/utils/matterControllerProfiles/windowCovering.js +0 -1
  51. package/nodes/utils/matterControllerSemanticInput.js +212 -0
  52. package/nodes/utils/matterEngine.mjs +118 -16
  53. package/package.json +1 -1
  54. package/resources/matterQrScanner.js +98 -0
  55. package/resources/zxing-browser-0.2.0.min.js +1 -0
  56. package/resources/zxing-browser-LICENSE.txt +21 -0
package/CHANGELOG.md CHANGED
@@ -6,14 +6,24 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
- **Version 6.0.12** - July 2026<br/>
9
+ **Version 6.1.1** - July 2026<br/>
10
10
 
11
+ - **Control Matter from KNX — contextual flow PIN help**: **Node Input/Output PINs** now reveals the capability-driven **Flow input** section directly below the selector instead of adding a separate editor tab. The section remains hidden while the pins are disabled. Light endpoints now receive contextual, copyable examples for their supported top-level `msg.on`, `msg.dimming`, `msg.color_temperature` and `msg.color` contracts, while non-light endpoints retain their advertised simple and advanced Matter examples.<br/>
12
+ - **Matter Bridge — contextual flow PIN help**: **Node Input/Output PINs** now sits outside the editor tabs and, when enabled, reveals a contextual **Flow input/output** section directly below it. Copyable Flow → Matter and Matter → Flow examples are filtered to the selected device type, covering lights, covers, thermostats, fans, sensors, alarms and the flow-only robot vacuum while documenting only the message contracts already accepted or emitted by the runtime.<br/>
13
+
14
+ **Version 6.1.0** - July 2026<br/>
15
+
16
+ - **Matter Controller — QR pairing-code reader**: the pairing editor can now fill the existing `MT:…` field by scanning a QR code with the local webcam or reading it from a selected image, including inverted white-on-dark QR codes used by some device web interfaces. Decoding remains entirely in the browser and never uploads the image or video; after a valid QR is read, pairing starts automatically, while manually typed codes still require **PAIR**. The optional device name is positioned before the scanner, the decoded code remains visible underneath it and fabric backup controls have moved to the bottom of the editor. A prominent blocking progress overlay prevents further editor interactions until commissioning succeeds or fails. When live camera access is unavailable because the editor is not open over HTTPS/`localhost`, permission was denied or the browser lacks camera support, the editor explains the reason and keeps image loading available.<br/>
17
+ - **Matter Controller — commissioned-device status**: fixed the connection-state import for the installed matter.js API, so the associated-devices table now reports `connected`, `disconnected`, `reconnecting` or `waitingfordiscovery` instead of misleading values such as `unknown (0)`. The table has also been regression-tested to preserve every row returned by the controller without collapsing equal Node IDs.<br/>
18
+ - **Matter Controller — per-device command isolation**: commands now remain ordered inside an independent execution lane for each commissioned Node ID instead of sharing one blocking serial queue. An offline, timing-out or removed device can no longer delay other Matter Controller nodes that react to the same KNX group address. Removed Node IDs are rejected before queueing, every Matter operation has a bounded timeout and pending commands are purged on removal. An unavailable Controller node latches its red status and ignores subsequent KNX/flow commands instead of accepting them optimistically; it resumes automatically when that specific Matter device reports `connected`, while opening the node editor explicitly enables a manual retry.<br/>
11
19
  - **Control Matter from KNX — Universal Mode**: a dedicated **Mode** selector now switches between a single Matter device and controller-wide Universal Mode. Its **Universal Battery Monitor** scans every commissioned node and endpoint for battery Power Source clusters, maintains a normalized cache, publishes an optional aggregate KNX alarm as DPT 1.005, cycles low-battery device names every 2 seconds as DPT 16.001, and answers `getAllBatteries` flow queries. The alternative **All Matter events** service preserves the generic attribute/event stream and dynamic read/write/command input.<br/>
12
20
  - **Universal Battery Monitor — KNX editor and diagnostics**: battery options now use the same vertical-tab and ETS/CSV group-address selectors as single-device profiles. Alarm and text mappings are aligned as Function/GA/DPT/Name rows, support editable names and KNX read responses, ignore wired Power Source endpoints, and show an aggregate battery status instead of per-device `unknown` states.<br/>
13
21
  - **Matter light capability refresh**: Color Control `featureMap` and the endpoint device type now drive the Tunable White and RGB/HSV tabs. New selections, changed devices and previously saved nodes normalize stale capabilities so Tunable White lights no longer retain unsupported colour mappings.<br/>
14
22
  - **Matter editor/help cleanup**: decorative animated light icons and duplicate localized help headings were removed from the Matter Controller, Matter Bridge and shared light editor where applicable.<br/>
15
23
  - **Matter Bridge — editor tab orientation**: the device editor now uses the same left-hand vertical tab layout as Matter Controller, keeping Mappings and Advanced options visually consistent without changing saved configuration.<br/>
16
- - **Control Matter from KNX — dedicated controller profiles**: Window Covering (`0x0102`), Thermostat (`0x0201`), Fan Control (`0x0202`) and Switch (`0x003B`) endpoints are now selected into explicit capability-driven profiles instead of the anonymous mapped fallback. They share the guarded mapping lifecycle while enforcing their native input/event cluster boundary and presenting semantic canvas status; Switch initial/long/multi-press events are filtered to the selected endpoint and exposed on the optional flow output. Simple actuators and sensors remain on the generic mapped profile.<br/>
24
+ - **Control Matter from KNX — simple flow input and capability help**: non-light endpoints now accept the same human-readable `msg.payload = { function, value }` contract used by Matter Bridge. The selected node and endpoint are implicit; percentages, temperatures and measurements use human units, while omitting `value` reads a supported state. A capability-driven **Flow input** tab, consistent with the node's existing editor tabs, replaces the modal help button and provides copyable simple examples plus every advertised readable/writable attribute, command, cluster ID and advanced message. It remains available for flow-only nodes without a KNX gateway. Existing top-level `clusterId`/`command`/`attribute` inputs remain compatible, and Door Lock also accepts `{function:"lock",value:true|false}`.<br/>
25
+ - **Examples — Matter semantic flow input**: added an importable, non-destructive Matter Controller example with manual Inject nodes for On/Off, level, cover position, thermostat setpoint, temperature read and Identify. It deliberately creates no `matter-config`, sends nothing automatically and directs users to select an existing fabric/endpoint and use only functions advertised in the **Flow input** tab.<br/>
26
+ - **Control Matter from KNX — dedicated controller profiles**: Window Covering (`0x0102`), Thermostat (`0x0201`), Fan Control (`0x0202`) and Switch (`0x003B`) endpoints are now selected into explicit capability-driven profiles instead of the anonymous mapped fallback. They share the guarded mapping lifecycle while presenting semantic canvas status; Switch initial/long/multi-press events are filtered to the selected endpoint and exposed on the optional flow output. Simple actuators and sensors remain on the generic mapped profile.<br/>
17
27
  - **Control Matter from KNX — flow attribute reads and persistent Door Lock status**: mapped endpoints now use an unambiguous top-level flow contract: `msg.clusterId` plus `msg.attribute` reads an attribute, `msg.value` makes it an attribute write, and `msg.command` plus `msg.args` invokes a command. Reads return the value in `msg.payload`, may force a remote request with `msg.requestFromRemote = true`, and accept numeric attribute ID `0`. Door Lock nodes now retain their last `locked`/`unlocked` canvas status when generic Matter or KNX notifications arrive.<br/>
18
28
 
19
29
  **Version 6.0.10** - July 2026<br/>
@@ -0,0 +1,248 @@
1
+ [
2
+ {
3
+ "id": "matter_semantic_tab",
4
+ "type": "tab",
5
+ "label": "Matter Controller - Flow input",
6
+ "disabled": false,
7
+ "info": "Example of the semantic msg.payload = { function, value } contract for Control Matter from KNX (BETA).\n\nAfter importing:\n1. Open the example Matter Controller node.\n2. Select your existing Matter Controller configuration and a commissioned non-light endpoint.\n3. Enable Node Input/Output PINs.\n4. In the Flow input section shown below the PIN selector, use only the Inject nodes matching functions advertised by the device.\n\nThis example creates no matter-config and sends no command automatically."
8
+ },
9
+ {
10
+ "id": "matter_semantic_instructions",
11
+ "type": "comment",
12
+ "z": "matter_semantic_tab",
13
+ "name": "1. Open the Controller, select matter-config + endpoint, then enable the flow PINs",
14
+ "info": "The Inject nodes are alternative examples: one endpoint normally does not support every function shown. Use only functions listed in the Controller node's Flow input section.",
15
+ "x": 520,
16
+ "y": 60,
17
+ "wires": []
18
+ },
19
+ {
20
+ "id": "matter_semantic_supported",
21
+ "type": "comment",
22
+ "z": "matter_semantic_tab",
23
+ "name": "2. Trigger only Inject nodes supported by the selected endpoint",
24
+ "info": "No Inject is configured to run automatically. Reads omit value; writes include value.",
25
+ "x": 340,
26
+ "y": 110,
27
+ "wires": []
28
+ },
29
+ {
30
+ "id": "matter_semantic_on",
31
+ "type": "inject",
32
+ "z": "matter_semantic_tab",
33
+ "name": "On: {onoff, true}",
34
+ "props": [
35
+ {
36
+ "p": "payload",
37
+ "v": "{\"function\":\"onoff\",\"value\":true}",
38
+ "vt": "json"
39
+ }
40
+ ],
41
+ "repeat": "",
42
+ "crontab": "",
43
+ "once": false,
44
+ "onceDelay": 0.1,
45
+ "topic": "",
46
+ "x": 190,
47
+ "y": 180,
48
+ "wires": [
49
+ [
50
+ "matter_semantic_controller"
51
+ ]
52
+ ]
53
+ },
54
+ {
55
+ "id": "matter_semantic_off",
56
+ "type": "inject",
57
+ "z": "matter_semantic_tab",
58
+ "name": "Off: {onoff, false}",
59
+ "props": [
60
+ {
61
+ "p": "payload",
62
+ "v": "{\"function\":\"onoff\",\"value\":false}",
63
+ "vt": "json"
64
+ }
65
+ ],
66
+ "repeat": "",
67
+ "crontab": "",
68
+ "once": false,
69
+ "onceDelay": 0.1,
70
+ "topic": "",
71
+ "x": 190,
72
+ "y": 230,
73
+ "wires": [
74
+ [
75
+ "matter_semantic_controller"
76
+ ]
77
+ ]
78
+ },
79
+ {
80
+ "id": "matter_semantic_level",
81
+ "type": "inject",
82
+ "z": "matter_semantic_tab",
83
+ "name": "Level 50%",
84
+ "props": [
85
+ {
86
+ "p": "payload",
87
+ "v": "{\"function\":\"level\",\"value\":50}",
88
+ "vt": "json"
89
+ }
90
+ ],
91
+ "repeat": "",
92
+ "crontab": "",
93
+ "once": false,
94
+ "onceDelay": 0.1,
95
+ "topic": "",
96
+ "x": 180,
97
+ "y": 280,
98
+ "wires": [
99
+ [
100
+ "matter_semantic_controller"
101
+ ]
102
+ ]
103
+ },
104
+ {
105
+ "id": "matter_semantic_position",
106
+ "type": "inject",
107
+ "z": "matter_semantic_tab",
108
+ "name": "Cover position 35%",
109
+ "props": [
110
+ {
111
+ "p": "payload",
112
+ "v": "{\"function\":\"position\",\"value\":35}",
113
+ "vt": "json"
114
+ }
115
+ ],
116
+ "repeat": "",
117
+ "crontab": "",
118
+ "once": false,
119
+ "onceDelay": 0.1,
120
+ "topic": "",
121
+ "x": 190,
122
+ "y": 330,
123
+ "wires": [
124
+ [
125
+ "matter_semantic_controller"
126
+ ]
127
+ ]
128
+ },
129
+ {
130
+ "id": "matter_semantic_setpoint",
131
+ "type": "inject",
132
+ "z": "matter_semantic_tab",
133
+ "name": "Setpoint 21,5 °C",
134
+ "props": [
135
+ {
136
+ "p": "payload",
137
+ "v": "{\"function\":\"setpoint\",\"value\":21.5}",
138
+ "vt": "json"
139
+ }
140
+ ],
141
+ "repeat": "",
142
+ "crontab": "",
143
+ "once": false,
144
+ "onceDelay": 0.1,
145
+ "topic": "",
146
+ "x": 190,
147
+ "y": 380,
148
+ "wires": [
149
+ [
150
+ "matter_semantic_controller"
151
+ ]
152
+ ]
153
+ },
154
+ {
155
+ "id": "matter_semantic_temperature",
156
+ "type": "inject",
157
+ "z": "matter_semantic_tab",
158
+ "name": "Read temperature",
159
+ "props": [
160
+ {
161
+ "p": "payload",
162
+ "v": "{\"function\":\"temperature\"}",
163
+ "vt": "json"
164
+ }
165
+ ],
166
+ "repeat": "",
167
+ "crontab": "",
168
+ "once": false,
169
+ "onceDelay": 0.1,
170
+ "topic": "",
171
+ "x": 190,
172
+ "y": 430,
173
+ "wires": [
174
+ [
175
+ "matter_semantic_controller"
176
+ ]
177
+ ]
178
+ },
179
+ {
180
+ "id": "matter_semantic_identify",
181
+ "type": "inject",
182
+ "z": "matter_semantic_tab",
183
+ "name": "Identify",
184
+ "props": [
185
+ {
186
+ "p": "payload",
187
+ "v": "{\"function\":\"identify\"}",
188
+ "vt": "json"
189
+ }
190
+ ],
191
+ "repeat": "",
192
+ "crontab": "",
193
+ "once": false,
194
+ "onceDelay": 0.1,
195
+ "topic": "",
196
+ "x": 170,
197
+ "y": 480,
198
+ "wires": [
199
+ [
200
+ "matter_semantic_controller"
201
+ ]
202
+ ]
203
+ },
204
+ {
205
+ "id": "matter_semantic_controller",
206
+ "type": "knxUltimateMatterControllerDevice",
207
+ "z": "matter_semantic_tab",
208
+ "server": "",
209
+ "serverMatter": "",
210
+ "matterControllerMode": "single",
211
+ "universalService": "batteryMonitor",
212
+ "matterNodeId": "",
213
+ "matterEndpointId": 1,
214
+ "matterDeviceName": "",
215
+ "matterDeviceCapabilities": "",
216
+ "matterMappings": "[]",
217
+ "name": "SELECT A MATTER ENDPOINT",
218
+ "readStatusAtStartup": "no",
219
+ "enableNodePINS": "yes",
220
+ "updateLocalStateFromKNXWrite": true,
221
+ "inputs": 1,
222
+ "outputs": 1,
223
+ "x": 570,
224
+ "y": 330,
225
+ "wires": [
226
+ [
227
+ "matter_semantic_debug"
228
+ ]
229
+ ]
230
+ },
231
+ {
232
+ "id": "matter_semantic_debug",
233
+ "type": "debug",
234
+ "z": "matter_semantic_tab",
235
+ "name": "Complete Matter response",
236
+ "active": true,
237
+ "tosidebar": true,
238
+ "console": false,
239
+ "tostatus": true,
240
+ "complete": "true",
241
+ "targetType": "full",
242
+ "statusVal": "payload",
243
+ "statusType": "auto",
244
+ "x": 850,
245
+ "y": 330,
246
+ "wires": []
247
+ }
248
+ ]
@@ -67,6 +67,28 @@
67
67
  robotvacuum: [] // Flow-only: no GA fields, controlled via the node PINs
68
68
  };
69
69
  const TYPE_ORDER = ['onofflight', 'dimmablelight', 'rgblight', 'colortemperaturelight', 'onoffplug', 'windowcovering', 'thermostat', 'fan', 'temperaturesensor', 'humiditysensor', 'lightsensor', 'occupancysensor', 'contactsensor', 'smokecoalarm', 'waterleakdetector', 'airqualitysensor', 'robotvacuum'];
70
+ const FLOW_HELP = {
71
+ onofflight: { input: [['onoff', true]], output: [['onoff', true]] },
72
+ onoffplug: { input: [['onoff', true]], output: [['onoff', true]] },
73
+ dimmablelight: { input: [['onoff', true], ['level', 50]], output: [['onoff', true], ['level', 50]] },
74
+ rgblight: { input: [['onoff', true], ['level', 50], ['rgb', { red: 255, green: 80, blue: 20 }]], output: [['onoff', true], ['level', 50], ['rgb', { red: 255, green: 80, blue: 20 }]] },
75
+ colortemperaturelight: { input: [['onoff', true], ['level', 50], ['colortemp', 3000]], output: [['onoff', true], ['level', 50], ['colortemp', 3000]] },
76
+ temperaturesensor: { input: [['temperature', 21.5]], output: [] },
77
+ humiditysensor: { input: [['humidity', 45]], output: [] },
78
+ lightsensor: { input: [['illuminance', 500]], output: [] },
79
+ occupancysensor: { input: [['occupancy', true]], output: [] },
80
+ contactsensor: { input: [['contact', true]], output: [] },
81
+ windowcovering: { input: [['position', 35]], output: [['updown', false], ['position', 35], ['stop', true]] },
82
+ thermostat: { input: [['currenttemp', 21.5], ['setpoint', 21], ['coolingsetpoint', 24]], output: [['setpoint', 21], ['coolingsetpoint', 24]] },
83
+ smokecoalarm: { input: [['smoke', true], ['co', true]], output: [] },
84
+ waterleakdetector: { input: [['leak', true]], output: [] },
85
+ airqualitysensor: { input: [['co2', 850]], output: [] },
86
+ fan: { input: [['fanspeed', 60]], output: [['fanspeed', 60]] },
87
+ robotvacuum: {
88
+ input: [['rvcstate', 'charging'], ['rvcmode', 'idle']],
89
+ output: [['rvccommand', 'gohome'], ['rvccommand', 'pause'], ['rvccommand', 'resume'], ['rvcmode', 'cleaning']]
90
+ }
91
+ };
70
92
 
71
93
  // Every GA/DPT key across all types, used to declare the node defaults.
72
94
  const ALL_KEYS = [];
@@ -167,6 +189,25 @@
167
189
  border-top: 1px solid #ccc;
168
190
  margin: 8px 0;
169
191
  }
192
+ .matter-bridge-flow-help-table {
193
+ width: 100%;
194
+ border-collapse: collapse;
195
+ margin-bottom: 12px;
196
+ }
197
+ .matter-bridge-flow-help-table th,
198
+ .matter-bridge-flow-help-table td {
199
+ border-bottom: 1px solid #ddd;
200
+ padding: 7px 6px;
201
+ text-align: left;
202
+ vertical-align: top;
203
+ }
204
+ .matter-bridge-flow-help-table code {
205
+ white-space: pre-wrap;
206
+ word-break: break-word;
207
+ }
208
+ .matter-bridge-flow-help-copy {
209
+ white-space: nowrap;
210
+ }
170
211
  </style>`);
171
212
  }
172
213
  const $tabs = $('#mb-tabs');
@@ -217,6 +258,9 @@
217
258
  const $commandAdvancedRows = $('.mb-command-advanced-row');
218
259
  const $dimmerAdvancedRows = $('.mb-dimmer-advanced-row');
219
260
  const $coverAdvancedRows = $('.mb-cover-advanced-row');
261
+ const $pinSelect = $('#node-input-enableNodePINS');
262
+ const $flowHelp = $('#matter-bridge-flow-help');
263
+ const $nameInput = $('#node-input-name');
220
264
  const gaInputs = {};
221
265
 
222
266
  function createLabeledField(row, element, options = {}) {
@@ -272,6 +316,51 @@
272
316
  gaInputs[key] = { wrapper, $ga, $dpt, $labelSpan };
273
317
  });
274
318
 
319
+ const inputMessageCode = (fn, value) => `msg.payload = ${JSON.stringify({ function: fn, value }, null, 2)};\nreturn msg;`;
320
+ const outputMessageCode = (fn, value) => JSON.stringify({
321
+ topic: ($nameInput.val() || node.name || 'Matter device').toString(),
322
+ payload: value,
323
+ matter: { fn, value }
324
+ }, null, 2);
325
+ const appendFlowHelpRow = ($body, fn, code) => {
326
+ const $row = $('<tr></tr>');
327
+ $('<td><code></code></td>').find('code').text(fn).end().appendTo($row);
328
+ $('<td><code></code></td>').find('code').text(code).end().appendTo($row);
329
+ $('<td></td>').append(
330
+ $('<button type="button" class="red-ui-button matter-bridge-flow-help-copy"><i class="fa fa-copy"></i></button>')
331
+ .attr('title', node._('knxUltimateMatterBridge.flow_help.copy') || 'Copy')
332
+ .data('copy-text', code)
333
+ ).appendTo($row);
334
+ $body.append($row);
335
+ };
336
+ const copyFlowHelpText = (value) => {
337
+ const copied = () => RED.notify(node._('knxUltimateMatterBridge.flow_help.copied') || 'Message copied', 'success');
338
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
339
+ navigator.clipboard.writeText(value).then(copied).catch(() => {
340
+ const $temporary = $('<textarea></textarea>').val(value).appendTo('body').select();
341
+ try { document.execCommand('copy'); copied(); } finally { $temporary.remove(); }
342
+ });
343
+ return;
344
+ }
345
+ const $temporary = $('<textarea></textarea>').val(value).appendTo('body').select();
346
+ try { document.execCommand('copy'); copied(); } finally { $temporary.remove(); }
347
+ };
348
+ const renderFlowHelp = () => {
349
+ const examples = FLOW_HELP[$type.val()] || { input: [], output: [] };
350
+ const $inputBody = $('#matter-bridge-flow-help-input-body').empty();
351
+ const $outputBody = $('#matter-bridge-flow-help-output-body').empty();
352
+ examples.input.forEach(([fn, value]) => appendFlowHelpRow($inputBody, fn, inputMessageCode(fn, value)));
353
+ examples.output.forEach(([fn, value]) => appendFlowHelpRow($outputBody, fn, outputMessageCode(fn, value)));
354
+ $('#matter-bridge-flow-help-input-empty').toggle(examples.input.length === 0);
355
+ $('#matter-bridge-flow-help-output-empty').toggle(examples.output.length === 0);
356
+ $('#matter-bridge-flow-help-output-table').toggle(examples.output.length > 0);
357
+ };
358
+ const updateFlowHelpVisibility = () => {
359
+ const visible = $pinSelect.val() === 'yes';
360
+ $flowHelp.toggle(visible);
361
+ if (visible) renderFlowHelp();
362
+ };
363
+
275
364
  function updateTypePresentation() {
276
365
  const selected = $type.val();
277
366
  const active = TYPE_FIELDS[selected] || [];
@@ -295,6 +384,7 @@
295
384
  $commandAdvancedRows.toggle(hasCommand);
296
385
  $dimmerAdvancedRows.toggle(hasLevel);
297
386
  $coverAdvancedRows.toggle(isCover);
387
+ updateFlowHelpVisibility();
298
388
  }
299
389
  $type.on('change', updateTypePresentation);
300
390
  updateTypePresentation();
@@ -312,7 +402,15 @@
312
402
  $('#node-input-coverUpdateMode').val(node.coverUpdateMode || 'optimistic');
313
403
  $('#node-input-coverStatusTimeoutMs').val(node.coverStatusTimeoutMs === undefined ? 3000 : node.coverStatusTimeoutMs);
314
404
  $('#node-input-readStatusAtStartup').val(node.readStatusAtStartup || 'yes');
315
- $('#node-input-enableNodePINS').val(node.enableNodePINS || 'no');
405
+ $pinSelect.val(node.enableNodePINS || 'no');
406
+ $pinSelect.on('change.knxUltimateMatterBridge', updateFlowHelpVisibility);
407
+ $nameInput.on('change.knxUltimateMatterBridge input.knxUltimateMatterBridge', () => {
408
+ if ($flowHelp.is(':visible')) renderFlowHelp();
409
+ });
410
+ $flowHelp.off('click.knxUltimateMatterBridge').on('click.knxUltimateMatterBridge', '.matter-bridge-flow-help-copy', function () {
411
+ copyFlowHelpText($(this).data('copy-text') || '');
412
+ });
413
+ updateFlowHelpVisibility();
316
414
  },
317
415
  oneditsave() {
318
416
  try { RED.sidebar.show('info'); } catch (error) { /* empty */ }
@@ -479,18 +577,52 @@
479
577
  </select>
480
578
  </div>
481
579
 
482
- <div class="form-row">
483
- <label for="node-input-enableNodePINS" style="width:260px;">
484
- <i class="fa fa-code"></i> <span data-i18n="knxUltimateMatterBridge.node_pins"></span>
485
- </label>
486
- <select id="node-input-enableNodePINS" style="width:220px;">
487
- <option value="no" data-i18n="knxUltimateMatterBridge.node_pins_hide"></option>
488
- <option value="yes" data-i18n="knxUltimateMatterBridge.node_pins_show"></option>
489
- </select>
490
- </div>
491
580
  </div>
492
581
  </div>
493
582
 
583
+ <div class="form-row" style="margin-top:12px;">
584
+ <label for="node-input-enableNodePINS" style="width:260px;">
585
+ <i class="fa fa-code"></i> <span data-i18n="knxUltimateMatterBridge.node_pins"></span>
586
+ </label>
587
+ <select id="node-input-enableNodePINS" style="width:220px;">
588
+ <option value="no" data-i18n="knxUltimateMatterBridge.node_pins_hide"></option>
589
+ <option value="yes" data-i18n="knxUltimateMatterBridge.node_pins_show"></option>
590
+ </select>
591
+ </div>
592
+
593
+ <div id="matter-bridge-flow-help" style="display:none; width:900px; margin-top:12px;">
594
+ <h4><i class="fa fa-code"></i> <span data-i18n="knxUltimateMatterBridge.flow_help.title"></span></h4>
595
+ <p data-i18n="knxUltimateMatterBridge.flow_help.intro"></p>
596
+
597
+ <h4 data-i18n="knxUltimateMatterBridge.flow_help.input_title"></h4>
598
+ <p data-i18n="knxUltimateMatterBridge.flow_help.input_hint"></p>
599
+ <table class="matter-bridge-flow-help-table">
600
+ <thead>
601
+ <tr>
602
+ <th data-i18n="knxUltimateMatterBridge.flow_help.function"></th>
603
+ <th data-i18n="knxUltimateMatterBridge.flow_help.message"></th>
604
+ <th></th>
605
+ </tr>
606
+ </thead>
607
+ <tbody id="matter-bridge-flow-help-input-body"></tbody>
608
+ </table>
609
+ <p id="matter-bridge-flow-help-input-empty" style="display:none;" data-i18n="knxUltimateMatterBridge.flow_help.no_input"></p>
610
+
611
+ <h4 data-i18n="knxUltimateMatterBridge.flow_help.output_title"></h4>
612
+ <p data-i18n="knxUltimateMatterBridge.flow_help.output_hint"></p>
613
+ <table id="matter-bridge-flow-help-output-table" class="matter-bridge-flow-help-table">
614
+ <thead>
615
+ <tr>
616
+ <th data-i18n="knxUltimateMatterBridge.flow_help.function"></th>
617
+ <th data-i18n="knxUltimateMatterBridge.flow_help.message"></th>
618
+ <th></th>
619
+ </tr>
620
+ </thead>
621
+ <tbody id="matter-bridge-flow-help-output-body"></tbody>
622
+ </table>
623
+ <p id="matter-bridge-flow-help-output-empty" style="display:none;" data-i18n="knxUltimateMatterBridge.flow_help.no_output"></p>
624
+ </div>
625
+
494
626
  <br/>
495
627
  <br/>
496
628
  <br/>
@@ -518,7 +650,8 @@ The advanced compatibility options are intentionally type-specific:
518
650
 
519
651
  The **KNX gateway is optional**: without it, enable the node PINs and drive the device from the flow.
520
652
 
521
- If you enable the **node PINs**:
653
+ The PIN selector sits outside the tabs. Enabling it reveals a contextual **Flow input/output**
654
+ section directly below, with copyable examples filtered to the selected device type:
522
655
 
523
656
  - input: `msg.payload = { function: "onoff", value: true }` updates the Matter state without the KNX bus
524
657
  - output: every command from a Matter controller is forwarded as `msg` (topic = device name, payload = value, `msg.matter` = raw command)