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
@@ -152,6 +152,14 @@
152
152
 
153
153
 
154
154
  var node = this; // Starting from v 4.1.31
155
+ if (node.id) {
156
+ $.ajax({
157
+ url: "KNXUltimateMatterControllerEnable",
158
+ type: "POST",
159
+ contentType: "application/json",
160
+ data: JSON.stringify({ nodeId: node.id })
161
+ });
162
+ }
155
163
  $("#node-input-updateLocalStateFromKNXWrite").prop("checked", node.updateLocalStateFromKNXWrite === true || node.updateLocalStateFromKNXWrite === "true"); // Starting from v 4.1.31
156
164
  const ensureConfigSelection = (selector) => {
157
165
  if ($(selector).val() !== "_ADD_") return;
@@ -221,6 +229,31 @@
221
229
  border-top: 1px solid #ccc;
222
230
  margin: 8px 0;
223
231
  }
232
+ .matter-input-help-table {
233
+ width: 100%;
234
+ border-collapse: collapse;
235
+ margin: 8px 0 18px;
236
+ }
237
+ .matter-input-help-table th,
238
+ .matter-input-help-table td {
239
+ border-bottom: 1px solid #ddd;
240
+ padding: 7px 6px;
241
+ text-align: left;
242
+ vertical-align: top;
243
+ }
244
+ .matter-input-help-table code {
245
+ white-space: pre-wrap;
246
+ word-break: break-word;
247
+ }
248
+ .matter-input-help-copy {
249
+ white-space: nowrap;
250
+ }
251
+ .matter-input-help-advanced {
252
+ max-height: 310px;
253
+ overflow: auto;
254
+ border: 1px solid #ddd;
255
+ padding: 0 8px;
256
+ }
224
257
  </style>`;
225
258
  $('head').append(style);
226
259
  }
@@ -241,6 +274,7 @@
241
274
  const $refreshDevicesButton = $(".hue-refresh-devices");
242
275
  const $locateDeviceButton = $(".hue-locate-device");
243
276
  const $hueDevicesLoading = $(".hue-devices-loading");
277
+ const $inputHelpPanel = $("#matter-flow-input-help");
244
278
  const UNIVERSAL_NODE_ID = '__UNIVERSAL__';
245
279
  const isUniversalMode = () => $matterControllerModeInput.val() === 'universal';
246
280
  $matterControllerModeInput.val(node.matterControllerMode || 'single');
@@ -549,6 +583,19 @@
549
583
  return !!cluster && (cluster.commands || []).some((cmd) => wanted.has(normalizeMatterToken(cmd.name)));
550
584
  };
551
585
  const matterCapabilitiesFromEndpoint = (ep) => {
586
+ const inputStructure = {
587
+ endpointId: Number(ep.endpointId),
588
+ clusters: (ep.clusters || []).map((cluster) => ({
589
+ id: Number(cluster.id),
590
+ name: cluster.name || '',
591
+ attributes: (cluster.attributes || []).map((attribute) => ({
592
+ id: Number(attribute.id),
593
+ name: attribute.name,
594
+ writable: attribute.writable === true
595
+ })),
596
+ commands: (cluster.commands || []).map((command) => ({ name: command.name }))
597
+ }))
598
+ };
552
599
  const deviceTypes = (ep.deviceTypes || []).map(normalizeMatterToken);
553
600
  const primaryDeviceType = (ep.deviceTypes || []).find((value) => !/bridgednode|aggregator|controlbridge/i.test(String(value))) || ep.name;
554
601
  const deviceTypeDisplay = matterPrettyType(primaryDeviceType);
@@ -568,6 +615,7 @@
568
615
  color: false,
569
616
  lockDoor: hasMatterCommand(doorLockCluster, ['lockDoor']),
570
617
  unlockDoor: hasMatterCommand(doorLockCluster, ['unlockDoor']),
618
+ inputStructure,
571
619
  requirePinForRemoteOperation: (() => {
572
620
  const attribute = (doorLockCluster.attributes || []).find((item) => normalizeMatterToken(item.name) === 'requirepinforremoteoperation');
573
621
  return attribute ? attribute.value === true : false;
@@ -611,7 +659,7 @@
611
659
  addAttribute(47, 'batPercentRemaining', 'battery', '5.001', false);
612
660
  addAttribute(144, 'activePower', 'active_power', '14.056', false);
613
661
  addAttribute(145, 'cumulativeEnergyImported', 'imported_energy', '13.013', false);
614
- return { known: true, profile: specializedProfile, deviceTypeDisplay, onOff: false, level: false, colorTemperature: false, color: false, mappedTargets };
662
+ return { known: true, profile: specializedProfile, deviceTypeDisplay, onOff: false, level: false, colorTemperature: false, color: false, mappedTargets, inputStructure };
615
663
  }
616
664
  const hasColorTemperatureType = /colortemperature|extendedcolor/.test(typeText);
617
665
  const hasColorType = /extendedcolor/.test(typeText);
@@ -804,6 +852,164 @@
804
852
  $matterCapabilitiesInput.val(serializeMatterCapabilities(currentMatterCapabilities));
805
853
  node.matterDeviceCapabilities = $matterCapabilitiesInput.val();
806
854
  }
855
+
856
+ const semanticInputDefinitions = [
857
+ {
858
+ fn: 'onoff',
859
+ label: 'On / Off',
860
+ read: [6, ['onOff']],
861
+ writes: [
862
+ { descriptor: [6, ['on']], value: true },
863
+ { descriptor: [6, ['off']], value: false },
864
+ { descriptor: [6, ['toggle']], value: 'toggle' }
865
+ ]
866
+ },
867
+ { fn: 'level', label: 'Level', read: [8, ['currentLevel']], write: [8, ['moveToLevelWithOnOff', 'moveToLevel']], value: 50 },
868
+ { fn: 'position', label: 'Cover position', read: [258, ['currentPositionLiftPercent100ths', 'currentPositionLiftPercentage']], write: [258, ['goToLiftPercentage']], value: 35 },
869
+ { fn: 'tiltposition', label: 'Cover tilt', read: [258, ['currentPositionTiltPercent100ths', 'currentPositionTiltPercentage']], write: [258, ['goToTiltPercentage']], value: 35 },
870
+ { fn: 'open', label: 'Open', write: [258, ['upOrOpen']], omitValue: true },
871
+ { fn: 'close', label: 'Close', write: [258, ['downOrClose']], omitValue: true },
872
+ { fn: 'stop', label: 'Stop', write: [258, ['stopMotion']], omitValue: true },
873
+ { fn: 'setpoint', label: 'Heating setpoint', read: [513, ['occupiedHeatingSetpoint']], writeAttribute: [513, ['occupiedHeatingSetpoint']], value: 21.5 },
874
+ { fn: 'coolingsetpoint', label: 'Cooling setpoint', read: [513, ['occupiedCoolingSetpoint']], writeAttribute: [513, ['occupiedCoolingSetpoint']], value: 24 },
875
+ { fn: 'currenttemp', label: 'Current temperature', read: [513, ['localTemperature']] },
876
+ { fn: 'fanspeed', label: 'Fan speed', read: [514, ['percentCurrent', 'percentSetting']], writeAttribute: [514, ['percentSetting']], value: 60 },
877
+ { fn: 'temperature', label: 'Temperature', read: [1026, ['measuredValue']] },
878
+ { fn: 'humidity', label: 'Humidity', read: [1029, ['measuredValue']] },
879
+ { fn: 'illuminance', label: 'Illuminance', read: [1024, ['measuredValue']] },
880
+ { fn: 'occupancy', label: 'Occupancy', read: [1030, ['occupancy']] },
881
+ { fn: 'contact', label: 'Contact', read: [69, ['stateValue']] },
882
+ { fn: 'battery', label: 'Battery', read: [47, ['batPercentRemaining']] },
883
+ { fn: 'activepower', label: 'Active power', read: [144, ['activePower']] },
884
+ { fn: 'importedenergy', label: 'Imported energy', read: [145, ['cumulativeEnergyImported']] },
885
+ { fn: 'identify', label: 'Identify', write: [3, ['identify']], omitValue: true }
886
+ ];
887
+ const inputStructureFor = (caps) => caps && caps.inputStructure && Array.isArray(caps.inputStructure.clusters)
888
+ ? caps.inputStructure
889
+ : null;
890
+ const structureMember = (structure, targetKind, descriptor, writableOnly = false) => {
891
+ if (!structure || !descriptor) return null;
892
+ const cluster = structure.clusters.find((item) => Number(item.id) === Number(descriptor[0]));
893
+ if (!cluster) return null;
894
+ const collection = targetKind === 'command' ? cluster.commands : cluster.attributes;
895
+ if (!Array.isArray(collection)) return null;
896
+ const wanted = descriptor[1].map(normalizeMatterToken);
897
+ return collection.find((item) =>
898
+ wanted.includes(normalizeMatterToken(item.name)) &&
899
+ (!writableOnly || item.writable === true)
900
+ ) || null;
901
+ };
902
+ const functionCode = (payload) => `msg.payload = ${JSON.stringify(payload, null, 2)};\nreturn msg;`;
903
+ const advancedCode = (kind, clusterId, name) => {
904
+ if (kind === 'read') return `msg.clusterId = ${Number(clusterId)};\nmsg.attribute = ${JSON.stringify(name)};\nreturn msg;`;
905
+ if (kind === 'write') return `msg.clusterId = ${Number(clusterId)};\nmsg.attribute = ${JSON.stringify(name)};\nmsg.value = 0;\nreturn msg;`;
906
+ return `msg.clusterId = ${Number(clusterId)};\nmsg.command = ${JSON.stringify(name)};\nmsg.args = {};\nreturn msg;`;
907
+ };
908
+ const appendInputHelpRow = ($body, operation, label, code) => {
909
+ const $row = $('<tr></tr>');
910
+ $('<td></td>').text(operation).appendTo($row);
911
+ $('<td></td>').text(label).appendTo($row);
912
+ $('<td><code></code></td>').find('code').text(code).end().appendTo($row);
913
+ $('<td></td>').append(
914
+ $('<button type="button" class="red-ui-button matter-input-help-copy"><i class="fa fa-copy"></i></button>')
915
+ .attr('title', node._('knxUltimateMatterControllerDevice.input_help_copy') || 'Copy')
916
+ .data('copy-text', code)
917
+ ).appendTo($row);
918
+ $body.append($row);
919
+ };
920
+ const renderInputHelp = () => {
921
+ const caps = getEffectiveMatterCapabilities();
922
+ const structure = inputStructureFor(caps);
923
+ const endpointId = structure ? structure.endpointId : Number($matterEndpointInput.val() || node.matterEndpointId || 1);
924
+ const isMatterLight = !caps.profile;
925
+ $('#matter-input-help-endpoint').text(String(endpointId));
926
+ const $simpleBody = $('#matter-input-help-simple-body').empty();
927
+ if (isMatterLight) {
928
+ const writeLabel = node._('knxUltimateMatterControllerDevice.input_help_write') || 'Write';
929
+ if (caps.onOff !== false) {
930
+ appendInputHelpRow($simpleBody, writeLabel, node._('knxUltimateMatterControllerDevice.input_functions.on') || 'On', 'msg.on = { on: true };\nreturn msg;');
931
+ appendInputHelpRow($simpleBody, writeLabel, node._('knxUltimateMatterControllerDevice.input_functions.off') || 'Off', 'msg.on = { on: false };\nreturn msg;');
932
+ }
933
+ if (caps.level !== false) {
934
+ appendInputHelpRow($simpleBody, writeLabel, node._('knxUltimateMatterControllerDevice.input_functions.brightness') || 'Brightness', 'msg.dimming = { brightness: 50 };\nreturn msg;');
935
+ }
936
+ if (caps.colorTemperature === true) {
937
+ appendInputHelpRow($simpleBody, writeLabel, node._('knxUltimateMatterControllerDevice.input_functions.color_temperature') || 'Colour temperature', 'msg.color_temperature = { mirek: 370 };\nreturn msg;');
938
+ }
939
+ if (caps.color === true) {
940
+ appendInputHelpRow($simpleBody, writeLabel, node._('knxUltimateMatterControllerDevice.input_functions.xy_color') || 'XY colour', 'msg.color = { xy: { x: 0.675, y: 0.322 } };\nreturn msg;');
941
+ }
942
+ } else if (caps.profile === 'doorLock') {
943
+ if (caps.lockDoor !== false) appendInputHelpRow($simpleBody, node._('knxUltimateMatterControllerDevice.input_help_write') || 'Write', node._('knxUltimateMatterControllerDevice.input_functions.lock') || 'Lock', functionCode({ function: 'lock', value: true }));
944
+ if (caps.unlockDoor !== false) appendInputHelpRow($simpleBody, node._('knxUltimateMatterControllerDevice.input_help_write') || 'Write', node._('knxUltimateMatterControllerDevice.input_functions.unlock') || 'Unlock', functionCode({ function: 'lock', value: false }));
945
+ } else {
946
+ semanticInputDefinitions.forEach((definition) => {
947
+ const readTarget = structureMember(structure, 'attribute', definition.read);
948
+ const writeVariants = definition.writes || [{
949
+ descriptor: definition.write || definition.writeAttribute,
950
+ targetKind: definition.write ? 'command' : 'attribute',
951
+ value: definition.value,
952
+ omitValue: definition.omitValue
953
+ }];
954
+ writeVariants.forEach((variant) => {
955
+ const targetKind = variant.targetKind || 'command';
956
+ const writeTarget = structureMember(structure, targetKind, variant.descriptor, targetKind === 'attribute');
957
+ if (!writeTarget) return;
958
+ const payload = { function: definition.fn };
959
+ if (!variant.omitValue) payload.value = variant.value;
960
+ appendInputHelpRow($simpleBody, node._('knxUltimateMatterControllerDevice.input_help_write') || 'Write', node._(`knxUltimateMatterControllerDevice.input_functions.${definition.fn}`) || definition.label, functionCode(payload));
961
+ });
962
+ if (readTarget) {
963
+ appendInputHelpRow($simpleBody, node._('knxUltimateMatterControllerDevice.input_help_read') || 'Read', node._(`knxUltimateMatterControllerDevice.input_functions.${definition.fn}`) || definition.label, functionCode({ function: definition.fn }));
964
+ }
965
+ });
966
+ }
967
+ $('#matter-input-help-simple-empty').toggle($simpleBody.children().length === 0);
968
+ $('#matter-input-help-simple-hint').attr(
969
+ 'data-i18n',
970
+ isMatterLight
971
+ ? 'knxUltimateMatterControllerDevice.input_help_light_hint'
972
+ : 'knxUltimateMatterControllerDevice.input_help_simple_hint'
973
+ ).text(node._(
974
+ isMatterLight
975
+ ? 'knxUltimateMatterControllerDevice.input_help_light_hint'
976
+ : 'knxUltimateMatterControllerDevice.input_help_simple_hint'
977
+ ) || '');
978
+
979
+ const $advancedBody = $('#matter-input-help-advanced-body').empty();
980
+ if (structure && !isMatterLight) {
981
+ structure.clusters.forEach((cluster) => {
982
+ (cluster.attributes || []).forEach((attribute) => {
983
+ const prefix = `${cluster.name || 'Cluster'} (${cluster.id}) · ${attribute.name} (${attribute.id})`;
984
+ appendInputHelpRow($advancedBody, node._('knxUltimateMatterControllerDevice.input_help_read') || 'Read', prefix, advancedCode('read', cluster.id, attribute.name));
985
+ if (attribute.writable === true) {
986
+ appendInputHelpRow($advancedBody, node._('knxUltimateMatterControllerDevice.input_help_write') || 'Write', prefix, advancedCode('write', cluster.id, attribute.name));
987
+ }
988
+ });
989
+ (cluster.commands || []).forEach((command) => {
990
+ const prefix = `${cluster.name || 'Cluster'} (${cluster.id}) · ${command.name}`;
991
+ appendInputHelpRow($advancedBody, node._('knxUltimateMatterControllerDevice.input_help_command') || 'Command', prefix, advancedCode('command', cluster.id, command.name));
992
+ });
993
+ });
994
+ }
995
+ $('#matter-input-help-advanced-empty').toggle($advancedBody.children().length === 0);
996
+ $('#matter-input-help-advanced-details').toggle(!isMatterLight);
997
+ };
998
+ const copyInputHelpText = (text) => {
999
+ const copied = () => RED.notify(node._('knxUltimateMatterControllerDevice.input_help_copied') || 'Message copied', 'success');
1000
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
1001
+ navigator.clipboard.writeText(text).then(copied).catch(() => {
1002
+ const $temporary = $('<textarea></textarea>').val(text).appendTo('body').select();
1003
+ try { document.execCommand('copy'); copied(); } finally { $temporary.remove(); }
1004
+ });
1005
+ return;
1006
+ }
1007
+ const $temporary = $('<textarea></textarea>').val(text).appendTo('body').select();
1008
+ try { document.execCommand('copy'); copied(); } finally { $temporary.remove(); }
1009
+ };
1010
+ $inputHelpPanel.off('click.matterInputHelp').on('click.matterInputHelp', '.matter-input-help-copy', function () {
1011
+ copyInputHelpText($(this).data('copy-text') || '');
1012
+ });
807
1013
  $tabs.addClass('hue-vertical-tabs');
808
1014
  $tabs.tabs(); // Tabs gestione KNX
809
1015
  $tabs.find('ul').addClass('ui-tabs-nav');
@@ -1044,8 +1250,9 @@
1044
1250
 
1045
1251
  const ensureVisibleMatterTabSelected = () => {
1046
1252
  const $active = $tabs.find('ul.ui-tabs-nav li.ui-tabs-active');
1047
- if ($active.length && $active.is(':visible')) return;
1048
- const $firstVisible = $tabs.find('ul.ui-tabs-nav li:visible').first();
1253
+ const isTabAvailable = function () { return $(this).css('display') !== 'none'; };
1254
+ if ($active.length && $active.filter(isTabAvailable).length) return;
1255
+ const $firstVisible = $tabs.find('ul.ui-tabs-nav li').filter(isTabAvailable).first();
1049
1256
  if (!$firstVisible.length) return;
1050
1257
  const visibleIndex = $tabs.find('ul.ui-tabs-nav li').index($firstVisible);
1051
1258
  if (visibleIndex >= 0) {
@@ -1107,14 +1314,15 @@
1107
1314
  const isDoorLock = caps.profile === 'doorLock';
1108
1315
  const isMapped = ['mapped', 'windowCovering', 'thermostat', 'fan', 'switch'].includes(caps.profile);
1109
1316
  const isUniversal = caps.profile === 'universal' || isUniversalMode();
1317
+ const knxSelected = hasKnxServerSelected();
1110
1318
  const deviceTypeDisplay = String(caps.deviceTypeDisplay || '').trim();
1111
1319
 
1112
- setMatterTabVisible('tabs-1', !isUniversal && supportsSwitch);
1113
- setMatterTabVisible('tabs-2', !isUniversal && supportsDim);
1114
- setMatterTabVisible('tabs-3', !isUniversal && supportsTemperature);
1115
- setMatterTabVisible('tabs-4', !isUniversal && supportsColor);
1116
- setMatterTabVisible('tabs-mapped', !isUniversal && isMapped);
1117
- setMatterTabVisible('tabs-6', !isUniversal && hasHueDevice);
1320
+ setMatterTabVisible('tabs-1', !isUniversal && knxSelected && supportsSwitch);
1321
+ setMatterTabVisible('tabs-2', !isUniversal && knxSelected && supportsDim);
1322
+ setMatterTabVisible('tabs-3', !isUniversal && knxSelected && supportsTemperature);
1323
+ setMatterTabVisible('tabs-4', !isUniversal && knxSelected && supportsColor);
1324
+ setMatterTabVisible('tabs-mapped', !isUniversal && knxSelected && isMapped);
1325
+ setMatterTabVisible('tabs-6', !isUniversal && knxSelected && hasHueDevice);
1118
1326
  $('#matter-universal-description').toggle(isUniversal);
1119
1327
  $pinSelect.val(isUniversal ? 'yes' : $pinSelect.val()).prop('disabled', isUniversal);
1120
1328
  updateLocateButtonState(false);
@@ -1129,6 +1337,9 @@
1129
1337
  $('#matter-door-lock-pin-required').toggle(isDoorLock && caps.requirePinForRemoteOperation === true);
1130
1338
  $('#matter-mapped-profile').toggle(isMapped);
1131
1339
  if (isMapped) renderMappedProfile(caps.mappedTargets || []);
1340
+ const showInputHelp = !isUniversal && hasHueDevice && $pinSelect.val() === 'yes';
1341
+ $inputHelpPanel.toggle(showInputHelp);
1342
+ if (showInputHelp) renderInputHelp();
1132
1343
  $('#matter-selected-device-type-value').text(deviceTypeDisplay);
1133
1344
  $('#matter-selected-device-type').toggle(hasHueDevice && deviceTypeDisplay !== '');
1134
1345
  try { $tabs.tabs('refresh'); } catch (error) { /* empty */ }
@@ -1186,9 +1397,12 @@
1186
1397
  };
1187
1398
 
1188
1399
  const updateTabsVisibility = () => {
1189
- const knxSelected = hasKnxServerSelected();
1190
1400
  const hueDeviceSelected = getHueDeviceValue() !== '';
1191
- const shouldShowTabs = !isUniversalMode() && knxSelected && hueDeviceSelected;
1401
+ applyMatterCapabilities(currentMatterCapabilities);
1402
+ const hasVisibleTab = $tabs.find('ul.ui-tabs-nav li').filter(function () {
1403
+ return $(this).css('display') !== 'none';
1404
+ }).length > 0;
1405
+ const shouldShowTabs = !isUniversalMode() && hueDeviceSelected && hasVisibleTab;
1192
1406
 
1193
1407
  if (shouldShowTabs) {
1194
1408
  $tabs.show();
@@ -1196,7 +1410,6 @@
1196
1410
  } else {
1197
1411
  $tabs.hide();
1198
1412
  }
1199
- applyMatterCapabilities(currentMatterCapabilities);
1200
1413
 
1201
1414
  if ($pinSectionRow.length) {
1202
1415
  $pinSectionRow.show();
@@ -1250,6 +1463,7 @@
1250
1463
  });
1251
1464
 
1252
1465
  $hueDeviceInput.on('change.knxUltimateMatterControllerDevice input.knxUltimateMatterControllerDevice', updateTabsVisibility);
1466
+ $pinSelect.on('change.knxUltimateMatterControllerDevice', updateTabsVisibility);
1253
1467
 
1254
1468
  if (($deviceNameInput.val() || '').trim() !== '') {
1255
1469
  applyHueDevicesPlaceholder(true);
@@ -2392,6 +2606,46 @@
2392
2606
  <option value="yes" data-i18n="knxUltimateMatterControllerDevice.node_pins_show"></option>
2393
2607
  </select>
2394
2608
  </div>
2609
+ <div id="matter-flow-input-help" style="display:none; width:900px; margin-top:12px;">
2610
+ <h4><i class="fa-solid fa-code"></i> <span data-i18n="knxUltimateMatterControllerDevice.input_help_label"></span></h4>
2611
+ <p>
2612
+ <strong data-i18n="knxUltimateMatterControllerDevice.input_help_endpoint"></strong>:
2613
+ <code id="matter-input-help-endpoint"></code>.
2614
+ <span data-i18n="knxUltimateMatterControllerDevice.input_help_endpoint_hint"></span>
2615
+ </p>
2616
+ <h4 data-i18n="knxUltimateMatterControllerDevice.input_help_simple_title"></h4>
2617
+ <p id="matter-input-help-simple-hint" data-i18n="knxUltimateMatterControllerDevice.input_help_simple_hint"></p>
2618
+ <table class="matter-input-help-table">
2619
+ <thead>
2620
+ <tr>
2621
+ <th data-i18n="knxUltimateMatterControllerDevice.input_help_operation"></th>
2622
+ <th data-i18n="knxUltimateMatterControllerDevice.input_help_function"></th>
2623
+ <th data-i18n="knxUltimateMatterControllerDevice.input_help_message"></th>
2624
+ <th></th>
2625
+ </tr>
2626
+ </thead>
2627
+ <tbody id="matter-input-help-simple-body"></tbody>
2628
+ </table>
2629
+ <p id="matter-input-help-simple-empty" style="display:none;" data-i18n="knxUltimateMatterControllerDevice.input_help_no_simple"></p>
2630
+ <details id="matter-input-help-advanced-details">
2631
+ <summary><strong data-i18n="knxUltimateMatterControllerDevice.input_help_advanced_title"></strong></summary>
2632
+ <p data-i18n="knxUltimateMatterControllerDevice.input_help_advanced_hint"></p>
2633
+ <div class="matter-input-help-advanced">
2634
+ <table class="matter-input-help-table">
2635
+ <thead>
2636
+ <tr>
2637
+ <th data-i18n="knxUltimateMatterControllerDevice.input_help_operation"></th>
2638
+ <th data-i18n="knxUltimateMatterControllerDevice.input_help_target"></th>
2639
+ <th data-i18n="knxUltimateMatterControllerDevice.input_help_message"></th>
2640
+ <th></th>
2641
+ </tr>
2642
+ </thead>
2643
+ <tbody id="matter-input-help-advanced-body"></tbody>
2644
+ </table>
2645
+ <p id="matter-input-help-advanced-empty" style="display:none;" data-i18n="knxUltimateMatterControllerDevice.input_help_no_structure"></p>
2646
+ </div>
2647
+ </details>
2648
+ </div>
2395
2649
  </div>
2396
2650
 
2397
2651
  <br/>
@@ -2414,6 +2668,37 @@ the device picker. Door Lock, Window Covering, Thermostat, Fan and Switch endpoi
2414
2668
  dedicated profiles. Switch press, long-press and multi-press events are emitted on the
2415
2669
  optional flow output; simpler endpoints continue through the generic mapped fallback.
2416
2670
 
2671
+ **Flow input**
2672
+
2673
+ Enable **Node Input/Output PINs** to reveal the **Flow input** section directly below
2674
+ the selector. Lights show copyable examples for supported top-level properties such as
2675
+ `msg.on`, `msg.dimming`, `msg.color_temperature` and `msg.color`. For non-light
2676
+ endpoints the section shows the selected endpoint, all advertised readable/writable
2677
+ attributes and all accepted commands.
2678
+
2679
+ Use `msg.payload = {function:"position", value:35}` for a simple write. Omit `value`
2680
+ to read a supported state, for example `{function:"temperature"}`. Values use human
2681
+ units and read output includes raw Matter details in `msg.matter`. The available
2682
+ functions are filtered by the endpoint and can include `onoff`, `level`, `position`,
2683
+ `tiltposition`, `open`, `close`, `stop`, `setpoint`, `coolingsetpoint`, `currenttemp`,
2684
+ `fanspeed`, sensor readings and `identify`. Door Lock accepts
2685
+ `{function:"lock", value:true|false}`.
2686
+
2687
+ The advanced top-level fields remain supported: `msg.clusterId` with
2688
+ `msg.command`/`msg.args`, or `msg.attribute` with optional `msg.value`. Node ID and
2689
+ Endpoint ID are already selected by this node; `msg.endpointId` is only an override.
2690
+
2691
+ **Device isolation**
2692
+
2693
+ Commands use a separate ordered queue for every commissioned device. An offline,
2694
+ timing-out or removed device cannot delay other Matter devices sharing the same KNX
2695
+ group address. A Controller node that still points to a removed device rejects the
2696
+ command immediately and shows **Device no longer commissioned** in red.
2697
+
2698
+ An unavailable-device error is latched. Further KNX and flow commands are ignored and
2699
+ cannot repaint the red status. The node resumes automatically when its Matter device
2700
+ reports `connected`; opening the node editor also clears the latch for a manual retry.
2701
+
2417
2702
  **Universal Mode**
2418
2703
 
2419
2704
  Select **Universal Mode** in the **Mode** selector to listen to every attribute report,
@@ -2519,7 +2804,7 @@ _Basic Matter effects_
2519
2804
  | Read status at startup | Read the Matter endpoint status at Node-RED startup or full deploy and send it to the KNX bus. |
2520
2805
  | KNX Brightness Status | Updates the KNX brightness status whenever the Matter light switches ON/OFF. It can send 0% while OFF and restore the previous value when ON, or leave the brightness status unchanged. |
2521
2806
  | Update local cached Matter state from KNX bus writes | When enabled, KNX writes immediately update the local Matter-state cache without waiting for the endpoint report. Disable it if the cache should follow only real Matter reports. |
2522
- | Node Input/Output PINs | For mapped endpoints, all Matter selectors belong directly to `msg`. Read an attribute with `msg.clusterId` and `msg.attribute`; add `msg.requestFromRemote = true` to force a device read instead of using the subscribed value. The result is emitted as `msg.payload`. Add `msg.value` to write an attribute, or use `msg.clusterId`, `msg.command` and `msg.args` to invoke a command. Attribute IDs such as `0` are accepted. Door Lock input accepts `msg.payload = true` to lock and `false` to unlock. |
2807
+ | Node Input/Output PINs | Reveals the **Flow input** section below the selector. Lights use the supported top-level state properties shown there (`msg.on`, `msg.dimming`, `msg.color_temperature`, `msg.color`). Mapped endpoints use `msg.payload = {function,value}` or the advanced Matter selectors. Door Lock accepts both `msg.payload = true/false` and `{function:"lock",value:true/false}`. |
2523
2808
  | Switch on behaviour | It sets the behaviour of your lights when switched on. You can choose from differents behaviours.<br/> **Select color: ** the light will be switched on with the color of your choice. To change color, just CLICK on the color selector (under the _Select color_ control).<br/>**Select temperature and brightness: ** the light will be switched on with the temperature (Kelvin) and brightness (0-100) of your choice.<br/>**None:** the light will retain its last status. In case you've enable the night lighting, after the night time ends, the lamp will resume the color/temperature/brightness state set at day time. |
2524
2809
  | Night Lighting | It allows to set a particular light color/brightness at nighttime. The options are the same as the daytime. You could select either a temperature/brightness or color. A cozy temperature of 2700 Kelvin, with a brightness of 10% or 20%, is a good choice for bathroom's night light.|
2525
2810
  | Day/Night | Select the group address used to set the day/night behaviour. The group address value is _true_ if daytime, _false_ if nighttime. |
@@ -10,8 +10,34 @@ const hueColorConverter = require('./utils/colorManipulators/hueColorConverter')
10
10
  const { createLightEngine } = require('./utils/lightEngines')
11
11
  const { hueStateToCanonical, matterEventToHuePatch } = require('./utils/lightEngines/matterHueShim')
12
12
  const { setupMatterControllerProfile } = require('./utils/matterControllerProfiles')
13
+ const { setupMatterControllerCommandGate } = require('./utils/matterControllerCommandGate')
14
+
15
+ let matterCommandGateEndpointRegistered = false
13
16
 
14
17
  module.exports = function (RED) {
18
+ if (!matterCommandGateEndpointRegistered) {
19
+ RED.httpAdmin.post('/KNXUltimateMatterControllerEnable', RED.auth.needsPermission('matter-config.write'), (req, res) => {
20
+ try {
21
+ const nodeId = String(req.body?.nodeId || '').trim()
22
+ if (nodeId === '') {
23
+ res.status(400).json({ error: 'Missing Matter Controller node id' })
24
+ return
25
+ }
26
+ const targetNode = RED.nodes.getNode(nodeId)
27
+ if (!targetNode || typeof targetNode.clearMatterCommandBlock !== 'function') {
28
+ // New or not-yet-deployed editor nodes have no runtime counterpart.
29
+ res.json({ status: 'not-deployed', cleared: false })
30
+ return
31
+ }
32
+ const cleared = targetNode.clearMatterCommandBlock('editor')
33
+ res.json({ status: 'ok', cleared })
34
+ } catch (error) {
35
+ res.status(500).json({ error: error.message || 'Unable to enable Matter commands' })
36
+ }
37
+ })
38
+ matterCommandGateEndpointRegistered = true
39
+ }
40
+
15
41
  function knxUltimateMatterControllerDevice (config) {
16
42
  RED.nodes.createNode(this, config)
17
43
  const node = this
@@ -20,6 +46,7 @@ module.exports = function (RED) {
20
46
  node.matterNodeId = config.matterNodeId !== undefined ? String(config.matterNodeId) : ''
21
47
  node.matterEndpointId = config.matterEndpointId !== undefined ? Number(config.matterEndpointId) : 1
22
48
  node.matterDeviceName = config.matterDeviceName !== undefined ? String(config.matterDeviceName) : ''
49
+ setupMatterControllerCommandGate(node)
23
50
 
24
51
  let matterCapabilities = {}
25
52
  try { matterCapabilities = JSON.parse(config.matterDeviceCapabilities || '{}') } catch (error) { /* empty */ }
@@ -45,6 +72,7 @@ module.exports = function (RED) {
45
72
  hueManager: {
46
73
  writeHueQueueAdd: (_lightID, _state, _operation) => {
47
74
  try {
75
+ if (node.matterCommandBlocked === true) return
48
76
  if (!node.lightEngine) return
49
77
  const patch = hueStateToCanonical(_state)
50
78
  if (!patch) return
@@ -217,6 +245,7 @@ module.exports = function (RED) {
217
245
  fill, shape, text, payload
218
246
  }) => {
219
247
  try {
248
+ if (node.matterCommandBlocked === true) return
220
249
  if (node.currentHUEDevice?.on?.on === true) { fill = 'blue'; shape = 'dot' } else { fill = 'blue'; shape = 'ring' };
221
250
  if (payload === undefined) payload = ''
222
251
  const dDate = new Date()
@@ -228,6 +257,7 @@ module.exports = function (RED) {
228
257
  // Compatibility callback used to update the Matter node status.
229
258
  node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
230
259
  try {
260
+ if (node.matterCommandBlocked === true) return
231
261
  if (node.currentHUEDevice?.on?.on === true) { fill = 'blue'; shape = 'dot' } else { fill = 'blue'; shape = 'ring' };
232
262
  if (payload === undefined) payload = ''
233
263
  const dDate = new Date()
@@ -238,6 +268,7 @@ module.exports = function (RED) {
238
268
  }
239
269
 
240
270
  node.writeHueState = function writeHueState (_state) {
271
+ if (node.matterCommandBlocked === true) return
241
272
  const defaultOperation = node.isGrouped_light === false ? 'setLight' : 'setGroupedLight'
242
273
  const isGroupedLightOff = node.isGrouped_light === true && node.currentHUEDevice?.on?.on === false
243
274
  const stateKeys = _state && typeof _state === 'object' ? Object.keys(_state) : []
@@ -330,6 +361,7 @@ module.exports = function (RED) {
330
361
  // Compatibility callback invoked by the Matter controller adapter.
331
362
  node.handleSend = (msg) => {
332
363
  if (!msg || !msg.knx || !isConfiguredKNXGA(msg.knx.destination)) return
364
+ if (node.matterCommandBlocked === true) return
333
365
  if (node.currentHUEDevice === undefined && node.serverHue.linkStatus === 'connected') {
334
366
  node.setNodeStatusHue({
335
367
  fill: 'yellow',
@@ -1671,6 +1703,11 @@ module.exports = function (RED) {
1671
1703
  }
1672
1704
 
1673
1705
  node.on('input', (msg, send, done) => {
1706
+ if (node.matterCommandBlocked === true) {
1707
+ const error = new Error(node.matterCommandBlockReason || 'Matter device unavailable')
1708
+ if (done) done(error); else node.error(error, msg)
1709
+ return
1710
+ }
1674
1711
  try {
1675
1712
  const state = RED.util.cloneMessage(msg)
1676
1713
  node.writeHueState(state)
@@ -45,7 +45,7 @@ Diese Optionen werden nur angezeigt, wenn sie zum gewählten Typ passen. Dimmbar
45
45
 
46
46
  ## Node-PINs
47
47
 
48
- Wenn du die Input/Output-PINs des Nodes aktivierst:
48
+ Der PIN-Selektor befindet sich außerhalb der Editor-Tabs. Nach dem Aktivieren erscheint direkt darunter ein kontextbezogener Bereich **Flow-Eingang/-Ausgang** mit kopierbaren Beispielen für den ausgewählten Gerätetyp:
49
49
 
50
50
  - **Input**: aktualisiere den Matter-Zustand aus dem Flow, ohne den KNX-Bus: `msg.payload = { function: "onoff", value: true }` (`function` ist eine von `onoff`, `level`, `rgb`, `colortemp`, `position`, `temperature`, `humidity`, `illuminance`, `occupancy`, `contact`, `currenttemp`, `setpoint`, `fanspeed`, `smoke`, `co`, `leak`, `co2`, `rvcstate`, `rvcmode`). Nützlich, um im Flow berechnete Werte (z.B. einen virtuellen Sensor) für Alexa & Co. bereitzustellen.
51
51
  - **Output**: Jeder von einem Matter-Controller empfangene Befehl wird an den Flow weitergeleitet: `msg.topic` = Gerätename, `msg.payload` = Wert, `msg.matter` = der rohe Befehl. Ein Gerät ohne Befehls-GAs wird zum **Nur-Flow-Gerät**.
@@ -36,6 +36,20 @@
36
36
  "mappings": "KNX-Zuordnungen",
37
37
  "advanced": "Erweiterte Optionen"
38
38
  },
39
+ "flow_help": {
40
+ "title": "Flow-Eingang/-Ausgang",
41
+ "intro": "Diese Beispiele entsprechen dem ausgewählten Gerätetyp und sind verfügbar, solange die Ein-/Ausgangs-PINs aktiviert sind.",
42
+ "input_title": "Flow → Matter (Node-Eingang)",
43
+ "input_hint": "Sende eine dieser Nachrichten, um den bereitgestellten Matter-Zustand ohne KNX-Schreibvorgang zu aktualisieren.",
44
+ "output_title": "Matter → Flow (Node-Ausgang)",
45
+ "output_hint": "Befehle eines Matter-Controllers werden mit dem Wert in msg.payload und den Rohdetails in msg.matter ausgegeben.",
46
+ "function": "Funktion",
47
+ "message": "Beispielnachricht",
48
+ "copy": "Kopieren",
49
+ "copied": "Nachricht kopiert",
50
+ "no_input": "Für diesen Gerätetyp gibt es keine Beispiele für Flow-Zustandsupdates.",
51
+ "no_output": "Dieser Gerätetyp gibt keine Controller-Befehle aus; sein Ausgang bleibt inaktiv."
52
+ },
39
53
  "functions": {
40
54
  "fn_onoff_cmd": "Ein/Aus Befehls-GA",
41
55
  "fn_onoff_status": "Ein/Aus Status-GA",
@@ -19,7 +19,15 @@ Er ersetzt die unveröffentlichten getrennten Matter-Controller-Nodes und behäl
19
19
  | Sensoren | Sensor-Endpunkte zeigen ihre Mess-/Status-GA nur bei Unterstützung: Temperatur, Feuchte, Helligkeit, Präsenz, Kontakt und Batterie. |
20
20
  | Read at startup | Veröffentlicht den gecachten Matter-Wert beim Deploy/Start oder wenn sich das Gerät erneut verbindet. |
21
21
  | Update local state from KNX write | Aktualisiert den lokalen Matter/KNX-Cache, wenn ein Telegramm auf eine konfigurierte KNX-GA geschrieben wird. |
22
- | Node Input/Output PINs | Zeigt Node-RED-Eingangs-/Ausgangspins. Der Eingang akzeptiert boolesche Payloads sowie Matter-ähnliche `msg.payload` oder `msg.on.on`; der Ausgang sendet Statusupdates. Die Auswahl bleibt beim erneuten Öffnen des Editors erhalten. |
22
+ | Node Input/Output PINs | Zeigt Node-RED-Eingangs-/Ausgangspins und direkt darunter den Bereich **Flow-Eingang**. Für Leuchten werden die unterstützten Lichtzustands-Nachrichten auf oberster Ebene angezeigt; andere Endpunkte zeigen das einfache `{function,value}`-Format und die erweiterten Matter-Felder. |
23
+
24
+ ## Flow-Eingang
25
+
26
+ Aktiviere **Node Input/Output PINs**, um den Bereich **Flow-Eingang** direkt unter dem Auswahlfeld einzublenden. Für Leuchten zeigt er kopierbare Beispiele der unterstützten Eigenschaften auf oberster Ebene, etwa `msg.on`, `msg.dimming`, `msg.color_temperature` und `msg.color`. Für andere Endpunkte wird er aus der angekündigten Struktur aufgebaut und zeigt Endpoint ID sowie alle lesbaren/schreibbaren Attribute und akzeptierten Befehle. Er bleibt auch ohne KNX-Gateway verfügbar.
27
+
28
+ Einfache Schreibvorgänge verwenden `msg.payload = {function:"position",value:35}`. Ohne `value` wird ein unterstützter Zustand gelesen, zum Beispiel `{function:"temperature"}`; die Ausgabe verwendet verständliche Einheiten und enthält Rohdetails in `msg.matter`. Funktionen wie `onoff`, `level`, `position`, `open`, `close`, `stop`, Sollwerte, Lüfter und Sensoren erscheinen nur bei Unterstützung. Türschlösser akzeptieren `{function:"lock",value:true|false}`.
29
+
30
+ Die bisherigen erweiterten Eingaben bleiben gültig: `msg.clusterId` mit `msg.command`/`msg.args` oder `msg.attribute` und optional `msg.value`. Node ID und Endpoint ID sind bereits im Knoten ausgewählt.
23
31
 
24
32
  ## Universeller Modus
25
33
 
@@ -34,4 +42,8 @@ Beispiele: `{nodeId:"100",endpointId:1,clusterId:6,command:"on",args:{}}` schalt
34
42
  ## Verhalten
35
43
 
36
44
  Der Knoten pflegt einen lokalen Cache aus Matter-Updates und KNX-Schreibtelegrammen, beantwortet KNX-Leseanfragen aus diesem Cache und kann Werte beim Start lesen/ausgeben. Er hört nur auf konfigurierte Gruppenadressen, daher wird fremder KNX-Verkehr ignoriert.
45
+
46
+ Befehle laufen pro gekoppeltem Gerät in einer eigenen geordneten Warteschlange. Ein offline befindliches, zeitüberschreitendes oder entferntes Gerät kann daher andere Matter-Geräte mit derselben KNX-Gruppenadresse nicht verzögern. Verweist ein Node weiterhin auf ein entferntes Gerät, wird der Befehl sofort abgewiesen und **Device no longer commissioned** rot angezeigt; wähle ein gültiges Matter-Gerät oder entferne den verwaisten Controller-Node.
47
+
48
+ Ein Fehler wegen eines nicht verfügbaren Geräts bleibt verriegelt: weitere KNX- und Flow-Befehle werden ignoriert und können den roten Status nicht überschreiben. Der Node wird automatisch wieder aktiv, sobald dieses Matter-Gerät `connected` meldet; auch das Öffnen des Node-Editors löst die Verriegelung für einen manuellen Neuversuch.
37
49
  </script>
@@ -11,6 +11,29 @@
11
11
  "node-input-hueLight": "Matter-Gerät",
12
12
  "paletteLabel": "Control Matter from KNX",
13
13
  "no_devices": "Keine Matter-Geräte gefunden. Matter-Controller prüfen und aktualisieren.",
14
+ "input_help_label": "Flow-Eingang",
15
+ "input_help_pin_hint": "Aktiviere die Ein-/Ausgangs-PINs des Knotens, bevor du diese Nachrichten sendest.",
16
+ "input_help_endpoint": "Ausgewählter Endpunkt",
17
+ "input_help_endpoint_hint": "Der Knoten kennt Node ID und Endpoint ID bereits; einfache Nachrichten benötigen sie nicht.",
18
+ "input_help_simple_title": "Einfache Nachrichten",
19
+ "input_help_simple_hint": "Diese Funktionen verwenden verständliche Einheiten und werden in den Matter-Cluster, Befehl oder das Attribut des ausgewählten Endpunkts übersetzt.",
20
+ "input_help_light_hint": "Licht-Endpunkte akzeptieren Lichtzustands-Eigenschaften direkt in msg (nicht in msg.payload). Es werden nur die von der ausgewählten Leuchte unterstützten Steuerungen angezeigt.",
21
+ "input_help_advanced_title": "Erweiterte Matter-Details",
22
+ "input_help_advanced_hint": "Es werden nur angekündigte Attribute und Befehle aufgelistet. Schreibbeispiele verwenden 0 als Platzhalter; Befehlsargumente müssen eventuell an den Matter-Datentyp angepasst werden.",
23
+ "input_help_operation": "Operation",
24
+ "input_help_function": "Funktion",
25
+ "input_help_target": "Matter-Ziel",
26
+ "input_help_message": "Nachricht",
27
+ "input_help_read": "Lesen",
28
+ "input_help_write": "Schreiben",
29
+ "input_help_command": "Befehl",
30
+ "input_help_copy": "Kopieren",
31
+ "input_help_copied": "Nachricht kopiert",
32
+ "input_help_no_simple": "Dieser Endpunkt besitzt keine bekannte einfache Eingangsfunktion.",
33
+ "input_help_no_structure": "Die Matter-Struktur ist nicht verfügbar. Aktualisiere die Geräteliste, während der Endpunkt online ist.",
34
+ "input_functions": {
35
+ "onoff": "Ein / Aus", "on": "Ein", "off": "Aus", "level": "Stufe", "brightness": "Helligkeit", "color_temperature": "Farbtemperatur", "xy_color": "XY-Farbe", "position": "Rollladenposition", "tiltposition": "Lamellenposition", "open": "Öffnen", "close": "Schließen", "stop": "Stopp", "setpoint": "Heizsollwert", "coolingsetpoint": "Kühlsollwert", "currenttemp": "Aktuelle Temperatur", "fanspeed": "Lüftergeschwindigkeit", "temperature": "Temperatur", "humidity": "Luftfeuchtigkeit", "illuminance": "Beleuchtungsstärke", "occupancy": "Belegung", "contact": "Kontakt", "battery": "Batterie", "activepower": "Wirkleistung", "importedenergy": "Bezogene Energie", "identify": "Identifizieren", "lock": "Verriegeln", "unlock": "Entriegeln"
36
+ },
14
37
  "tabs": {
15
38
  "switch": "Schalten",
16
39
  "dim": "Dimmen",