node-red-contrib-knx-ultimate 6.0.11 → 6.1.0
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 +11 -3
- package/nodes/knxUltimateHueLight.html +1 -5
- package/nodes/knxUltimateMatterControllerDevice.html +584 -47
- package/nodes/knxUltimateMatterControllerDevice.js +37 -0
- package/nodes/locales/de/knxUltimateMatterBridge.html +0 -2
- package/nodes/locales/de/knxUltimateMatterControllerDevice.html +23 -3
- package/nodes/locales/de/knxUltimateMatterControllerDevice.json +39 -1
- package/nodes/locales/de/matter-config.html +11 -1
- package/nodes/locales/de/matter-config.json +12 -0
- package/nodes/locales/en/knxUltimateMatterBridge.html +0 -2
- package/nodes/locales/en/knxUltimateMatterControllerDevice.html +23 -3
- package/nodes/locales/en/knxUltimateMatterControllerDevice.json +39 -1
- package/nodes/locales/en/matter-config.html +11 -1
- package/nodes/locales/en/matter-config.json +12 -0
- package/nodes/locales/es/knxUltimateMatterBridge.html +0 -2
- package/nodes/locales/es/knxUltimateMatterControllerDevice.html +23 -3
- package/nodes/locales/es/knxUltimateMatterControllerDevice.json +39 -1
- package/nodes/locales/es/matter-config.html +11 -1
- package/nodes/locales/es/matter-config.json +12 -0
- package/nodes/locales/fr/knxUltimateMatterBridge.html +0 -2
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +23 -3
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +47 -1
- package/nodes/locales/fr/matter-config.html +11 -1
- package/nodes/locales/fr/matter-config.json +12 -0
- package/nodes/locales/it/knxUltimateMatterBridge.html +0 -2
- package/nodes/locales/it/knxUltimateMatterControllerDevice.html +23 -3
- package/nodes/locales/it/knxUltimateMatterControllerDevice.json +39 -1
- 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 +0 -2
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +23 -3
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +47 -1
- 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 +53 -6
- 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/index.js +3 -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/universal.js +462 -0
- package/nodes/utils/matterControllerProfiles/windowCovering.js +0 -1
- package/nodes/utils/matterControllerSemanticInput.js +212 -0
- package/nodes/utils/matterEngine.mjs +120 -17
- 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
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
//buttonState: {value: true},
|
|
15
15
|
server: { type: "knxUltimate-config", required: false },
|
|
16
16
|
serverMatter: { type: "matter-config", required: true },
|
|
17
|
+
matterControllerMode: { value: "single" },
|
|
18
|
+
universalService: { value: "batteryMonitor" },
|
|
19
|
+
universalBatteryThreshold: { value: 20, validate: RED.validators.number() },
|
|
20
|
+
universalLowBatteryGA: { value: "" },
|
|
21
|
+
universalLowBatteryGAName: { value: "" },
|
|
22
|
+
universalLowBatteryTextGA: { value: "" },
|
|
23
|
+
universalLowBatteryTextGAName: { value: "" },
|
|
24
|
+
universalBatteryOnlyLow: { value: true },
|
|
25
|
+
universalEmitInitialState: { value: true },
|
|
26
|
+
universalIncludeRawEvent: { value: true },
|
|
17
27
|
matterNodeId: { value: "" },
|
|
18
28
|
matterEndpointId: { value: 1 },
|
|
19
29
|
matterDeviceName: { value: "" },
|
|
@@ -142,6 +152,14 @@
|
|
|
142
152
|
|
|
143
153
|
|
|
144
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
|
+
}
|
|
145
163
|
$("#node-input-updateLocalStateFromKNXWrite").prop("checked", node.updateLocalStateFromKNXWrite === true || node.updateLocalStateFromKNXWrite === "true"); // Starting from v 4.1.31
|
|
146
164
|
const ensureConfigSelection = (selector) => {
|
|
147
165
|
if ($(selector).val() !== "_ADD_") return;
|
|
@@ -211,6 +229,31 @@
|
|
|
211
229
|
border-top: 1px solid #ccc;
|
|
212
230
|
margin: 8px 0;
|
|
213
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
|
+
}
|
|
214
257
|
</style>`;
|
|
215
258
|
$('head').append(style);
|
|
216
259
|
}
|
|
@@ -221,6 +264,8 @@
|
|
|
221
264
|
const KNX_EMPTY_VALUES = new Set(['', 'none', '_ADD_', '__NONE__']);
|
|
222
265
|
// Historical variable names are retained for saved-flow compatibility; they point to Matter widgets.
|
|
223
266
|
const $hueServerInput = $("#node-input-serverMatter");
|
|
267
|
+
const $matterControllerModeInput = $("#node-input-matterControllerMode");
|
|
268
|
+
const $universalServiceInput = $("#node-input-universalService");
|
|
224
269
|
const $hueDeviceInput = $("#node-input-matterNodeId");
|
|
225
270
|
const $deviceNameInput = $("#node-input-matterDeviceName");
|
|
226
271
|
const $matterEndpointInput = $("#node-input-matterEndpointId");
|
|
@@ -229,6 +274,10 @@
|
|
|
229
274
|
const $refreshDevicesButton = $(".hue-refresh-devices");
|
|
230
275
|
const $locateDeviceButton = $(".hue-locate-device");
|
|
231
276
|
const $hueDevicesLoading = $(".hue-devices-loading");
|
|
277
|
+
const $inputHelpPanel = $("#tabs-input");
|
|
278
|
+
const UNIVERSAL_NODE_ID = '__UNIVERSAL__';
|
|
279
|
+
const isUniversalMode = () => $matterControllerModeInput.val() === 'universal';
|
|
280
|
+
$matterControllerModeInput.val(node.matterControllerMode || 'single');
|
|
232
281
|
let cachedHueDevices = Array.isArray(node._cachedHueLightDevices) ? node._cachedHueLightDevices : [];
|
|
233
282
|
node._cachedHueLightDevices = cachedHueDevices;
|
|
234
283
|
const defaultHueDevicePlaceholder = $deviceNameInput.attr('placeholder') || '';
|
|
@@ -289,7 +338,10 @@
|
|
|
289
338
|
const title = locateSessionActive
|
|
290
339
|
? (node._('knxUltimateMatterControllerDevice.locate_stop_title') || 'Stop Matter locate')
|
|
291
340
|
: (node._('knxUltimateMatterControllerDevice.locate_start_title') || 'Locate selected Matter device');
|
|
292
|
-
$locateDeviceButton.attr('title',
|
|
341
|
+
$locateDeviceButton.attr('title', isUniversalMode()
|
|
342
|
+
? (node._('knxUltimateMatterControllerDevice.universal_mode_locate_disabled') || 'Locate is unavailable in Universal Mode.')
|
|
343
|
+
: title);
|
|
344
|
+
$locateDeviceButton.prop('disabled', isUniversalMode());
|
|
293
345
|
};
|
|
294
346
|
|
|
295
347
|
const removeNodeRemovalListener = () => {
|
|
@@ -340,6 +392,10 @@
|
|
|
340
392
|
};
|
|
341
393
|
|
|
342
394
|
const performLocateRequest = ({ silent = false, allowStoredContext = false, action } = {}) => {
|
|
395
|
+
if (isUniversalMode()) {
|
|
396
|
+
if (!silent) RED.notify(node._('knxUltimateMatterControllerDevice.universal_mode_locate_disabled') || 'Locate is unavailable in Universal Mode.', 'warning');
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
343
399
|
const context = buildLocateContext({ allowStored: allowStoredContext });
|
|
344
400
|
if (!context) {
|
|
345
401
|
if (!silent) {
|
|
@@ -419,7 +475,7 @@
|
|
|
419
475
|
}).always(() => {
|
|
420
476
|
locatePendingRequest = null;
|
|
421
477
|
if ($locateDeviceButton.length) {
|
|
422
|
-
$locateDeviceButton.prop('disabled',
|
|
478
|
+
$locateDeviceButton.prop('disabled', isUniversalMode());
|
|
423
479
|
}
|
|
424
480
|
});
|
|
425
481
|
return request;
|
|
@@ -527,6 +583,19 @@
|
|
|
527
583
|
return !!cluster && (cluster.commands || []).some((cmd) => wanted.has(normalizeMatterToken(cmd.name)));
|
|
528
584
|
};
|
|
529
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
|
+
};
|
|
530
599
|
const deviceTypes = (ep.deviceTypes || []).map(normalizeMatterToken);
|
|
531
600
|
const primaryDeviceType = (ep.deviceTypes || []).find((value) => !/bridgednode|aggregator|controlbridge/i.test(String(value))) || ep.name;
|
|
532
601
|
const deviceTypeDisplay = matterPrettyType(primaryDeviceType);
|
|
@@ -546,6 +615,7 @@
|
|
|
546
615
|
color: false,
|
|
547
616
|
lockDoor: hasMatterCommand(doorLockCluster, ['lockDoor']),
|
|
548
617
|
unlockDoor: hasMatterCommand(doorLockCluster, ['unlockDoor']),
|
|
618
|
+
inputStructure,
|
|
549
619
|
requirePinForRemoteOperation: (() => {
|
|
550
620
|
const attribute = (doorLockCluster.attributes || []).find((item) => normalizeMatterToken(item.name) === 'requirepinforremoteoperation');
|
|
551
621
|
return attribute ? attribute.value === true : false;
|
|
@@ -589,47 +659,79 @@
|
|
|
589
659
|
addAttribute(47, 'batPercentRemaining', 'battery', '5.001', false);
|
|
590
660
|
addAttribute(144, 'activePower', 'active_power', '14.056', false);
|
|
591
661
|
addAttribute(145, 'cumulativeEnergyImported', 'imported_energy', '13.013', false);
|
|
592
|
-
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 };
|
|
593
663
|
}
|
|
594
664
|
const hasColorTemperatureType = /colortemperature|extendedcolor/.test(typeText);
|
|
595
665
|
const hasColorType = /extendedcolor/.test(typeText);
|
|
666
|
+
const isTunableWhiteOnly = hasColorTemperatureType && !hasColorType;
|
|
596
667
|
const hasLevelType = /dimmable|colortemperature|extendedcolor/.test(typeText);
|
|
668
|
+
const colorFeatureMapAttribute = (colorCluster?.attributes || []).find((attribute) => normalizeMatterToken(attribute.name) === 'featuremap');
|
|
669
|
+
const colorFeatureMap = Number(colorFeatureMapAttribute?.value);
|
|
670
|
+
const hasColorFeatureMap = Number.isFinite(colorFeatureMap);
|
|
597
671
|
const hasColorTemperature = !!colorCluster && (
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
672
|
+
hasColorFeatureMap
|
|
673
|
+
? (colorFeatureMap & 0x10) !== 0
|
|
674
|
+
: (
|
|
675
|
+
hasColorTemperatureType ||
|
|
676
|
+
hasMatterAttribute(colorCluster, ['colorTemperatureMireds', 'colorTempPhysicalMinMireds', 'colorTempPhysicalMaxMireds']) ||
|
|
677
|
+
hasMatterCommand(colorCluster, ['moveToColorTemperature'])
|
|
678
|
+
)
|
|
601
679
|
);
|
|
602
680
|
const hasColor = !!colorCluster && (
|
|
603
|
-
|
|
604
|
-
|
|
681
|
+
!isTunableWhiteOnly && (
|
|
682
|
+
hasColorFeatureMap
|
|
683
|
+
? (colorFeatureMap & 0x09) !== 0
|
|
684
|
+
: (
|
|
685
|
+
hasColorType ||
|
|
686
|
+
hasMatterCommand(colorCluster, ['moveToColor', 'moveToHue', 'moveToSaturation', 'moveToHueAndSaturation'])
|
|
687
|
+
)
|
|
688
|
+
)
|
|
605
689
|
);
|
|
606
690
|
return {
|
|
607
691
|
known: true,
|
|
608
692
|
deviceTypeDisplay,
|
|
609
|
-
|
|
693
|
+
matterLightType: isTunableWhiteOnly ? 'colorTemperature' : (hasColorType ? 'extendedColor' : 'other'),
|
|
694
|
+
colorDetectionVersion: 3,
|
|
610
695
|
onOff: !!onOffCluster || /onoff|dimmable|colortemperature|extendedcolor/.test(typeText),
|
|
611
696
|
level: !!levelCluster || hasLevelType,
|
|
612
697
|
colorTemperature: hasColorTemperature,
|
|
613
698
|
color: hasColor
|
|
614
699
|
};
|
|
615
700
|
};
|
|
701
|
+
const normalizeMatterCapabilities = (capabilities) => {
|
|
702
|
+
if (!capabilities || typeof capabilities !== 'object') return capabilities;
|
|
703
|
+
const normalized = Object.assign({}, capabilities);
|
|
704
|
+
const displayType = normalizeMatterToken(normalized.deviceTypeDisplay);
|
|
705
|
+
const tunableWhiteOnly = normalized.matterLightType === 'colorTemperature' ||
|
|
706
|
+
displayType === 'tunablewhite' ||
|
|
707
|
+
displayType === 'colortemperaturelight';
|
|
708
|
+
if (tunableWhiteOnly) {
|
|
709
|
+
normalized.colorTemperature = true;
|
|
710
|
+
normalized.color = false;
|
|
711
|
+
normalized.colorDetectionVersion = 3;
|
|
712
|
+
} else if (normalized.known === true && normalized.color === true &&
|
|
713
|
+
normalized.colorDetectionVersion !== 2 && normalized.colorDetectionVersion !== 3) {
|
|
714
|
+
normalized.color = false;
|
|
715
|
+
}
|
|
716
|
+
return normalized;
|
|
717
|
+
};
|
|
616
718
|
const parseMatterCapabilities = (raw) => {
|
|
617
719
|
if (raw && typeof raw === 'object') {
|
|
618
|
-
|
|
619
|
-
return raw;
|
|
720
|
+
return normalizeMatterCapabilities(raw);
|
|
620
721
|
}
|
|
621
722
|
if (typeof raw !== 'string' || raw.trim() === '') return null;
|
|
622
723
|
try {
|
|
623
|
-
|
|
624
|
-
if (parsed && parsed.known === true && parsed.color === true && parsed.colorDetectionVersion !== 2) {
|
|
625
|
-
parsed.color = false;
|
|
626
|
-
}
|
|
627
|
-
return parsed;
|
|
724
|
+
return normalizeMatterCapabilities(JSON.parse(raw));
|
|
628
725
|
} catch (error) {
|
|
629
726
|
return null;
|
|
630
727
|
}
|
|
631
728
|
};
|
|
632
729
|
const serializeMatterCapabilities = (capabilities) => JSON.stringify(capabilities || {});
|
|
730
|
+
const universalMatterCapabilities = () => ({
|
|
731
|
+
known: true,
|
|
732
|
+
profile: 'universal',
|
|
733
|
+
deviceTypeDisplay: node._('knxUltimateMatterControllerDevice.universal_mode') || 'Universal Mode'
|
|
734
|
+
});
|
|
633
735
|
const getJSONPromise = (url) => new Promise((resolve, reject) => { $.getJSON(url, resolve).fail(reject); });
|
|
634
736
|
|
|
635
737
|
// Filters Matter items shaped as { value, matterNodeId, matterEndpointId }.
|
|
@@ -742,13 +844,158 @@
|
|
|
742
844
|
|
|
743
845
|
|
|
744
846
|
const $tabs = $("#tabs");
|
|
847
|
+
const $universalTabs = $("#matter-universal-tabs");
|
|
745
848
|
const $pinSectionRow = $("#node-input-enableNodePINS").closest('.form-row');
|
|
746
849
|
const $pinSelect = $("#node-input-enableNodePINS");
|
|
747
850
|
let currentMatterCapabilities = parseMatterCapabilities($matterCapabilitiesInput.val() || node.matterDeviceCapabilities);
|
|
851
|
+
if (currentMatterCapabilities) {
|
|
852
|
+
$matterCapabilitiesInput.val(serializeMatterCapabilities(currentMatterCapabilities));
|
|
853
|
+
node.matterDeviceCapabilities = $matterCapabilitiesInput.val();
|
|
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
|
+
$('#matter-input-help-endpoint').text(String(endpointId));
|
|
925
|
+
const $simpleBody = $('#matter-input-help-simple-body').empty();
|
|
926
|
+
if (caps.profile === 'doorLock') {
|
|
927
|
+
if (caps.lockDoor !== false) appendInputHelpRow($simpleBody, node._('knxUltimateMatterControllerDevice.input_help_write') || 'Write', node._('knxUltimateMatterControllerDevice.input_functions.lock') || 'Lock', functionCode({ function: 'lock', value: true }));
|
|
928
|
+
if (caps.unlockDoor !== false) appendInputHelpRow($simpleBody, node._('knxUltimateMatterControllerDevice.input_help_write') || 'Write', node._('knxUltimateMatterControllerDevice.input_functions.unlock') || 'Unlock', functionCode({ function: 'lock', value: false }));
|
|
929
|
+
} else {
|
|
930
|
+
semanticInputDefinitions.forEach((definition) => {
|
|
931
|
+
const readTarget = structureMember(structure, 'attribute', definition.read);
|
|
932
|
+
const writeVariants = definition.writes || [{
|
|
933
|
+
descriptor: definition.write || definition.writeAttribute,
|
|
934
|
+
targetKind: definition.write ? 'command' : 'attribute',
|
|
935
|
+
value: definition.value,
|
|
936
|
+
omitValue: definition.omitValue
|
|
937
|
+
}];
|
|
938
|
+
writeVariants.forEach((variant) => {
|
|
939
|
+
const targetKind = variant.targetKind || 'command';
|
|
940
|
+
const writeTarget = structureMember(structure, targetKind, variant.descriptor, targetKind === 'attribute');
|
|
941
|
+
if (!writeTarget) return;
|
|
942
|
+
const payload = { function: definition.fn };
|
|
943
|
+
if (!variant.omitValue) payload.value = variant.value;
|
|
944
|
+
appendInputHelpRow($simpleBody, node._('knxUltimateMatterControllerDevice.input_help_write') || 'Write', node._(`knxUltimateMatterControllerDevice.input_functions.${definition.fn}`) || definition.label, functionCode(payload));
|
|
945
|
+
});
|
|
946
|
+
if (readTarget) {
|
|
947
|
+
appendInputHelpRow($simpleBody, node._('knxUltimateMatterControllerDevice.input_help_read') || 'Read', node._(`knxUltimateMatterControllerDevice.input_functions.${definition.fn}`) || definition.label, functionCode({ function: definition.fn }));
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
$('#matter-input-help-simple-empty').toggle($simpleBody.children().length === 0);
|
|
952
|
+
|
|
953
|
+
const $advancedBody = $('#matter-input-help-advanced-body').empty();
|
|
954
|
+
if (structure) {
|
|
955
|
+
structure.clusters.forEach((cluster) => {
|
|
956
|
+
(cluster.attributes || []).forEach((attribute) => {
|
|
957
|
+
const prefix = `${cluster.name || 'Cluster'} (${cluster.id}) · ${attribute.name} (${attribute.id})`;
|
|
958
|
+
appendInputHelpRow($advancedBody, node._('knxUltimateMatterControllerDevice.input_help_read') || 'Read', prefix, advancedCode('read', cluster.id, attribute.name));
|
|
959
|
+
if (attribute.writable === true) {
|
|
960
|
+
appendInputHelpRow($advancedBody, node._('knxUltimateMatterControllerDevice.input_help_write') || 'Write', prefix, advancedCode('write', cluster.id, attribute.name));
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
(cluster.commands || []).forEach((command) => {
|
|
964
|
+
const prefix = `${cluster.name || 'Cluster'} (${cluster.id}) · ${command.name}`;
|
|
965
|
+
appendInputHelpRow($advancedBody, node._('knxUltimateMatterControllerDevice.input_help_command') || 'Command', prefix, advancedCode('command', cluster.id, command.name));
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
$('#matter-input-help-advanced-empty').toggle($advancedBody.children().length === 0);
|
|
970
|
+
};
|
|
971
|
+
const copyInputHelpText = (text) => {
|
|
972
|
+
const copied = () => RED.notify(node._('knxUltimateMatterControllerDevice.input_help_copied') || 'Message copied', 'success');
|
|
973
|
+
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
974
|
+
navigator.clipboard.writeText(text).then(copied).catch(() => {
|
|
975
|
+
const $temporary = $('<textarea></textarea>').val(text).appendTo('body').select();
|
|
976
|
+
try { document.execCommand('copy'); copied(); } finally { $temporary.remove(); }
|
|
977
|
+
});
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const $temporary = $('<textarea></textarea>').val(text).appendTo('body').select();
|
|
981
|
+
try { document.execCommand('copy'); copied(); } finally { $temporary.remove(); }
|
|
982
|
+
};
|
|
983
|
+
$inputHelpPanel.off('click.matterInputHelp').on('click.matterInputHelp', '.matter-input-help-copy', function () {
|
|
984
|
+
copyInputHelpText($(this).data('copy-text') || '');
|
|
985
|
+
});
|
|
748
986
|
$tabs.addClass('hue-vertical-tabs');
|
|
749
987
|
$tabs.tabs(); // Tabs gestione KNX
|
|
988
|
+
$tabs.off('tabsactivate.matterInputHelp').on('tabsactivate.matterInputHelp', (event, ui) => {
|
|
989
|
+
if (ui && ui.newPanel && ui.newPanel.attr('id') === 'tabs-input') {
|
|
990
|
+
renderInputHelp();
|
|
991
|
+
}
|
|
992
|
+
});
|
|
750
993
|
$tabs.find('ul').addClass('ui-tabs-nav');
|
|
751
994
|
$tabs.find('li').removeClass('ui-corner-top').addClass('ui-corner-left');
|
|
995
|
+
$universalTabs.addClass('hue-vertical-tabs');
|
|
996
|
+
$universalTabs.tabs();
|
|
997
|
+
$universalTabs.find('ul').addClass('ui-tabs-nav');
|
|
998
|
+
$universalTabs.find('li').removeClass('ui-corner-top').addClass('ui-corner-left');
|
|
752
999
|
|
|
753
1000
|
function getDPT(_dpt, _destinationWidget) {
|
|
754
1001
|
// DPT Switch command
|
|
@@ -835,7 +1082,49 @@
|
|
|
835
1082
|
} catch (e) { }
|
|
836
1083
|
}
|
|
837
1084
|
|
|
1085
|
+
function getFixedDptGroupAddress(_sourceWidgetAutocomplete, _destinationWidgetName, _dptPrefix) {
|
|
1086
|
+
const $source = $(_sourceWidgetAutocomplete);
|
|
1087
|
+
if ($source.data('ui-autocomplete')) {
|
|
1088
|
+
try { $source.autocomplete('destroy'); } catch (error) { /* empty */ }
|
|
1089
|
+
}
|
|
1090
|
+
$source.autocomplete({
|
|
1091
|
+
minLength: 0,
|
|
1092
|
+
source: function (request, response) {
|
|
1093
|
+
if (!hasKnxServerSelected()) {
|
|
1094
|
+
response([]);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
const serverId = resolveKnxServerValue();
|
|
1098
|
+
$.getJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
|
|
1099
|
+
response($.map(data, function (value) {
|
|
1100
|
+
const searchable = `${value.ga} (${value.devicename}) DPT${value.dpt}`;
|
|
1101
|
+
if (!htmlUtilsfullCSVSearch(searchable, `${request.term || ''} ${_dptPrefix}`)) return null;
|
|
1102
|
+
return {
|
|
1103
|
+
label: `${value.ga} # ${value.devicename} # ${value.dpt}`,
|
|
1104
|
+
value: value.ga,
|
|
1105
|
+
deviceName: value.devicename
|
|
1106
|
+
};
|
|
1107
|
+
}));
|
|
1108
|
+
});
|
|
1109
|
+
},
|
|
1110
|
+
select: function (event, ui) {
|
|
1111
|
+
$(_destinationWidgetName).val(ui.item.deviceName || '');
|
|
1112
|
+
}
|
|
1113
|
+
}).focus(function () {
|
|
1114
|
+
$(this).autocomplete('search', `${$(this).val()}exactmatch`);
|
|
1115
|
+
});
|
|
1116
|
+
try {
|
|
1117
|
+
if (hasKnxServerSelected()) {
|
|
1118
|
+
const srv = RED.nodes.node(resolveKnxServerValue());
|
|
1119
|
+
if (srv && srv.id) KNX_enableSecureFormatting($source, srv.id);
|
|
1120
|
+
}
|
|
1121
|
+
} catch (error) { /* empty */ }
|
|
1122
|
+
}
|
|
1123
|
+
|
|
838
1124
|
const refreshKnxBindings = () => {
|
|
1125
|
+
getFixedDptGroupAddress("#node-input-universalLowBatteryGA", "#node-input-universalLowBatteryGAName", "1.");
|
|
1126
|
+
getFixedDptGroupAddress("#node-input-universalLowBatteryTextGA", "#node-input-universalLowBatteryTextGAName", "16.");
|
|
1127
|
+
|
|
839
1128
|
getDPT("1.", "#node-input-dptLightSwitch");
|
|
840
1129
|
getGroupAddress("#node-input-GALightSwitch", "#node-input-nameLightSwitch", "#node-input-dptLightSwitch", ["1."]);
|
|
841
1130
|
|
|
@@ -939,8 +1228,9 @@
|
|
|
939
1228
|
|
|
940
1229
|
const ensureVisibleMatterTabSelected = () => {
|
|
941
1230
|
const $active = $tabs.find('ul.ui-tabs-nav li.ui-tabs-active');
|
|
942
|
-
|
|
943
|
-
|
|
1231
|
+
const isTabAvailable = function () { return $(this).css('display') !== 'none'; };
|
|
1232
|
+
if ($active.length && $active.filter(isTabAvailable).length) return;
|
|
1233
|
+
const $firstVisible = $tabs.find('ul.ui-tabs-nav li').filter(isTabAvailable).first();
|
|
944
1234
|
if (!$firstVisible.length) return;
|
|
945
1235
|
const visibleIndex = $tabs.find('ul.ui-tabs-nav li').index($firstVisible);
|
|
946
1236
|
if (visibleIndex >= 0) {
|
|
@@ -991,7 +1281,7 @@
|
|
|
991
1281
|
};
|
|
992
1282
|
|
|
993
1283
|
const applyMatterCapabilities = (capabilities) => {
|
|
994
|
-
currentMatterCapabilities = capabilities || null;
|
|
1284
|
+
currentMatterCapabilities = normalizeMatterCapabilities(capabilities) || null;
|
|
995
1285
|
const caps = getEffectiveMatterCapabilities();
|
|
996
1286
|
const hasHueDevice = getHueDeviceValue() !== '';
|
|
997
1287
|
const supportsSwitch = hasHueDevice && caps.onOff !== false;
|
|
@@ -1001,14 +1291,20 @@
|
|
|
1001
1291
|
const hasRichBehaviour = supportsDim && (supportsTemperature || supportsColor);
|
|
1002
1292
|
const isDoorLock = caps.profile === 'doorLock';
|
|
1003
1293
|
const isMapped = ['mapped', 'windowCovering', 'thermostat', 'fan', 'switch'].includes(caps.profile);
|
|
1294
|
+
const isUniversal = caps.profile === 'universal' || isUniversalMode();
|
|
1295
|
+
const knxSelected = hasKnxServerSelected();
|
|
1004
1296
|
const deviceTypeDisplay = String(caps.deviceTypeDisplay || '').trim();
|
|
1005
1297
|
|
|
1006
|
-
setMatterTabVisible('tabs-1', supportsSwitch);
|
|
1007
|
-
setMatterTabVisible('tabs-2', supportsDim);
|
|
1008
|
-
setMatterTabVisible('tabs-3', supportsTemperature);
|
|
1009
|
-
setMatterTabVisible('tabs-4', supportsColor);
|
|
1010
|
-
setMatterTabVisible('tabs-mapped', isMapped);
|
|
1011
|
-
setMatterTabVisible('tabs-
|
|
1298
|
+
setMatterTabVisible('tabs-1', !isUniversal && knxSelected && supportsSwitch);
|
|
1299
|
+
setMatterTabVisible('tabs-2', !isUniversal && knxSelected && supportsDim);
|
|
1300
|
+
setMatterTabVisible('tabs-3', !isUniversal && knxSelected && supportsTemperature);
|
|
1301
|
+
setMatterTabVisible('tabs-4', !isUniversal && knxSelected && supportsColor);
|
|
1302
|
+
setMatterTabVisible('tabs-mapped', !isUniversal && knxSelected && isMapped);
|
|
1303
|
+
setMatterTabVisible('tabs-input', !isUniversal && hasHueDevice && (isMapped || isDoorLock));
|
|
1304
|
+
setMatterTabVisible('tabs-6', !isUniversal && knxSelected && hasHueDevice);
|
|
1305
|
+
$('#matter-universal-description').toggle(isUniversal);
|
|
1306
|
+
$pinSelect.val(isUniversal ? 'yes' : $pinSelect.val()).prop('disabled', isUniversal);
|
|
1307
|
+
updateLocateButtonState(false);
|
|
1012
1308
|
$('#matter-door-lock-options').toggle(isDoorLock);
|
|
1013
1309
|
$('#matter-switch-command-label').text(isDoorLock
|
|
1014
1310
|
? (node._('knxUltimateMatterControllerDevice.door_lock_command') || 'Lock / unlock')
|
|
@@ -1020,6 +1316,7 @@
|
|
|
1020
1316
|
$('#matter-door-lock-pin-required').toggle(isDoorLock && caps.requirePinForRemoteOperation === true);
|
|
1021
1317
|
$('#matter-mapped-profile').toggle(isMapped);
|
|
1022
1318
|
if (isMapped) renderMappedProfile(caps.mappedTargets || []);
|
|
1319
|
+
if (hasHueDevice && !isUniversal && (isMapped || isDoorLock)) renderInputHelp();
|
|
1023
1320
|
$('#matter-selected-device-type-value').text(deviceTypeDisplay);
|
|
1024
1321
|
$('#matter-selected-device-type').toggle(hasHueDevice && deviceTypeDisplay !== '');
|
|
1025
1322
|
try { $tabs.tabs('refresh'); } catch (error) { /* empty */ }
|
|
@@ -1077,9 +1374,12 @@
|
|
|
1077
1374
|
};
|
|
1078
1375
|
|
|
1079
1376
|
const updateTabsVisibility = () => {
|
|
1080
|
-
const knxSelected = hasKnxServerSelected();
|
|
1081
1377
|
const hueDeviceSelected = getHueDeviceValue() !== '';
|
|
1082
|
-
|
|
1378
|
+
applyMatterCapabilities(currentMatterCapabilities);
|
|
1379
|
+
const hasVisibleTab = $tabs.find('ul.ui-tabs-nav li').filter(function () {
|
|
1380
|
+
return $(this).css('display') !== 'none';
|
|
1381
|
+
}).length > 0;
|
|
1382
|
+
const shouldShowTabs = !isUniversalMode() && hueDeviceSelected && hasVisibleTab;
|
|
1083
1383
|
|
|
1084
1384
|
if (shouldShowTabs) {
|
|
1085
1385
|
$tabs.show();
|
|
@@ -1087,13 +1387,52 @@
|
|
|
1087
1387
|
} else {
|
|
1088
1388
|
$tabs.hide();
|
|
1089
1389
|
}
|
|
1090
|
-
applyMatterCapabilities(currentMatterCapabilities);
|
|
1091
1390
|
|
|
1092
1391
|
if ($pinSectionRow.length) {
|
|
1093
1392
|
$pinSectionRow.show();
|
|
1094
1393
|
}
|
|
1095
1394
|
};
|
|
1395
|
+
|
|
1396
|
+
const refreshUniversalServiceVisibility = () => {
|
|
1397
|
+
const universal = $matterControllerModeInput.val() === 'universal';
|
|
1398
|
+
$('#matter-universal-options').toggle(universal);
|
|
1399
|
+
$universalTabs.toggle(universal && $universalServiceInput.val() === 'batteryMonitor');
|
|
1400
|
+
if ($universalTabs.is(':visible')) {
|
|
1401
|
+
try { $universalTabs.tabs('refresh'); } catch (error) { /* empty */ }
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
const applyControllerMode = () => {
|
|
1406
|
+
const universal = $matterControllerModeInput.val() === 'universal';
|
|
1407
|
+
$('#matter-device-section-title, #matter-device-row').toggle(!universal);
|
|
1408
|
+
if (universal) {
|
|
1409
|
+
const universalName = node._('knxUltimateMatterControllerDevice.universal_mode') || 'Universal Mode';
|
|
1410
|
+
currentMatterCapabilities = universalMatterCapabilities();
|
|
1411
|
+
selectedMatterDeviceName = universalName;
|
|
1412
|
+
$deviceNameInput.val(universalName);
|
|
1413
|
+
$hueDeviceInput.val(UNIVERSAL_NODE_ID);
|
|
1414
|
+
$matterEndpointInput.val('');
|
|
1415
|
+
$matterCapabilitiesInput.val(serializeMatterCapabilities(currentMatterCapabilities));
|
|
1416
|
+
$nodeNameInput.val(node._('knxUltimateMatterControllerDevice.universal_controller_name') || 'Matter Universal Controller');
|
|
1417
|
+
$pinSelect.val('yes');
|
|
1418
|
+
} else if (String($hueDeviceInput.val() || '') === UNIVERSAL_NODE_ID) {
|
|
1419
|
+
currentMatterCapabilities = null;
|
|
1420
|
+
selectedMatterDeviceName = '';
|
|
1421
|
+
$deviceNameInput.val('');
|
|
1422
|
+
$hueDeviceInput.val('');
|
|
1423
|
+
$matterEndpointInput.val('');
|
|
1424
|
+
$matterCapabilitiesInput.val('');
|
|
1425
|
+
}
|
|
1426
|
+
refreshUniversalServiceVisibility();
|
|
1427
|
+
applyMatterCapabilities(currentMatterCapabilities);
|
|
1428
|
+
updateTabsVisibility();
|
|
1429
|
+
};
|
|
1430
|
+
|
|
1096
1431
|
updateTabsVisibility();
|
|
1432
|
+
applyControllerMode();
|
|
1433
|
+
|
|
1434
|
+
$matterControllerModeInput.on('change.knxUltimateMatterControllerDevice', applyControllerMode);
|
|
1435
|
+
$universalServiceInput.on('change.knxUltimateMatterControllerDevice', refreshUniversalServiceVisibility);
|
|
1097
1436
|
|
|
1098
1437
|
$knxServerInput.on('change.knxUltimateMatterControllerDevice', () => {
|
|
1099
1438
|
refreshKnxBindings();
|
|
@@ -1417,9 +1756,12 @@
|
|
|
1417
1756
|
} catch (error) { }
|
|
1418
1757
|
|
|
1419
1758
|
//RED.sidebar.removeTab("tabNRColor");
|
|
1420
|
-
|
|
1759
|
+
const universalMode = $("#node-input-matterControllerMode").val() === 'universal';
|
|
1760
|
+
this.matterControllerMode = universalMode ? 'universal' : 'single';
|
|
1761
|
+
if (universalMode || $("#node-input-enableNodePINS").val() === "yes") {
|
|
1421
1762
|
this.outputs = 1;
|
|
1422
1763
|
this.inputs = 1;
|
|
1764
|
+
this.enableNodePINS = 'yes';
|
|
1423
1765
|
} else {
|
|
1424
1766
|
this.outputs = 0;
|
|
1425
1767
|
this.inputs = 0;
|
|
@@ -1428,7 +1770,7 @@
|
|
|
1428
1770
|
this.updateLocalStateFromKNXWrite = $("#node-input-updateLocalStateFromKNXWrite").is(":checked"); // Starting from v 4.1.31
|
|
1429
1771
|
this.matterDeviceName = ($("#node-input-matterDeviceName").val() || '').trim();
|
|
1430
1772
|
this.matterNodeId = ($("#node-input-matterNodeId").val() || '').trim();
|
|
1431
|
-
this.matterEndpointId = Number($("#node-input-matterEndpointId").val() || 1);
|
|
1773
|
+
this.matterEndpointId = universalMode ? '' : Number($("#node-input-matterEndpointId").val() || 1);
|
|
1432
1774
|
this.matterDeviceCapabilities = ($("#node-input-matterDeviceCapabilities").val() || '').trim();
|
|
1433
1775
|
const mappedMappings = [];
|
|
1434
1776
|
const $mappedRows = $('#matter-mapped-profile-body .matter-mapped-row');
|
|
@@ -1444,7 +1786,9 @@
|
|
|
1444
1786
|
? JSON.stringify(mappedMappings)
|
|
1445
1787
|
: ($('#node-input-matterMappings').val() || this.matterMappings || '[]');
|
|
1446
1788
|
$('#node-input-matterMappings').val(this.matterMappings);
|
|
1447
|
-
this.name =
|
|
1789
|
+
this.name = universalMode
|
|
1790
|
+
? ($("#node-input-name").val() || this._('knxUltimateMatterControllerDevice.universal_controller_name') || 'Matter Universal Controller')
|
|
1791
|
+
: (this.matterDeviceName || this.name || '');
|
|
1448
1792
|
$("#node-input-name").val(this.name);
|
|
1449
1793
|
const parseSavedMatterCapabilities = (raw) => {
|
|
1450
1794
|
if (typeof raw !== 'string' || raw.trim() === '') return null;
|
|
@@ -1631,10 +1975,6 @@
|
|
|
1631
1975
|
target="_blank" href="https://youtu.be/jjEUI1J8bkA"><u><span data-i18n="common.youtube_sample"></span></u></a>
|
|
1632
1976
|
<br />
|
|
1633
1977
|
<br />
|
|
1634
|
-
<p align="center">
|
|
1635
|
-
<i class="fa-regular fa-lightbulb fa-bounce fa-2x"></i>
|
|
1636
|
-
</p>
|
|
1637
|
-
<br />
|
|
1638
1978
|
<label for="node-input-server">
|
|
1639
1979
|
<img
|
|
1640
1980
|
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAEZyIDYgQXVnIDIwMTAgMjE6NTI6MTkgKzAxMDD84aS8AAAAB3RJTUUH3gYYCicNV+4WIQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAACUSURBVHjaY2CgFZg5c+Z/ZEyWAZ8+f/6/ZsWs/xoamqMGkGrA6Wla/1+fVARjEBuGsSoGmY4eZSCNL59d/g8DIDbIAHR14OgFGQByKjIGKX5+6/T///8gGMQGiV1+/B0Fg70GIkD+RMYgxf/O5/7//2MSmAZhkBi6OrgB6Bg5DGB4ajr3f2xqsYYLSDE2THJUDg0AAAqyDVd4tp4YAAAAAElFTkSuQmCC"></img>
|
|
@@ -1651,12 +1991,116 @@
|
|
|
1651
1991
|
<input type="text" id="node-input-serverMatter" />
|
|
1652
1992
|
</div>
|
|
1653
1993
|
|
|
1994
|
+
<div class="form-row">
|
|
1995
|
+
<label for="node-input-matterControllerMode">
|
|
1996
|
+
<i class="fa fa-list"></i>
|
|
1997
|
+
<span data-i18n="knxUltimateMatterControllerDevice.controller_mode"></span>
|
|
1998
|
+
</label>
|
|
1999
|
+
<select id="node-input-matterControllerMode">
|
|
2000
|
+
<option value="single" data-i18n="knxUltimateMatterControllerDevice.single_mode"></option>
|
|
2001
|
+
<option value="universal" data-i18n="knxUltimateMatterControllerDevice.universal_mode"></option>
|
|
2002
|
+
</select>
|
|
2003
|
+
</div>
|
|
2004
|
+
|
|
2005
|
+
<div id="matter-universal-description" class="form-tips" style="display:none; margin:8px 0;"
|
|
2006
|
+
data-i18n="knxUltimateMatterControllerDevice.universal_mode_description"></div>
|
|
2007
|
+
|
|
2008
|
+
<div id="matter-universal-options" style="display:none;">
|
|
2009
|
+
<div class="form-row">
|
|
2010
|
+
<label for="node-input-universalService">
|
|
2011
|
+
<i class="fa fa-bell"></i>
|
|
2012
|
+
<span data-i18n="knxUltimateMatterControllerDevice.universal_service"></span>
|
|
2013
|
+
</label>
|
|
2014
|
+
<select id="node-input-universalService">
|
|
2015
|
+
<option value="batteryMonitor" data-i18n="knxUltimateMatterControllerDevice.universal_service_battery_monitor"></option>
|
|
2016
|
+
<option value="allEvents" data-i18n="knxUltimateMatterControllerDevice.universal_service_all_events"></option>
|
|
2017
|
+
</select>
|
|
2018
|
+
</div>
|
|
2019
|
+
<div id="matter-universal-tabs" style="display:none; width:900px;">
|
|
2020
|
+
<ul>
|
|
2021
|
+
<li>
|
|
2022
|
+
<a href="#matter-universal-battery-options">
|
|
2023
|
+
<i class="fa fa-battery-quarter"></i>
|
|
2024
|
+
<span data-i18n="knxUltimateMatterControllerDevice.universal_service_battery_monitor"></span>
|
|
2025
|
+
</a>
|
|
2026
|
+
</li>
|
|
2027
|
+
</ul>
|
|
2028
|
+
<div id="matter-universal-battery-options">
|
|
2029
|
+
<div class="form-row">
|
|
2030
|
+
<label for="node-input-universalBatteryThreshold">
|
|
2031
|
+
<i class="fa fa-battery-quarter"></i>
|
|
2032
|
+
<span data-i18n="knxUltimateMatterControllerDevice.universal_battery_threshold"></span>
|
|
2033
|
+
</label>
|
|
2034
|
+
<input type="number" id="node-input-universalBatteryThreshold" min="0" max="100" step="1" style="width:100px;" />
|
|
2035
|
+
<span style="margin-left:5px;">%</span>
|
|
2036
|
+
</div>
|
|
2037
|
+
<div class="form-row" style="display:flex; align-items:center; flex-wrap:nowrap; gap:5px;">
|
|
2038
|
+
<label for="node-input-universalLowBatteryGA" style="width:155px; flex:0 0 155px; margin:0;">
|
|
2039
|
+
<i class="fa fa-bell"></i>
|
|
2040
|
+
<span data-i18n="knxUltimateMatterControllerDevice.universal_low_battery_ga"></span>
|
|
2041
|
+
</label>
|
|
2042
|
+
|
|
2043
|
+
<label for="node-input-universalLowBatteryGA" style="width:25px; flex:0 0 25px; margin:0;"><span data-i18n="common.ga"></span></label>
|
|
2044
|
+
<input type="text" id="node-input-universalLowBatteryGA" placeholder="Ex: 1/1/1"
|
|
2045
|
+
style="width:70px; flex:0 0 70px; margin:0; text-align:left;" />
|
|
2046
|
+
|
|
2047
|
+
<label for="node-input-universalLowBatteryDPT" style="width:38px; flex:0 0 38px; margin:0; text-align:right;"><span data-i18n="common.dpt"></span></label>
|
|
2048
|
+
<input type="text" id="node-input-universalLowBatteryDPT" value="1.005" readonly
|
|
2049
|
+
style="width:75px; flex:0 0 75px; margin:0;" />
|
|
2050
|
+
|
|
2051
|
+
<label for="node-input-universalLowBatteryGAName" style="width:45px; flex:0 0 45px; margin:0; text-align:right;"><span data-i18n="common.name"></span></label>
|
|
2052
|
+
<input type="text" id="node-input-universalLowBatteryGAName"
|
|
2053
|
+
style="width:auto; min-width:90px; flex:1 1 auto; margin:0; text-align:left;" />
|
|
2054
|
+
</div>
|
|
2055
|
+
<div class="form-row" style="display:flex; align-items:center; flex-wrap:nowrap; gap:5px;">
|
|
2056
|
+
<label for="node-input-universalLowBatteryTextGA" style="width:155px; flex:0 0 155px; margin:0;">
|
|
2057
|
+
<i class="fa fa-font"></i>
|
|
2058
|
+
<span data-i18n="knxUltimateMatterControllerDevice.universal_low_battery_text_ga"></span>
|
|
2059
|
+
</label>
|
|
2060
|
+
|
|
2061
|
+
<label for="node-input-universalLowBatteryTextGA" style="width:25px; flex:0 0 25px; margin:0;"><span data-i18n="common.ga"></span></label>
|
|
2062
|
+
<input type="text" id="node-input-universalLowBatteryTextGA" placeholder="Ex: 1/1/1"
|
|
2063
|
+
style="width:70px; flex:0 0 70px; margin:0; text-align:left;" />
|
|
2064
|
+
|
|
2065
|
+
<label for="node-input-universalLowBatteryTextDPT" style="width:38px; flex:0 0 38px; margin:0; text-align:right;"><span data-i18n="common.dpt"></span></label>
|
|
2066
|
+
<input type="text" id="node-input-universalLowBatteryTextDPT" value="16.001" readonly
|
|
2067
|
+
style="width:75px; flex:0 0 75px; margin:0;" />
|
|
2068
|
+
|
|
2069
|
+
<label for="node-input-universalLowBatteryTextGAName" style="width:45px; flex:0 0 45px; margin:0; text-align:right;"><span data-i18n="common.name"></span></label>
|
|
2070
|
+
<input type="text" id="node-input-universalLowBatteryTextGAName"
|
|
2071
|
+
style="width:auto; min-width:90px; flex:1 1 auto; margin:0; text-align:left;" />
|
|
2072
|
+
</div>
|
|
2073
|
+
<div class="form-row">
|
|
2074
|
+
<label for="node-input-universalBatteryOnlyLow" style="width:260px;">
|
|
2075
|
+
<i class="fa fa-filter"></i>
|
|
2076
|
+
<span data-i18n="knxUltimateMatterControllerDevice.universal_battery_only_low"></span>
|
|
2077
|
+
</label>
|
|
2078
|
+
<input type="checkbox" id="node-input-universalBatteryOnlyLow" style="display:inline-block; width:auto;" />
|
|
2079
|
+
</div>
|
|
2080
|
+
<div class="form-row">
|
|
2081
|
+
<label for="node-input-universalEmitInitialState" style="width:260px;">
|
|
2082
|
+
<i class="fa fa-camera"></i>
|
|
2083
|
+
<span data-i18n="knxUltimateMatterControllerDevice.universal_emit_initial"></span>
|
|
2084
|
+
</label>
|
|
2085
|
+
<input type="checkbox" id="node-input-universalEmitInitialState" style="display:inline-block; width:auto;" />
|
|
2086
|
+
</div>
|
|
2087
|
+
<div class="form-row">
|
|
2088
|
+
<label for="node-input-universalIncludeRawEvent" style="width:260px;">
|
|
2089
|
+
<i class="fa fa-code"></i>
|
|
2090
|
+
<span data-i18n="knxUltimateMatterControllerDevice.universal_include_raw"></span>
|
|
2091
|
+
</label>
|
|
2092
|
+
<input type="checkbox" id="node-input-universalIncludeRawEvent" style="display:inline-block; width:auto;" />
|
|
2093
|
+
</div>
|
|
2094
|
+
</div>
|
|
2095
|
+
</div>
|
|
2096
|
+
</div>
|
|
2097
|
+
|
|
1654
2098
|
<br />
|
|
1655
|
-
<p>
|
|
2099
|
+
<p id="matter-device-section-title">
|
|
1656
2100
|
<b><span data-i18n="knxUltimateMatterControllerDevice.matter_section"></span></b>
|
|
1657
2101
|
</p>
|
|
1658
2102
|
|
|
1659
|
-
<div class="form-row">
|
|
2103
|
+
<div class="form-row" id="matter-device-row">
|
|
1660
2104
|
<label for="node-input-matterDeviceName">
|
|
1661
2105
|
<i class="fa fa-play-circle"></i> <span data-i18n="knxUltimateMatterControllerDevice.matter_device"></span>
|
|
1662
2106
|
</label>
|
|
@@ -1690,6 +2134,7 @@
|
|
|
1690
2134
|
<li><a href="#tabs-3"><i class="fa-solid fa-temperature-quarter"></i> <span data-i18n="knxUltimateMatterControllerDevice.tabs.tunable_white"></span></a></li>
|
|
1691
2135
|
<li><a href="#tabs-4"><i class="fa-solid fa-palette"></i> <span data-i18n="knxUltimateMatterControllerDevice.tabs.rgb_hsv"></span></a></li>
|
|
1692
2136
|
<li><a href="#tabs-mapped"><i class="fa-solid fa-list-check"></i> <span data-i18n="knxUltimateMatterControllerDevice.tabs.mappings"></span></a></li>
|
|
2137
|
+
<li><a href="#tabs-input"><i class="fa-solid fa-code"></i> <span data-i18n="knxUltimateMatterControllerDevice.input_help_label"></span></a></li>
|
|
1693
2138
|
<li><a href="#tabs-6"><i class="fa-solid fa-code-merge"></i> <span data-i18n="knxUltimateMatterControllerDevice.tabs.behaviour"></span></a></li>
|
|
1694
2139
|
</ul>
|
|
1695
2140
|
<div id="tabs-mapped">
|
|
@@ -1697,6 +2142,46 @@
|
|
|
1697
2142
|
<div id="matter-mapped-profile-body"></div>
|
|
1698
2143
|
</div>
|
|
1699
2144
|
</div>
|
|
2145
|
+
<div id="tabs-input">
|
|
2146
|
+
<p class="form-tips" data-i18n="knxUltimateMatterControllerDevice.input_help_pin_hint"></p>
|
|
2147
|
+
<p>
|
|
2148
|
+
<strong data-i18n="knxUltimateMatterControllerDevice.input_help_endpoint"></strong>:
|
|
2149
|
+
<code id="matter-input-help-endpoint"></code>.
|
|
2150
|
+
<span data-i18n="knxUltimateMatterControllerDevice.input_help_endpoint_hint"></span>
|
|
2151
|
+
</p>
|
|
2152
|
+
<h4 data-i18n="knxUltimateMatterControllerDevice.input_help_simple_title"></h4>
|
|
2153
|
+
<p data-i18n="knxUltimateMatterControllerDevice.input_help_simple_hint"></p>
|
|
2154
|
+
<table class="matter-input-help-table">
|
|
2155
|
+
<thead>
|
|
2156
|
+
<tr>
|
|
2157
|
+
<th data-i18n="knxUltimateMatterControllerDevice.input_help_operation"></th>
|
|
2158
|
+
<th data-i18n="knxUltimateMatterControllerDevice.input_help_function"></th>
|
|
2159
|
+
<th data-i18n="knxUltimateMatterControllerDevice.input_help_message"></th>
|
|
2160
|
+
<th></th>
|
|
2161
|
+
</tr>
|
|
2162
|
+
</thead>
|
|
2163
|
+
<tbody id="matter-input-help-simple-body"></tbody>
|
|
2164
|
+
</table>
|
|
2165
|
+
<p id="matter-input-help-simple-empty" style="display:none;" data-i18n="knxUltimateMatterControllerDevice.input_help_no_simple"></p>
|
|
2166
|
+
<details>
|
|
2167
|
+
<summary><strong data-i18n="knxUltimateMatterControllerDevice.input_help_advanced_title"></strong></summary>
|
|
2168
|
+
<p data-i18n="knxUltimateMatterControllerDevice.input_help_advanced_hint"></p>
|
|
2169
|
+
<div class="matter-input-help-advanced">
|
|
2170
|
+
<table class="matter-input-help-table">
|
|
2171
|
+
<thead>
|
|
2172
|
+
<tr>
|
|
2173
|
+
<th data-i18n="knxUltimateMatterControllerDevice.input_help_operation"></th>
|
|
2174
|
+
<th data-i18n="knxUltimateMatterControllerDevice.input_help_target"></th>
|
|
2175
|
+
<th data-i18n="knxUltimateMatterControllerDevice.input_help_message"></th>
|
|
2176
|
+
<th></th>
|
|
2177
|
+
</tr>
|
|
2178
|
+
</thead>
|
|
2179
|
+
<tbody id="matter-input-help-advanced-body"></tbody>
|
|
2180
|
+
</table>
|
|
2181
|
+
<p id="matter-input-help-advanced-empty" style="display:none;" data-i18n="knxUltimateMatterControllerDevice.input_help_no_structure"></p>
|
|
2182
|
+
</div>
|
|
2183
|
+
</details>
|
|
2184
|
+
</div>
|
|
1700
2185
|
<div id="tabs-1">
|
|
1701
2186
|
<p>
|
|
1702
2187
|
<div class="form-row">
|
|
@@ -2125,18 +2610,19 @@
|
|
|
2125
2610
|
</div>
|
|
2126
2611
|
<br/>
|
|
2127
2612
|
</div>
|
|
2128
|
-
<div class="form-row">
|
|
2129
|
-
<label for="node-input-enableNodePINS" style="width:260px;">
|
|
2130
|
-
<i class="fa fa-circle"></i> <span data-i18n="knxUltimateMatterControllerDevice.node_pins"></span>
|
|
2131
|
-
</label>
|
|
2132
|
-
<select id="node-input-enableNodePINS">
|
|
2133
|
-
<option value="no" data-i18n="knxUltimateMatterControllerDevice.node_pins_hide"></option>
|
|
2134
|
-
<option value="yes" data-i18n="knxUltimateMatterControllerDevice.node_pins_show"></option>
|
|
2135
|
-
</select>
|
|
2136
|
-
</div>
|
|
2137
2613
|
</p>
|
|
2138
2614
|
</div>
|
|
2139
2615
|
</div>
|
|
2616
|
+
|
|
2617
|
+
<div class="form-row" style="margin-top:12px;">
|
|
2618
|
+
<label for="node-input-enableNodePINS" style="width:260px;">
|
|
2619
|
+
<i class="fa fa-circle"></i> <span data-i18n="knxUltimateMatterControllerDevice.node_pins"></span>
|
|
2620
|
+
</label>
|
|
2621
|
+
<select id="node-input-enableNodePINS">
|
|
2622
|
+
<option value="no" data-i18n="knxUltimateMatterControllerDevice.node_pins_hide"></option>
|
|
2623
|
+
<option value="yes" data-i18n="knxUltimateMatterControllerDevice.node_pins_show"></option>
|
|
2624
|
+
</select>
|
|
2625
|
+
</div>
|
|
2140
2626
|
</div>
|
|
2141
2627
|
|
|
2142
2628
|
<br/>
|
|
@@ -2159,6 +2645,57 @@ the device picker. Door Lock, Window Covering, Thermostat, Fan and Switch endpoi
|
|
|
2159
2645
|
dedicated profiles. Switch press, long-press and multi-press events are emitted on the
|
|
2160
2646
|
optional flow output; simpler endpoints continue through the generic mapped fallback.
|
|
2161
2647
|
|
|
2648
|
+
**Flow input**
|
|
2649
|
+
|
|
2650
|
+
For a selected non-light endpoint, open the **Flow input** tab in the editor.
|
|
2651
|
+
The capability-driven tab shows copyable simple examples plus the selected endpoint,
|
|
2652
|
+
all advertised readable/writable attributes and all accepted commands.
|
|
2653
|
+
|
|
2654
|
+
Use `msg.payload = {function:"position", value:35}` for a simple write. Omit `value`
|
|
2655
|
+
to read a supported state, for example `{function:"temperature"}`. Values use human
|
|
2656
|
+
units and read output includes raw Matter details in `msg.matter`. The available
|
|
2657
|
+
functions are filtered by the endpoint and can include `onoff`, `level`, `position`,
|
|
2658
|
+
`tiltposition`, `open`, `close`, `stop`, `setpoint`, `coolingsetpoint`, `currenttemp`,
|
|
2659
|
+
`fanspeed`, sensor readings and `identify`. Door Lock accepts
|
|
2660
|
+
`{function:"lock", value:true|false}`.
|
|
2661
|
+
|
|
2662
|
+
The advanced top-level fields remain supported: `msg.clusterId` with
|
|
2663
|
+
`msg.command`/`msg.args`, or `msg.attribute` with optional `msg.value`. Node ID and
|
|
2664
|
+
Endpoint ID are already selected by this node; `msg.endpointId` is only an override.
|
|
2665
|
+
|
|
2666
|
+
**Device isolation**
|
|
2667
|
+
|
|
2668
|
+
Commands use a separate ordered queue for every commissioned device. An offline,
|
|
2669
|
+
timing-out or removed device cannot delay other Matter devices sharing the same KNX
|
|
2670
|
+
group address. A Controller node that still points to a removed device rejects the
|
|
2671
|
+
command immediately and shows **Device no longer commissioned** in red.
|
|
2672
|
+
|
|
2673
|
+
An unavailable-device error is latched. Further KNX and flow commands are ignored and
|
|
2674
|
+
cannot repaint the red status. The node resumes automatically when its Matter device
|
|
2675
|
+
reports `connected`; opening the node editor also clears the latch for a manual retry.
|
|
2676
|
+
|
|
2677
|
+
**Universal Mode**
|
|
2678
|
+
|
|
2679
|
+
Select **Universal Mode** in the **Mode** selector to listen to every attribute report,
|
|
2680
|
+
cluster event and device initialization from this controller. It always exposes one input
|
|
2681
|
+
and one output, does not require a KNX gateway, and does not use endpoint KNX mappings.
|
|
2682
|
+
Output values are in `msg.payload`; routing metadata is in `msg.matter`, including
|
|
2683
|
+
`source`, `nodeId`, `endpointId`, `clusterId` and the attribute/event name.
|
|
2684
|
+
The **Universal Battery Monitor** scans every node and endpoint for Power Source,
|
|
2685
|
+
emits an initial snapshot, and maintains complete battery state. It can emit only
|
|
2686
|
+
batteries below the configured threshold or every battery update. `msg.payload`
|
|
2687
|
+
contains normalized percent, charge level, replacement state, replaceability and
|
|
2688
|
+
voltage; raw Matter metadata remains in `msg.matter`. Send
|
|
2689
|
+
`{payload:{action:"getAllBatteries"}}` to retrieve the cached inventory.
|
|
2690
|
+
|
|
2691
|
+
Use `msg.nodeId`, `msg.endpointId`, `msg.clusterId` and either `msg.command` plus
|
|
2692
|
+
`msg.args`, or `msg.attribute`. Add `msg.value` for an attribute write; omit it for a
|
|
2693
|
+
read. The same selectors may be placed under `msg.matter`. For example, read battery
|
|
2694
|
+
with `{nodeId:"100", endpointId:1, clusterId:47, attribute:"batPercentRemaining"}` or
|
|
2695
|
+
switch on with `{nodeId:"100", endpointId:1, clusterId:6, command:"on", args:{}}`.
|
|
2696
|
+
Battery is emitted in Matter's raw half-percent units and can be divided by two in a
|
|
2697
|
+
Function node.
|
|
2698
|
+
|
|
2162
2699
|
|Property|Description|
|
|
2163
2700
|
|--|--|
|
|
2164
2701
|
| KNX GW | Select the KNX gateway to be used |
|
|
@@ -2242,7 +2779,7 @@ _Basic Matter effects_
|
|
|
2242
2779
|
| Read status at startup | Read the Matter endpoint status at Node-RED startup or full deploy and send it to the KNX bus. |
|
|
2243
2780
|
| 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. |
|
|
2244
2781
|
| 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. |
|
|
2245
|
-
| Node Input/Output PINs | For mapped endpoints,
|
|
2782
|
+
| Node Input/Output PINs | For mapped endpoints, use the simple `msg.payload = {function,value}` contract shown in the **Flow input** tab, or the advanced top-level Matter selectors. Read an attribute with `msg.clusterId` and `msg.attribute`; add `msg.requestFromRemote = true` to force a device read. Add `msg.value` to write, or use `msg.command` and `msg.args`. Door Lock accepts both `msg.payload = true/false` and `{function:"lock",value:true/false}`. |
|
|
2246
2783
|
| 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. |
|
|
2247
2784
|
| 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.|
|
|
2248
2785
|
| Day/Night | Select the group address used to set the day/night behaviour. The group address value is _true_ if daytime, _false_ if nighttime. |
|