matterbridge 3.0.3-dev-20250521-42f79f6 → 3.0.4-dev-20250525-c88cf84
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 +25 -4
- package/README.md +6 -12
- package/dist/frontend.js +85 -104
- package/dist/helpers.js +9 -3
- package/dist/matter/export.js +1 -1
- package/dist/matterbridgeBehaviors.js +39 -1
- package/dist/matterbridgeEndpoint.js +56 -29
- package/dist/matterbridgeEndpointHelpers.js +23 -55
- package/npm-shrinkwrap.json +10 -10
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,27 @@ If you like this project and find it useful, please consider giving it a star on
|
|
|
8
8
|
<img src="bmc-button.svg" alt="Buy me a coffee" width="120">
|
|
9
9
|
</a>
|
|
10
10
|
|
|
11
|
+
## [3.0.4] - 2025-05-??
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- [jsdoc]: Improved jsdoc for cluster helpers.
|
|
16
|
+
- [cover]: Added createDefaultTiltWindowCoveringClusterServer().
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- [legacy]: Removed legacy matter.js EndpointServer and logEndpoint that will be removed in matter.js 0.14.0. For developers: if you need to log the endpoint the call is Logger.get('LogEndpoint').info(endpoint).
|
|
21
|
+
- [package]: Updated dependencies.
|
|
22
|
+
- [package]: Updated multer package to 2.0.0.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- [virtualDevice]: Fixed possible vulnerability in the length of the nodeLabel.
|
|
27
|
+
|
|
28
|
+
<a href="https://www.buymeacoffee.com/luligugithub">
|
|
29
|
+
<img src="bmc-button.svg" alt="Buy me a coffee" width="80">
|
|
30
|
+
</a>
|
|
31
|
+
|
|
11
32
|
## [3.0.3] - 2025-05-19
|
|
12
33
|
|
|
13
34
|
### New plugins
|
|
@@ -23,10 +44,10 @@ AEG RX 9 / Electrolux Pure i9 robot vacuum plugin for Matterbridge.
|
|
|
23
44
|
|
|
24
45
|
### Added
|
|
25
46
|
|
|
26
|
-
- [virtual] Added virtual devices configuration mode in the Matterbridge Settings: 'Disabled', 'Light', 'Outlet', 'Switch', 'Mounted_switch'. Switch is not supported by Alexa. Mounted Switch is not supported by Apple.
|
|
27
|
-
- [deviceTypes] Added evse, waterHeater, solarPower, batteryStorage and heatPump device type.
|
|
28
|
-
- [waterHeater] Added WaterHeater class to create a Water Heater Device Type in one line of code (thanks https://github.com/lboue).
|
|
29
|
-
- [subscribe] Added a third parameter context (provisional implementation: when "context.offline === true" then this is a change coming from the device).
|
|
47
|
+
- [virtual]: Added virtual devices configuration mode in the Matterbridge Settings: 'Disabled', 'Light', 'Outlet', 'Switch', 'Mounted_switch'. Switch is not supported by Alexa. Mounted Switch is not supported by Apple.
|
|
48
|
+
- [deviceTypes]: Added evse, waterHeater, solarPower, batteryStorage and heatPump device type.
|
|
49
|
+
- [waterHeater]: Added WaterHeater class to create a Water Heater Device Type in one line of code (thanks https://github.com/lboue).
|
|
50
|
+
- [subscribe]: Added a third parameter context (provisional implementation: when "context.offline === true" then this is a change coming from the device).
|
|
30
51
|
|
|
31
52
|
### Changed
|
|
32
53
|
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://hub.docker.com/r/luligu/matterbridge)
|
|
6
6
|
[](https://hub.docker.com/r/luligu/matterbridge)
|
|
7
7
|

|
|
8
|
-

|
|
9
9
|
|
|
10
10
|
[](https://www.npmjs.com/package/matter-history)
|
|
11
11
|
[](https://www.npmjs.com/package/node-ansi-logger)
|
|
@@ -86,7 +86,7 @@ Follow these steps to install Matterbridge:
|
|
|
86
86
|
npm install -g matterbridge --omit=dev
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
on Linux you may need the necessary permissions:
|
|
89
|
+
on Linux or macOS you may need the necessary permissions:
|
|
90
90
|
|
|
91
91
|
```
|
|
92
92
|
sudo npm install -g matterbridge --omit=dev
|
|
@@ -142,7 +142,7 @@ Here's how to specify a different port number:
|
|
|
142
142
|
matterbridge -frontend [port number]
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
To use the frontend with ssl
|
|
145
|
+
To use the frontend with ssl see below.
|
|
146
146
|
|
|
147
147
|
From the frontend you can do all operations in an easy way.
|
|
148
148
|
|
|
@@ -196,7 +196,7 @@ The other Home Assistant Community Add-ons and plugins are not verified to work
|
|
|
196
196
|
<img src="screenshot/Shelly.svg" alt="Shelly plugin logo" width="100" />
|
|
197
197
|
</a>
|
|
198
198
|
|
|
199
|
-
Matterbridge shelly plugin allows you to expose all Shelly Gen 1, Gen 2, Gen 3 and BLU devices to Matter.
|
|
199
|
+
Matterbridge shelly plugin allows you to expose all Shelly Gen 1, Gen 2, Gen 3 and Gen 4 and BLU devices to Matter.
|
|
200
200
|
|
|
201
201
|
Features:
|
|
202
202
|
|
|
@@ -253,7 +253,7 @@ It is the ideal companion of the official [Matterbridge Home Assistant Add-on](h
|
|
|
253
253
|
<img src="frontend/public/matterbridge.svg" alt="Matterbridge logo" width="100" />
|
|
254
254
|
</a>
|
|
255
255
|
|
|
256
|
-
Matterbridge Webhooks plugin allows you to expose any webhooks to Matter
|
|
256
|
+
Matterbridge Webhooks plugin allows you to expose any webhooks to Matter.
|
|
257
257
|
|
|
258
258
|
### BTHome
|
|
259
259
|
|
|
@@ -506,12 +506,6 @@ As of version 18.4.x, the Robot is supported by the Home app only as a single, n
|
|
|
506
506
|
|
|
507
507
|
If a Robot is present alongside other devices in the bridge, the entire bridge becomes unstable in the Home app.
|
|
508
508
|
|
|
509
|
-
### Concentration measurements clusters
|
|
510
|
-
|
|
511
|
-
As of version 18.4.x, all cluster derived from the concentration measurement cluster hang the Home app while pairing and the entire bridge becomes unstable in the Home app.
|
|
512
|
-
|
|
513
|
-
For example: air quality sensors with TVOC measurement or co sensors with CarbonMonoxide measurement.
|
|
514
|
-
|
|
515
509
|
## Home Assistant
|
|
516
510
|
|
|
517
511
|
So far is the only controller supporting some Matter 1.2, 1.3 and 1.4 device type:
|
|
@@ -559,7 +553,7 @@ There is no support for these Matter device types:
|
|
|
559
553
|
|
|
560
554
|
In the zigbee2mqtt and shelly plugins select the option to expose
|
|
561
555
|
the switch devices like light or outlet cause they don't show up like switch
|
|
562
|
-
(Matterbridge uses a
|
|
556
|
+
(Matterbridge uses a switch device type without client cluster).
|
|
563
557
|
|
|
564
558
|
## SmartThings
|
|
565
559
|
|
package/dist/frontend.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
|
|
2
|
+
import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
|
|
2
3
|
import { createServer } from 'node:http';
|
|
3
4
|
import https from 'node:https';
|
|
4
5
|
import os from 'node:os';
|
|
@@ -11,7 +12,7 @@ import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE,
|
|
|
11
12
|
import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean, withTimeout } from './utils/export.js';
|
|
12
13
|
import { plg } from './matterbridgeTypes.js';
|
|
13
14
|
import { hasParameter } from './utils/export.js';
|
|
14
|
-
import {
|
|
15
|
+
import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
|
|
15
16
|
export const WS_ID_LOG = 0;
|
|
16
17
|
export const WS_ID_REFRESH_NEEDED = 1;
|
|
17
18
|
export const WS_ID_RESTART_NEEDED = 2;
|
|
@@ -569,8 +570,8 @@ export class Frontend {
|
|
|
569
570
|
return true;
|
|
570
571
|
return false;
|
|
571
572
|
}
|
|
572
|
-
getPowerSource(
|
|
573
|
-
if (!
|
|
573
|
+
getPowerSource(endpoint) {
|
|
574
|
+
if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
|
|
574
575
|
return undefined;
|
|
575
576
|
const powerSource = (device) => {
|
|
576
577
|
const featureMap = device.getAttribute(PowerSource.Cluster.id, 'featureMap');
|
|
@@ -584,9 +585,9 @@ export class Frontend {
|
|
|
584
585
|
}
|
|
585
586
|
return;
|
|
586
587
|
};
|
|
587
|
-
if (
|
|
588
|
-
return powerSource(
|
|
589
|
-
for (const child of
|
|
588
|
+
if (endpoint.hasClusterServer(PowerSource.Cluster.id))
|
|
589
|
+
return powerSource(endpoint);
|
|
590
|
+
for (const child of endpoint.getChildEndpoints()) {
|
|
590
591
|
if (child.hasClusterServer(PowerSource.Cluster.id))
|
|
591
592
|
return powerSource(child);
|
|
592
593
|
}
|
|
@@ -632,7 +633,7 @@ export class Frontend {
|
|
|
632
633
|
let attributes = '';
|
|
633
634
|
let supportedModes = [];
|
|
634
635
|
device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
635
|
-
if (typeof attributeValue === 'undefined')
|
|
636
|
+
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
636
637
|
return;
|
|
637
638
|
if (clusterName === 'onOff' && attributeName === 'onOff')
|
|
638
639
|
attributes += `OnOff: ${attributeValue} `;
|
|
@@ -787,6 +788,64 @@ export class Frontend {
|
|
|
787
788
|
});
|
|
788
789
|
return devices;
|
|
789
790
|
}
|
|
791
|
+
getClusters(pluginName, endpointNumber) {
|
|
792
|
+
const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
|
|
793
|
+
if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
794
|
+
this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const deviceTypes = [];
|
|
798
|
+
const clusters = [];
|
|
799
|
+
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
800
|
+
deviceTypes.push(d.deviceType);
|
|
801
|
+
});
|
|
802
|
+
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
803
|
+
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
804
|
+
return;
|
|
805
|
+
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
806
|
+
return;
|
|
807
|
+
clusters.push({
|
|
808
|
+
endpoint: endpoint.number.toString(),
|
|
809
|
+
id: 'main',
|
|
810
|
+
deviceTypes,
|
|
811
|
+
clusterName: capitalizeFirstLetter(clusterName),
|
|
812
|
+
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
813
|
+
attributeName,
|
|
814
|
+
attributeId: '0x' + attributeId.toString(16).padStart(2, '0'),
|
|
815
|
+
attributeValue: typeof attributeValue === 'object' ? stringify(attributeValue) : attributeValue.toString(),
|
|
816
|
+
attributeLocalValue: attributeValue,
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
const childEndpoints = endpoint.getChildEndpoints();
|
|
820
|
+
childEndpoints.forEach((childEndpoint) => {
|
|
821
|
+
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
822
|
+
this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const deviceTypes = [];
|
|
826
|
+
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
827
|
+
deviceTypes.push(d.deviceType);
|
|
828
|
+
});
|
|
829
|
+
childEndpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
830
|
+
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
831
|
+
return;
|
|
832
|
+
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
833
|
+
return;
|
|
834
|
+
clusters.push({
|
|
835
|
+
endpoint: childEndpoint.number.toString(),
|
|
836
|
+
id: childEndpoint.maybeId ?? 'null',
|
|
837
|
+
deviceTypes,
|
|
838
|
+
clusterName: capitalizeFirstLetter(clusterName),
|
|
839
|
+
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
840
|
+
attributeName,
|
|
841
|
+
attributeId: '0x' + attributeId.toString(16).padStart(2, '0'),
|
|
842
|
+
attributeValue: typeof attributeValue === 'object' ? stringify(attributeValue) : attributeValue.toString(),
|
|
843
|
+
attributeLocalValue: attributeValue,
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, endpoint: endpoint.maybeNumber, deviceTypes, clusters };
|
|
848
|
+
}
|
|
790
849
|
async wsMessageHandler(client, message) {
|
|
791
850
|
let data;
|
|
792
851
|
try {
|
|
@@ -1099,102 +1158,24 @@ export class Frontend {
|
|
|
1099
1158
|
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter endpoint in /api/clusters' }));
|
|
1100
1159
|
return;
|
|
1101
1160
|
}
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
return;
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
if (clusterServer.name === 'Descriptor' && key === 'deviceTypeList') {
|
|
1124
|
-
value.getLocal().forEach((deviceType) => {
|
|
1125
|
-
deviceTypes.push(deviceType.deviceType);
|
|
1126
|
-
});
|
|
1127
|
-
}
|
|
1128
|
-
let attributeValue;
|
|
1129
|
-
let attributeLocalValue;
|
|
1130
|
-
try {
|
|
1131
|
-
if (typeof value.getLocal() === 'object')
|
|
1132
|
-
attributeValue = stringify(value.getLocal());
|
|
1133
|
-
else
|
|
1134
|
-
attributeValue = value.getLocal().toString();
|
|
1135
|
-
attributeLocalValue = value.getLocal();
|
|
1136
|
-
}
|
|
1137
|
-
catch (error) {
|
|
1138
|
-
attributeValue = 'Fabric-Scoped';
|
|
1139
|
-
attributeLocalValue = 'Fabric-Scoped';
|
|
1140
|
-
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
1141
|
-
}
|
|
1142
|
-
clusters.push({
|
|
1143
|
-
endpoint: device.number ? device.number.toString() : '...',
|
|
1144
|
-
id: 'main',
|
|
1145
|
-
deviceTypes,
|
|
1146
|
-
clusterName: clusterServer.name,
|
|
1147
|
-
clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
|
|
1148
|
-
attributeName: key,
|
|
1149
|
-
attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
|
|
1150
|
-
attributeValue,
|
|
1151
|
-
attributeLocalValue,
|
|
1152
|
-
});
|
|
1153
|
-
});
|
|
1154
|
-
});
|
|
1155
|
-
endpointServer.getChildEndpoints().forEach((childEndpoint) => {
|
|
1156
|
-
deviceTypes = [];
|
|
1157
|
-
const name = childEndpoint.endpoint?.id;
|
|
1158
|
-
const clusterServers = childEndpoint.getAllClusterServers();
|
|
1159
|
-
clusterServers.forEach((clusterServer) => {
|
|
1160
|
-
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
1161
|
-
if (clusterServer.name === 'EveHistory')
|
|
1162
|
-
return;
|
|
1163
|
-
if (clusterServer.name === 'Descriptor' && key === 'deviceTypeList') {
|
|
1164
|
-
value.getLocal().forEach((deviceType) => {
|
|
1165
|
-
deviceTypes.push(deviceType.deviceType);
|
|
1166
|
-
});
|
|
1167
|
-
}
|
|
1168
|
-
let attributeValue;
|
|
1169
|
-
let attributeLocalValue;
|
|
1170
|
-
try {
|
|
1171
|
-
if (typeof value.getLocal() === 'object')
|
|
1172
|
-
attributeValue = stringify(value.getLocal());
|
|
1173
|
-
else
|
|
1174
|
-
attributeValue = value.getLocal().toString();
|
|
1175
|
-
attributeLocalValue = value.getLocal();
|
|
1176
|
-
}
|
|
1177
|
-
catch (error) {
|
|
1178
|
-
attributeValue = 'Fabric-Scoped';
|
|
1179
|
-
attributeLocalValue = 'Fabric-Scoped';
|
|
1180
|
-
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
1181
|
-
}
|
|
1182
|
-
clusters.push({
|
|
1183
|
-
endpoint: childEndpoint.number ? childEndpoint.number.toString() : '...',
|
|
1184
|
-
id: name,
|
|
1185
|
-
deviceTypes,
|
|
1186
|
-
clusterName: clusterServer.name,
|
|
1187
|
-
clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
|
|
1188
|
-
attributeName: key,
|
|
1189
|
-
attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
|
|
1190
|
-
attributeValue,
|
|
1191
|
-
attributeLocalValue,
|
|
1192
|
-
});
|
|
1193
|
-
});
|
|
1194
|
-
});
|
|
1195
|
-
});
|
|
1196
|
-
});
|
|
1197
|
-
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, plugin: data.params.plugin, deviceName, serialNumber, endpoint: data.params.endpoint, deviceTypes, response: clusters }));
|
|
1161
|
+
const response = this.getClusters(data.params.plugin, data.params.endpoint);
|
|
1162
|
+
if (response) {
|
|
1163
|
+
client.send(JSON.stringify({
|
|
1164
|
+
id: data.id,
|
|
1165
|
+
method: data.method,
|
|
1166
|
+
src: 'Matterbridge',
|
|
1167
|
+
dst: data.src,
|
|
1168
|
+
plugin: data.params.plugin,
|
|
1169
|
+
deviceName: response.deviceName,
|
|
1170
|
+
serialNumber: response.serialNumber,
|
|
1171
|
+
endpoint: response.endpoint,
|
|
1172
|
+
deviceTypes: response.deviceTypes,
|
|
1173
|
+
response: response.clusters,
|
|
1174
|
+
}));
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Endpoint not found in /api/clusters' }));
|
|
1178
|
+
}
|
|
1198
1179
|
}
|
|
1199
1180
|
else if (data.method === '/api/select' || data.method === '/api/select/devices') {
|
|
1200
1181
|
if (!isValidString(data.params.plugin, 10)) {
|
package/dist/helpers.js
CHANGED
|
@@ -25,13 +25,19 @@ export async function addVirtualDevice(aggregatorEndpoint, name, type, callback)
|
|
|
25
25
|
}
|
|
26
26
|
const device = new Endpoint(deviceType, {
|
|
27
27
|
id: name.replaceAll(' ', '') + ':' + type,
|
|
28
|
-
bridgedDeviceBasicInformation: { nodeLabel: name },
|
|
28
|
+
bridgedDeviceBasicInformation: { nodeLabel: name.slice(0, 32) },
|
|
29
29
|
onOff: { onOff: false, startUpOnOff: OnOff.StartUpOnOff.Off },
|
|
30
30
|
});
|
|
31
|
-
device.events.onOff.onOff$Changed.on(
|
|
31
|
+
device.events.onOff.onOff$Changed.on((value) => {
|
|
32
32
|
if (value) {
|
|
33
|
-
await device.setStateOf(OnOffBaseServer, { onOff: false });
|
|
34
33
|
callback();
|
|
34
|
+
process.nextTick(async () => {
|
|
35
|
+
try {
|
|
36
|
+
await device.setStateOf(OnOffBaseServer, { onOff: false });
|
|
37
|
+
}
|
|
38
|
+
catch (_error) {
|
|
39
|
+
}
|
|
40
|
+
});
|
|
35
41
|
}
|
|
36
42
|
});
|
|
37
43
|
await aggregatorEndpoint.add(device);
|
package/dist/matter/export.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from '@matter/main';
|
|
2
2
|
export { SemanticNamespace, ClosureTag, CompassDirectionTag, CompassLocationTag, DirectionTag, ElectricalMeasurementTag, LaundryTag, LevelTag, LocationTag, NumberTag, PositionTag, PowerSourceTag, RefrigeratorTag, RoomAirConditionerTag, SwitchesTag, } from '@matter/main';
|
|
3
3
|
export { AttributeElement, ClusterElement, ClusterModel, CommandElement, EventElement, FieldElement } from '@matter/main/model';
|
|
4
|
-
export {
|
|
4
|
+
export { MdnsService, Val } from '@matter/main/protocol';
|
|
@@ -117,6 +117,10 @@ export class MatterbridgeServerDevice {
|
|
|
117
117
|
this.log.info(`Setting cover lift percentage to ${liftPercent100thsValue} (endpoint ${this.endpointId}.${this.endpointNumber})`);
|
|
118
118
|
this.commandHandler.executeHandler('goToLiftPercentage', { request: { liftPercent100thsValue }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
|
|
119
119
|
}
|
|
120
|
+
goToTiltPercentage({ tiltPercent100thsValue }) {
|
|
121
|
+
this.log.info(`Setting cover tilt percentage to ${tiltPercent100thsValue} (endpoint ${this.endpointId}.${this.endpointNumber})`);
|
|
122
|
+
this.commandHandler.executeHandler('goToTiltPercentage', { request: { tiltPercent100thsValue }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
|
|
123
|
+
}
|
|
120
124
|
lockDoor() {
|
|
121
125
|
this.log.info(`Locking door (endpoint ${this.endpointId}.${this.endpointNumber})`);
|
|
122
126
|
this.commandHandler.executeHandler('lockDoor', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
|
|
@@ -281,7 +285,7 @@ export class MatterbridgeColorControlServer extends ColorControlServer.with(Colo
|
|
|
281
285
|
super.moveToColorTemperature({ optionsOverride, optionsMask, colorTemperatureMireds, transitionTime });
|
|
282
286
|
}
|
|
283
287
|
}
|
|
284
|
-
export class
|
|
288
|
+
export class MatterbridgeLiftWindowCoveringServer extends WindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift) {
|
|
285
289
|
upOrOpen() {
|
|
286
290
|
const device = this.agent.get(MatterbridgeServer).state.deviceCommand;
|
|
287
291
|
device.upOrOpen();
|
|
@@ -309,6 +313,40 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
|
|
|
309
313
|
async handleMovement(type, reversed, direction, targetPercent100ths) {
|
|
310
314
|
}
|
|
311
315
|
}
|
|
316
|
+
export class MatterbridgeLiftTiltWindowCoveringServer extends WindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.Tilt, WindowCovering.Feature.PositionAwareTilt) {
|
|
317
|
+
upOrOpen() {
|
|
318
|
+
const device = this.agent.get(MatterbridgeServer).state.deviceCommand;
|
|
319
|
+
device.upOrOpen();
|
|
320
|
+
device.log.debug(`MatterbridgeLiftTiltWindowCoveringServer: upOrOpen called`);
|
|
321
|
+
super.upOrOpen();
|
|
322
|
+
}
|
|
323
|
+
downOrClose() {
|
|
324
|
+
const device = this.agent.get(MatterbridgeServer).state.deviceCommand;
|
|
325
|
+
device.downOrClose();
|
|
326
|
+
device.log.debug(`MatterbridgeLiftTiltWindowCoveringServer: downOrClose called`);
|
|
327
|
+
super.downOrClose();
|
|
328
|
+
}
|
|
329
|
+
stopMotion() {
|
|
330
|
+
const device = this.agent.get(MatterbridgeServer).state.deviceCommand;
|
|
331
|
+
device.stopMotion();
|
|
332
|
+
device.log.debug(`MatterbridgeLiftTiltWindowCoveringServer: stopMotion called`);
|
|
333
|
+
super.stopMotion();
|
|
334
|
+
}
|
|
335
|
+
goToLiftPercentage({ liftPercent100thsValue }) {
|
|
336
|
+
const device = this.agent.get(MatterbridgeServer).state.deviceCommand;
|
|
337
|
+
device.goToLiftPercentage({ liftPercent100thsValue });
|
|
338
|
+
device.log.debug(`MatterbridgeLiftTiltWindowCoveringServer: goToLiftPercentage with ${liftPercent100thsValue}`);
|
|
339
|
+
super.goToLiftPercentage({ liftPercent100thsValue });
|
|
340
|
+
}
|
|
341
|
+
goToTiltPercentage({ tiltPercent100thsValue }) {
|
|
342
|
+
const device = this.agent.get(MatterbridgeServer).state.deviceCommand;
|
|
343
|
+
device.goToTiltPercentage({ tiltPercent100thsValue });
|
|
344
|
+
device.log.debug(`MatterbridgeLiftTiltWindowCoveringServer: goToTiltPercentage with ${tiltPercent100thsValue}`);
|
|
345
|
+
super.goToTiltPercentage({ tiltPercent100thsValue });
|
|
346
|
+
}
|
|
347
|
+
async handleMovement(type, reversed, direction, targetPercent100ths) {
|
|
348
|
+
}
|
|
349
|
+
}
|
|
312
350
|
export class MatterbridgeDoorLockServer extends DoorLockServer {
|
|
313
351
|
lockDoor() {
|
|
314
352
|
const device = this.agent.get(MatterbridgeServer).state.deviceCommand;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AnsiLogger,
|
|
1
|
+
import { AnsiLogger, CYAN, YELLOW, db, debugStringify, hk, or, zb } from './logger/export.js';
|
|
2
2
|
import { bridgedNode } from './matterbridgeDeviceTypes.js';
|
|
3
3
|
import { isValidNumber, isValidObject, isValidString } from './utils/export.js';
|
|
4
|
-
import { MatterbridgeServer, MatterbridgeServerDevice, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer,
|
|
5
|
-
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel,
|
|
4
|
+
import { MatterbridgeServer, MatterbridgeServerDevice, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeLiftWindowCoveringServer, MatterbridgeLiftTiltWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, MatterbridgeOperationalStateServer, } from './matterbridgeBehaviors.js';
|
|
5
|
+
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, } from './matterbridgeEndpointHelpers.js';
|
|
6
6
|
import { Endpoint, Lifecycle, MutableEndpoint, NamedHandler, SupportedBehaviors, UINT16_MAX, UINT32_MAX, VendorId } from '@matter/main';
|
|
7
7
|
import { getClusterNameById, MeasurementType } from '@matter/main/types';
|
|
8
8
|
import { Descriptor } from '@matter/main/clusters/descriptor';
|
|
@@ -182,20 +182,8 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
182
182
|
async subscribeAttribute(cluster, attribute, listener, log) {
|
|
183
183
|
return await subscribeAttribute(this, cluster, attribute, listener, log);
|
|
184
184
|
}
|
|
185
|
-
async triggerEvent(
|
|
186
|
-
|
|
187
|
-
if (this.construction.status !== Lifecycle.Status.Active) {
|
|
188
|
-
this.log.error(`triggerEvent ${hk}${clusterName}.${event}${er} error: Endpoint ${or}${this.maybeId}${er}:${or}${this.maybeNumber}${er} is in the ${BLUE}${this.construction.status}${er} state`);
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
const events = this.events;
|
|
192
|
-
if (!(clusterName in events) || !(event in events[clusterName])) {
|
|
193
|
-
this.log.error(`triggerEvent ${hk}${event}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${this.id}${er}:${or}${this.number}${er}`);
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
await this.act((agent) => agent[clusterName].events[event].emit(payload, agent.context));
|
|
197
|
-
log?.info(`${db}Trigger event ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${event}${db} with ${debugStringify(payload)}${db} on endpoint ${or}${this.id}${db}:${or}${this.number}${db} `);
|
|
198
|
-
return true;
|
|
185
|
+
async triggerEvent(cluster, event, payload, log) {
|
|
186
|
+
return await triggerEvent(this, cluster, event, payload, log);
|
|
199
187
|
}
|
|
200
188
|
addClusterServers(serverList) {
|
|
201
189
|
addClusterServers(this, serverList);
|
|
@@ -237,6 +225,8 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
237
225
|
if (!this.lifecycle.isReady || this.construction.status !== Lifecycle.Status.Active)
|
|
238
226
|
return;
|
|
239
227
|
for (const [clusterName, clusterAttributes] of Object.entries(this.state)) {
|
|
228
|
+
if (!isNaN(Number(clusterName)))
|
|
229
|
+
continue;
|
|
240
230
|
for (const [attributeName, attributeValue] of Object.entries(clusterAttributes)) {
|
|
241
231
|
const clusterId = getClusterId(this, clusterName);
|
|
242
232
|
if (clusterId === undefined) {
|
|
@@ -589,8 +579,8 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
589
579
|
colorTempPhysicalMinMireds,
|
|
590
580
|
colorTempPhysicalMaxMireds,
|
|
591
581
|
coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
|
|
592
|
-
remainingTime: 0,
|
|
593
582
|
startUpColorTemperatureMireds: null,
|
|
583
|
+
remainingTime: 0,
|
|
594
584
|
});
|
|
595
585
|
return this;
|
|
596
586
|
}
|
|
@@ -634,7 +624,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
634
624
|
});
|
|
635
625
|
return this;
|
|
636
626
|
}
|
|
637
|
-
createCtColorControlClusterServer(colorTemperatureMireds =
|
|
627
|
+
createCtColorControlClusterServer(colorTemperatureMireds = 250, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
|
|
638
628
|
this.behaviors.require(MatterbridgeColorControlServer.with(ColorControl.Feature.ColorTemperature), {
|
|
639
629
|
colorMode: ColorControl.ColorMode.ColorTemperatureMireds,
|
|
640
630
|
enhancedColorMode: ColorControl.EnhancedColorMode.ColorTemperatureMireds,
|
|
@@ -647,8 +637,8 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
647
637
|
colorTempPhysicalMinMireds,
|
|
648
638
|
colorTempPhysicalMaxMireds,
|
|
649
639
|
coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
|
|
650
|
-
remainingTime: 0,
|
|
651
640
|
startUpColorTemperatureMireds: null,
|
|
641
|
+
remainingTime: 0,
|
|
652
642
|
});
|
|
653
643
|
return this;
|
|
654
644
|
}
|
|
@@ -658,12 +648,13 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
658
648
|
await this.setAttribute(ColorControl.Cluster.id, 'enhancedColorMode', colorMode, this.log);
|
|
659
649
|
}
|
|
660
650
|
}
|
|
661
|
-
createDefaultWindowCoveringClusterServer(positionPercent100ths) {
|
|
662
|
-
this.behaviors.require(
|
|
663
|
-
type
|
|
651
|
+
createDefaultWindowCoveringClusterServer(positionPercent100ths, type = WindowCovering.WindowCoveringType.Rollershade, endProductType = WindowCovering.EndProductType.RollerShade) {
|
|
652
|
+
this.behaviors.require(MatterbridgeLiftWindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift), {
|
|
653
|
+
type,
|
|
654
|
+
numberOfActuationsLift: 0,
|
|
664
655
|
configStatus: {
|
|
665
656
|
operational: true,
|
|
666
|
-
onlineReserved:
|
|
657
|
+
onlineReserved: false,
|
|
667
658
|
liftMovementReversed: false,
|
|
668
659
|
liftPositionAware: true,
|
|
669
660
|
tiltPositionAware: false,
|
|
@@ -671,13 +662,37 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
671
662
|
tiltEncoderControlled: false,
|
|
672
663
|
},
|
|
673
664
|
operationalStatus: { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped },
|
|
674
|
-
endProductType
|
|
665
|
+
endProductType,
|
|
675
666
|
mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false },
|
|
676
667
|
targetPositionLiftPercent100ths: positionPercent100ths ?? 0,
|
|
677
668
|
currentPositionLiftPercent100ths: positionPercent100ths ?? 0,
|
|
678
669
|
});
|
|
679
670
|
return this;
|
|
680
671
|
}
|
|
672
|
+
createDefaultLiftTiltWindowCoveringClusterServer(positionLiftPercent100ths, positionTiltPercent100ths, type = WindowCovering.WindowCoveringType.TiltBlindLift, endProductType = WindowCovering.EndProductType.InteriorBlind) {
|
|
673
|
+
this.behaviors.require(MatterbridgeLiftTiltWindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.Tilt, WindowCovering.Feature.PositionAwareTilt), {
|
|
674
|
+
type,
|
|
675
|
+
numberOfActuationsLift: 0,
|
|
676
|
+
numberOfActuationsTilt: 0,
|
|
677
|
+
configStatus: {
|
|
678
|
+
operational: true,
|
|
679
|
+
onlineReserved: false,
|
|
680
|
+
liftMovementReversed: false,
|
|
681
|
+
liftPositionAware: true,
|
|
682
|
+
tiltPositionAware: true,
|
|
683
|
+
liftEncoderControlled: false,
|
|
684
|
+
tiltEncoderControlled: false,
|
|
685
|
+
},
|
|
686
|
+
operationalStatus: { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped },
|
|
687
|
+
endProductType,
|
|
688
|
+
mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false },
|
|
689
|
+
targetPositionLiftPercent100ths: positionLiftPercent100ths ?? 0,
|
|
690
|
+
currentPositionLiftPercent100ths: positionLiftPercent100ths ?? 0,
|
|
691
|
+
targetPositionTiltPercent100ths: positionTiltPercent100ths ?? 0,
|
|
692
|
+
currentPositionTiltPercent100ths: positionTiltPercent100ths ?? 0,
|
|
693
|
+
});
|
|
694
|
+
return this;
|
|
695
|
+
}
|
|
681
696
|
async setWindowCoveringTargetAsCurrentAndStopped() {
|
|
682
697
|
const position = this.getAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', this.log);
|
|
683
698
|
if (isValidNumber(position, 0, 10000)) {
|
|
@@ -689,6 +704,13 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
689
704
|
}, this.log);
|
|
690
705
|
}
|
|
691
706
|
this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths and targetPositionLiftPercent100ths to ${position} and operationalStatus to Stopped.`);
|
|
707
|
+
if (this.hasAttributeServer(WindowCovering.Cluster.id, 'currentPositionTiltPercent100ths')) {
|
|
708
|
+
const position = this.getAttribute(WindowCovering.Cluster.id, 'currentPositionTiltPercent100ths', this.log);
|
|
709
|
+
if (isValidNumber(position, 0, 10000)) {
|
|
710
|
+
await this.setAttribute(WindowCovering.Cluster.id, 'targetPositionTiltPercent100ths', position, this.log);
|
|
711
|
+
}
|
|
712
|
+
this.log.debug(`Set WindowCovering currentPositionTiltPercent100ths and targetPositionTiltPercent100ths to ${position} and operationalStatus to Stopped.`);
|
|
713
|
+
}
|
|
692
714
|
}
|
|
693
715
|
async setWindowCoveringCurrentTargetStatus(current, target, status) {
|
|
694
716
|
await this.setAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', current, this.log);
|
|
@@ -715,10 +737,15 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
715
737
|
return status.global;
|
|
716
738
|
}
|
|
717
739
|
}
|
|
718
|
-
async setWindowCoveringTargetAndCurrentPosition(
|
|
719
|
-
await this.setAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths',
|
|
720
|
-
await this.setAttribute(WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths',
|
|
721
|
-
this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${
|
|
740
|
+
async setWindowCoveringTargetAndCurrentPosition(liftPosition, tiltPosition) {
|
|
741
|
+
await this.setAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', liftPosition, this.log);
|
|
742
|
+
await this.setAttribute(WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', liftPosition, this.log);
|
|
743
|
+
this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${liftPosition} and targetPositionLiftPercent100ths: ${liftPosition}.`);
|
|
744
|
+
if (tiltPosition && this.hasAttributeServer(WindowCovering.Cluster.id, 'currentPositionTiltPercent100ths')) {
|
|
745
|
+
await this.setAttribute(WindowCovering.Cluster.id, 'currentPositionTiltPercent100ths', tiltPosition, this.log);
|
|
746
|
+
await this.setAttribute(WindowCovering.Cluster.id, 'targetPositionTiltPercent100ths', tiltPosition, this.log);
|
|
747
|
+
this.log.debug(`Set WindowCovering currentPositionTiltPercent100ths: ${tiltPosition} and targetPositionTiltPercent100ths: ${tiltPosition}.`);
|
|
748
|
+
}
|
|
722
749
|
}
|
|
723
750
|
createDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
|
|
724
751
|
this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode), {
|
|
@@ -74,7 +74,7 @@ import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from '@ma
|
|
|
74
74
|
import { createHash } from 'node:crypto';
|
|
75
75
|
import { BLUE, CYAN, db, debugStringify, er, hk, or, YELLOW, zb } from 'node-ansi-logger';
|
|
76
76
|
import { deepCopy, deepEqual, isValidArray } from './utils/export.js';
|
|
77
|
-
import { MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer,
|
|
77
|
+
import { MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeLiftWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeOperationalStateServer, } from './matterbridgeBehaviors.js';
|
|
78
78
|
export function capitalizeFirstLetter(name) {
|
|
79
79
|
if (!name)
|
|
80
80
|
return name;
|
|
@@ -141,7 +141,7 @@ export function getBehaviourTypeFromClusterServerId(clusterId) {
|
|
|
141
141
|
if (clusterId === ColorControl.Cluster.id)
|
|
142
142
|
return MatterbridgeColorControlServer;
|
|
143
143
|
if (clusterId === WindowCovering.Cluster.id)
|
|
144
|
-
return
|
|
144
|
+
return MatterbridgeLiftWindowCoveringServer.with('Lift', 'PositionAwareLift');
|
|
145
145
|
if (clusterId === Thermostat.Cluster.id)
|
|
146
146
|
return MatterbridgeThermostatServer.with('AutoMode', 'Heating', 'Cooling');
|
|
147
147
|
if (clusterId === FanControl.Cluster.id)
|
|
@@ -383,59 +383,8 @@ export function getClusterId(endpoint, cluster) {
|
|
|
383
383
|
return endpoint.behaviors.supported[lowercaseFirstLetter(cluster)]?.schema?.id;
|
|
384
384
|
}
|
|
385
385
|
export function getAttributeId(endpoint, cluster, attribute) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
else if (attribute === 'featureMap')
|
|
389
|
-
return 0xfffc;
|
|
390
|
-
else if (attribute === 'eventList')
|
|
391
|
-
return 0xfffa;
|
|
392
|
-
else if (attribute === 'generatedCommandList')
|
|
393
|
-
return 0xfff8;
|
|
394
|
-
else if (attribute === 'acceptedCommandList')
|
|
395
|
-
return 0xfff9;
|
|
396
|
-
else if (attribute === 'clusterRevision')
|
|
397
|
-
return 0xfffd;
|
|
398
|
-
else {
|
|
399
|
-
if (endpoint.behaviors.supported[lowercaseFirstLetter(cluster)]?.schema?.type === 'ConcentrationMeasurement') {
|
|
400
|
-
if (attribute === 'measuredValue')
|
|
401
|
-
return 0x0;
|
|
402
|
-
else if (attribute === 'minMeasuredValue')
|
|
403
|
-
return 0x1;
|
|
404
|
-
else if (attribute === 'maxMeasuredValue')
|
|
405
|
-
return 0x2;
|
|
406
|
-
else if (attribute === 'peakMeasuredValue')
|
|
407
|
-
return 0x3;
|
|
408
|
-
else if (attribute === 'peakMeasuredValueWindow')
|
|
409
|
-
return 0x4;
|
|
410
|
-
else if (attribute === 'averageMeasuredValue')
|
|
411
|
-
return 0x5;
|
|
412
|
-
else if (attribute === 'averageMeasuredValueWindow')
|
|
413
|
-
return 0x6;
|
|
414
|
-
else if (attribute === 'uncertainty')
|
|
415
|
-
return 0x7;
|
|
416
|
-
else if (attribute === 'measurementUnit')
|
|
417
|
-
return 0x8;
|
|
418
|
-
else if (attribute === 'measurementMedium')
|
|
419
|
-
return 0x9;
|
|
420
|
-
else if (attribute === 'levelValue')
|
|
421
|
-
return 0xa;
|
|
422
|
-
}
|
|
423
|
-
if (endpoint.behaviors.supported[lowercaseFirstLetter(cluster)]?.schema?.type === 'OperationalState') {
|
|
424
|
-
if (attribute === 'phaseList')
|
|
425
|
-
return 0x0;
|
|
426
|
-
else if (attribute === 'currentPhase')
|
|
427
|
-
return 0x1;
|
|
428
|
-
else if (attribute === 'countdownTime')
|
|
429
|
-
return 0x2;
|
|
430
|
-
else if (attribute === 'operationalStateList')
|
|
431
|
-
return 0x3;
|
|
432
|
-
else if (attribute === 'operationalState')
|
|
433
|
-
return 0x4;
|
|
434
|
-
else if (attribute === 'operationalError')
|
|
435
|
-
return 0x5;
|
|
436
|
-
}
|
|
437
|
-
return endpoint.behaviors.supported[lowercaseFirstLetter(cluster)]?.schema?.children?.find((child) => child.name === capitalizeFirstLetter(attribute))?.id;
|
|
438
|
-
}
|
|
386
|
+
const clusterBehavior = endpoint.behaviors.supported[lowercaseFirstLetter(cluster)];
|
|
387
|
+
return clusterBehavior?.cluster.attributes[lowercaseFirstLetter(attribute)]?.id;
|
|
439
388
|
}
|
|
440
389
|
export function getAttribute(endpoint, cluster, attribute, log) {
|
|
441
390
|
const clusterName = getBehavior(endpoint, cluster)?.id;
|
|
@@ -534,6 +483,25 @@ export async function subscribeAttribute(endpoint, cluster, attribute, listener,
|
|
|
534
483
|
log?.info(`${db}Subscribed endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db}`);
|
|
535
484
|
return true;
|
|
536
485
|
}
|
|
486
|
+
export async function triggerEvent(endpoint, cluster, event, payload, log) {
|
|
487
|
+
const clusterName = getBehavior(endpoint, cluster)?.id;
|
|
488
|
+
if (!clusterName) {
|
|
489
|
+
endpoint.log.error(`triggerEvent ${hk}${event}${er} error: cluster not found on endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er}`);
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
if (endpoint.construction.status !== Lifecycle.Status.Active) {
|
|
493
|
+
endpoint.log.error(`triggerEvent ${hk}${clusterName}.${event}${er} error: Endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
const events = endpoint.events;
|
|
497
|
+
if (!(clusterName in events) || !(event in events[clusterName])) {
|
|
498
|
+
endpoint.log.error(`triggerEvent ${hk}${event}${er} error: cluster ${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
await endpoint.act((agent) => agent[clusterName].events[event].emit(payload, agent.context));
|
|
502
|
+
log?.info(`${db}Trigger event ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${event}${db} with ${debugStringify(payload)}${db} on endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} `);
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
537
505
|
export function getDefaultOperationalStateClusterServer(operationalState = OperationalState.OperationalStateEnum.Stopped) {
|
|
538
506
|
return optionsFor(MatterbridgeOperationalStateServer, {
|
|
539
507
|
phaseList: [],
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4-dev-20250525-c88cf84",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge",
|
|
9
|
-
"version": "3.0.
|
|
9
|
+
"version": "3.0.4-dev-20250525-c88cf84",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@matter/main": "0.13.0",
|
|
13
13
|
"archiver": "7.0.1",
|
|
14
14
|
"express": "5.1.0",
|
|
15
15
|
"glob": "11.0.2",
|
|
16
|
-
"multer": "
|
|
16
|
+
"multer": "2.0.0",
|
|
17
17
|
"node-ansi-logger": "3.0.1",
|
|
18
18
|
"node-persist-manager": "1.0.8",
|
|
19
19
|
"ws": "8.18.2"
|
|
@@ -1124,9 +1124,9 @@
|
|
|
1124
1124
|
"license": "ISC"
|
|
1125
1125
|
},
|
|
1126
1126
|
"node_modules/jackspeak": {
|
|
1127
|
-
"version": "4.1.
|
|
1128
|
-
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.
|
|
1129
|
-
"integrity": "sha512-
|
|
1127
|
+
"version": "4.1.1",
|
|
1128
|
+
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
|
|
1129
|
+
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
|
|
1130
1130
|
"license": "BlueOak-1.0.0",
|
|
1131
1131
|
"dependencies": {
|
|
1132
1132
|
"@isaacs/cliui": "^8.0.2"
|
|
@@ -1280,9 +1280,9 @@
|
|
|
1280
1280
|
"license": "MIT"
|
|
1281
1281
|
},
|
|
1282
1282
|
"node_modules/multer": {
|
|
1283
|
-
"version": "
|
|
1284
|
-
"resolved": "https://registry.npmjs.org/multer/-/multer-
|
|
1285
|
-
"integrity": "sha512-
|
|
1283
|
+
"version": "2.0.0",
|
|
1284
|
+
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.0.tgz",
|
|
1285
|
+
"integrity": "sha512-bS8rPZurbAuHGAnApbM9d4h1wSoYqrOqkE+6a64KLMK9yWU7gJXBDDVklKQ3TPi9DRb85cRs6yXaC0+cjxRtRg==",
|
|
1286
1286
|
"license": "MIT",
|
|
1287
1287
|
"dependencies": {
|
|
1288
1288
|
"append-field": "^1.0.0",
|
|
@@ -1294,7 +1294,7 @@
|
|
|
1294
1294
|
"xtend": "^4.0.0"
|
|
1295
1295
|
},
|
|
1296
1296
|
"engines": {
|
|
1297
|
-
"node": ">=
|
|
1297
|
+
"node": ">= 10.16.0"
|
|
1298
1298
|
}
|
|
1299
1299
|
},
|
|
1300
1300
|
"node_modules/multer/node_modules/media-typer": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4-dev-20250525-c88cf84",
|
|
4
4
|
"description": "Matterbridge plugin manager for Matter",
|
|
5
5
|
"author": "https://github.com/Luligu",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"archiver": "7.0.1",
|
|
99
99
|
"express": "5.1.0",
|
|
100
100
|
"glob": "11.0.2",
|
|
101
|
-
"multer": "
|
|
101
|
+
"multer": "2.0.0",
|
|
102
102
|
"node-ansi-logger": "3.0.1",
|
|
103
103
|
"node-persist-manager": "1.0.8",
|
|
104
104
|
"ws": "8.18.2"
|