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 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
  [![Docker Version](https://img.shields.io/docker/v/luligu/matterbridge?label=docker%20version&sort=semver)](https://hub.docker.com/r/luligu/matterbridge)
6
6
  [![Docker Pulls](https://img.shields.io/docker/pulls/luligu/matterbridge.svg)](https://hub.docker.com/r/luligu/matterbridge)
7
7
  ![Node.js CI](https://github.com/Luligu/matterbridge/actions/workflows/build.yml/badge.svg)
8
- ![Coverage](https://img.shields.io/badge/Jest%20coverage-85%25-brightgreen)
8
+ ![Coverage](https://img.shields.io/badge/Jest%20coverage-86%25-brightgreen)
9
9
 
10
10
  [![power by](https://img.shields.io/badge/powered%20by-matter--history-blue)](https://www.npmjs.com/package/matter-history)
11
11
  [![power by](https://img.shields.io/badge/powered%20by-node--ansi--logger-blue)](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 place the certificates in the .matterbridge/certs directory: cert.pem, key.pem and ca.pem (optional).
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 modified switch device type without client cluster).
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 { EndpointServer, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Lifecycle } from '@matter/main';
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 { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
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(device) {
573
- if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
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 (device.hasClusterServer(PowerSource.Cluster.id))
588
- return powerSource(device);
589
- for (const child of device.getChildEndpoints()) {
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 clusters = [];
1103
- let deviceName = '';
1104
- let serialNumber = '';
1105
- let deviceTypes = [];
1106
- this.matterbridge.devices.forEach(async (device) => {
1107
- if (data.params.plugin !== device.plugin)
1108
- return;
1109
- if (data.params.endpoint !== device.number)
1110
- return;
1111
- deviceName = device.deviceName ?? 'Unknown';
1112
- serialNumber = device.serialNumber ?? 'Unknown';
1113
- deviceTypes = [];
1114
- const endpointServer = EndpointServer.forEndpoint(device);
1115
- const clusterServers = endpointServer.getAllClusterServers();
1116
- clusterServers.forEach((clusterServer) => {
1117
- Object.entries(clusterServer.attributes).forEach(([key, value]) => {
1118
- if (clusterServer.name === 'EveHistory') {
1119
- if (['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(key)) {
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(async (value) => {
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);
@@ -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 { logEndpoint, MdnsService, Val } from '@matter/main/protocol';
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 MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift) {
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, BLUE, CYAN, YELLOW, db, debugStringify, er, hk, or, zb } from './logger/export.js';
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, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, MatterbridgeOperationalStateServer, } from './matterbridgeBehaviors.js';
5
- import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, capitalizeFirstLetter, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, } from './matterbridgeEndpointHelpers.js';
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(clusterId, event, payload, log) {
186
- const clusterName = lowercaseFirstLetter(getClusterNameById(clusterId));
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 = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
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(MatterbridgeWindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift), {
663
- type: WindowCovering.WindowCoveringType.Rollershade,
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: true,
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: WindowCovering.EndProductType.RollerShade,
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(position) {
719
- await this.setAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', position, this.log);
720
- await this.setAttribute(WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position, this.log);
721
- this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${position} and targetPositionLiftPercent100ths: ${position}.`);
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, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeOperationalStateServer, } from './matterbridgeBehaviors.js';
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 MatterbridgeWindowCoveringServer.with('Lift', 'PositionAwareLift');
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
- if (attribute === 'attributeList')
387
- return 0xfffb;
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: [],
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.0.3-dev-20250521-42f79f6",
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.3-dev-20250521-42f79f6",
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": "1.4.5-lts.2",
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.0",
1128
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz",
1129
- "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==",
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": "1.4.5-lts.2",
1284
- "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
1285
- "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
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": ">= 6.0.0"
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-dev-20250521-42f79f6",
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": "1.4.5-lts.2",
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"