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.
- package/CHANGELOG.md +12 -2
- package/examples/Matter Controller - Semantic Flow Input.json +248 -0
- package/nodes/knxUltimateMatterBridge.html +144 -11
- package/nodes/knxUltimateMatterControllerDevice.html +298 -13
- package/nodes/knxUltimateMatterControllerDevice.js +37 -0
- package/nodes/locales/de/knxUltimateMatterBridge.html +1 -1
- package/nodes/locales/de/knxUltimateMatterBridge.json +14 -0
- package/nodes/locales/de/knxUltimateMatterControllerDevice.html +13 -1
- package/nodes/locales/de/knxUltimateMatterControllerDevice.json +23 -0
- package/nodes/locales/de/matter-config.html +11 -1
- package/nodes/locales/de/matter-config.json +12 -0
- package/nodes/locales/en/knxUltimateMatterBridge.html +1 -1
- package/nodes/locales/en/knxUltimateMatterBridge.json +14 -0
- package/nodes/locales/en/knxUltimateMatterControllerDevice.html +13 -1
- package/nodes/locales/en/knxUltimateMatterControllerDevice.json +23 -0
- package/nodes/locales/en/matter-config.html +11 -1
- package/nodes/locales/en/matter-config.json +12 -0
- package/nodes/locales/es/knxUltimateMatterBridge.html +1 -1
- package/nodes/locales/es/knxUltimateMatterBridge.json +14 -0
- package/nodes/locales/es/knxUltimateMatterControllerDevice.html +13 -1
- package/nodes/locales/es/knxUltimateMatterControllerDevice.json +23 -0
- package/nodes/locales/es/matter-config.html +11 -1
- package/nodes/locales/es/matter-config.json +12 -0
- package/nodes/locales/fr/knxUltimateMatterBridge.html +1 -1
- package/nodes/locales/fr/knxUltimateMatterBridge.json +14 -0
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +13 -1
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +31 -0
- package/nodes/locales/fr/matter-config.html +11 -1
- package/nodes/locales/fr/matter-config.json +12 -0
- package/nodes/locales/it/knxUltimateMatterBridge.html +1 -1
- package/nodes/locales/it/knxUltimateMatterBridge.json +14 -0
- package/nodes/locales/it/knxUltimateMatterControllerDevice.html +13 -1
- package/nodes/locales/it/knxUltimateMatterControllerDevice.json +23 -0
- package/nodes/locales/it/matter-config.html +11 -1
- package/nodes/locales/it/matter-config.json +12 -0
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +1 -1
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +14 -0
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +13 -1
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +31 -0
- package/nodes/locales/zh-CN/matter-config.html +11 -1
- package/nodes/locales/zh-CN/matter-config.json +12 -0
- package/nodes/matter-config.html +215 -23
- package/nodes/matter-config.js +48 -2
- package/nodes/utils/matterControllerCommandGate.js +47 -0
- package/nodes/utils/matterControllerProfiles/doorLock.js +19 -4
- package/nodes/utils/matterControllerProfiles/fan.js +0 -1
- package/nodes/utils/matterControllerProfiles/mappedEndpoint.js +32 -15
- package/nodes/utils/matterControllerProfiles/switch.js +0 -1
- package/nodes/utils/matterControllerProfiles/thermostat.js +0 -1
- package/nodes/utils/matterControllerProfiles/windowCovering.js +0 -1
- package/nodes/utils/matterControllerSemanticInput.js +212 -0
- package/nodes/utils/matterEngine.mjs +118 -16
- package/package.json +1 -1
- package/resources/matterQrScanner.js +98 -0
- package/resources/zxing-browser-0.2.0.min.js +1 -0
- 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
|
-
|
|
1048
|
-
|
|
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
|
-
|
|
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 |
|
|
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
|
-
|
|
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
|
|
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",
|