matterbridge 3.0.2-dev-20250508-7214e17 → 3.0.2-dev-20250510-ae61aa7

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,15 +8,12 @@ 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
- ### Home Assistant
12
-
13
- If you want to run Matterbridge in Home Assistant please use the official add-on https://github.com/Luligu/matterbridge-home-assistant-addon that also has Ingress and side panel.
14
- It is also available the official Matterbridge Home Assistant plugin https://github.com/Luligu/matterbridge-hass.
15
-
16
11
  ## [3.0.2] - 2025-05-??
17
12
 
18
13
  ### Added
19
14
 
15
+ - [virtual] Added virtual devices Restart Matterbridge and Update Matterbridge.
16
+
20
17
  ### Changed
21
18
 
22
19
  - [package]: Updated dependencies.
@@ -42,6 +39,7 @@ It is also available the official Matterbridge Home Assistant plugin https://git
42
39
  - [frontend]: Moved all settings from express to web socket.
43
40
  - [endpoint]: Added OperationalState cluster helper and behavior.
44
41
  - [behaviors]: Added Jest test on MatterbridgeBehaviors.
42
+ - [docker]: Further optimized the dockerfile for the image with tag latest.
45
43
 
46
44
  ### Changed
47
45
 
package/README.md CHANGED
@@ -33,7 +33,7 @@ No complex setup just copy paste the installation scripts.
33
33
 
34
34
  Matterbridge is light weight and run also on slow Linux machine with 512MB of memory.
35
35
 
36
- It runs perfectly on Windows too.
36
+ It runs perfectly on macOS and Windows too.
37
37
 
38
38
  If you like this project and find it useful, please consider giving it a star on GitHub at https://github.com/Luligu/matterbridge and sponsoring it.
39
39
 
package/dist/frontend.js CHANGED
@@ -8,7 +8,7 @@ import express from 'express';
8
8
  import WebSocket, { WebSocketServer } from 'ws';
9
9
  import multer from 'multer';
10
10
  import { AnsiLogger, stringify, debugStringify, CYAN, db, er, nf, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW, nt } from './logger/export.js';
11
- import { createZip, deepCopy, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean } from './utils/export.js';
11
+ import { createZip, isValidArray, isValidNumber, isValidObject, isValidString, isValidBoolean } from './utils/export.js';
12
12
  import { plg } from './matterbridgeTypes.js';
13
13
  import { hasParameter } from './utils/export.js';
14
14
  import { BridgedDeviceBasicInformation, PowerSource } from '@matter/main/clusters';
@@ -33,11 +33,6 @@ export class Frontend {
33
33
  httpServer;
34
34
  httpsServer;
35
35
  webSocketServer;
36
- prevCpus = deepCopy(os.cpus());
37
- lastCpuUsage = 0;
38
- memoryData = [];
39
- memoryInterval;
40
- memoryTimeout;
41
36
  constructor(matterbridge) {
42
37
  this.matterbridge = matterbridge;
43
38
  this.log = new AnsiLogger({ logName: 'Frontend', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
@@ -452,13 +447,27 @@ export class Frontend {
452
447
  }
453
448
  async stop() {
454
449
  if (this.httpServer) {
455
- this.httpServer.close();
450
+ this.httpServer.close((error) => {
451
+ if (error) {
452
+ this.log.error(`Error closing http server: ${error}`);
453
+ }
454
+ else {
455
+ this.log.debug('Http server closed successfully');
456
+ }
457
+ });
456
458
  this.httpServer.removeAllListeners();
457
459
  this.httpServer = undefined;
458
460
  this.log.debug('Frontend http server closed successfully');
459
461
  }
460
462
  if (this.httpsServer) {
461
- this.httpsServer.close();
463
+ this.httpsServer.close((error) => {
464
+ if (error) {
465
+ this.log.error(`Error closing https server: ${error}`);
466
+ }
467
+ else {
468
+ this.log.debug('Https server closed successfully');
469
+ }
470
+ });
462
471
  this.httpsServer.removeAllListeners();
463
472
  this.httpsServer = undefined;
464
473
  this.log.debug('Frontend https server closed successfully');
@@ -1203,7 +1212,7 @@ export class Frontend {
1203
1212
  }
1204
1213
  else if (data.method === '/api/action') {
1205
1214
  if (!isValidString(data.params.plugin, 5) || !isValidString(data.params.action, 1)) {
1206
- client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter command in /api/action' }));
1215
+ client.send(JSON.stringify({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter in /api/action' }));
1207
1216
  return;
1208
1217
  }
1209
1218
  const plugin = this.matterbridge.plugins.get(data.params.plugin);
@@ -1446,10 +1455,10 @@ export class Frontend {
1446
1455
  }
1447
1456
  if (isValidArray(config.blackList, 1)) {
1448
1457
  if (select === 'serial' && config.blackList.includes(data.params.serial)) {
1449
- config.blackList = config.blackList.filter((serial) => serial !== data.params.serial);
1458
+ config.blackList = config.blackList.filter((item) => item !== data.params.serial);
1450
1459
  }
1451
1460
  else if (select === 'name' && config.blackList.includes(data.params.name)) {
1452
- config.blackList = config.blackList.filter((name) => name !== data.params.name);
1461
+ config.blackList = config.blackList.filter((item) => item !== data.params.name);
1453
1462
  }
1454
1463
  }
1455
1464
  if (plugin.platform)
@@ -1484,10 +1493,10 @@ export class Frontend {
1484
1493
  }
1485
1494
  if (isValidArray(config.whiteList, 1)) {
1486
1495
  if (select === 'serial' && config.whiteList.includes(data.params.serial)) {
1487
- config.whiteList = config.whiteList.filter((serial) => serial !== data.params.serial);
1496
+ config.whiteList = config.whiteList.filter((item) => item !== data.params.serial);
1488
1497
  }
1489
1498
  else if (select === 'name' && config.whiteList.includes(data.params.name)) {
1490
- config.whiteList = config.whiteList.filter((name) => name !== data.params.name);
1499
+ config.whiteList = config.whiteList.filter((item) => item !== data.params.name);
1491
1500
  }
1492
1501
  }
1493
1502
  if (isValidArray(config.blackList)) {
@@ -12,10 +12,12 @@ import { DeviceManager } from './deviceManager.js';
12
12
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
13
13
  import { bridge } from './matterbridgeDeviceTypes.js';
14
14
  import { Frontend } from './frontend.js';
15
- import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
15
+ import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Endpoint, } from '@matter/main';
16
16
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
17
+ import { OnOffPlugInUnitDevice } from '@matter/main/devices/on-off-plug-in-unit';
17
18
  import { AggregatorEndpoint } from '@matter/main/endpoints';
18
19
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
20
+ import { OnOffBaseServer } from '@matter/main/behaviors/on-off';
19
21
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
20
22
  const plg = '\u001B[38;5;33m';
21
23
  const dev = '\u001B[38;5;79m';
@@ -1519,6 +1521,30 @@ export class Matterbridge extends EventEmitter {
1519
1521
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1520
1522
  serverNode.lifecycle.online.on(async () => {
1521
1523
  this.log.notice(`Server node for ${storeId} is online`);
1524
+ if (!hasParameter('novirtual') && this.bridgeMode === 'bridge') {
1525
+ this.log.notice(`Creating virtual devices for server node ${storeId}`);
1526
+ const virtualRestart = new Endpoint(OnOffPlugInUnitDevice.with(BridgedDeviceBasicInformationServer), { id: 'Restart Matterbridge', bridgedDeviceBasicInformation: { nodeLabel: 'Restart' } });
1527
+ virtualRestart.events.onOff.onOff$Changed.on(async (value) => {
1528
+ if (value) {
1529
+ await virtualRestart.setStateOf(OnOffBaseServer, { onOff: false });
1530
+ if (this.restartMode === '')
1531
+ this.restartProcess();
1532
+ else
1533
+ this.shutdownProcess();
1534
+ }
1535
+ });
1536
+ await this.aggregatorNode?.add(virtualRestart);
1537
+ await virtualRestart.setStateOf(OnOffBaseServer, { onOff: false });
1538
+ const virtualUpdate = new Endpoint(OnOffPlugInUnitDevice.with(BridgedDeviceBasicInformationServer), { id: 'Update Matterbridge', bridgedDeviceBasicInformation: { nodeLabel: 'Update' } });
1539
+ virtualUpdate.events.onOff.onOff$Changed.on(async (value) => {
1540
+ if (value) {
1541
+ await virtualUpdate.setStateOf(OnOffBaseServer, { onOff: false });
1542
+ this.updateProcess();
1543
+ }
1544
+ });
1545
+ await this.aggregatorNode?.add(virtualUpdate);
1546
+ await virtualUpdate.setStateOf(OnOffBaseServer, { onOff: false });
1547
+ }
1522
1548
  if (!serverNode.lifecycle.isCommissioned) {
1523
1549
  this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission ...`);
1524
1550
  const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
@@ -11,6 +11,7 @@ import { OperationalState } from '@matter/main/clusters/operational-state';
11
11
  import { ModeBase } from '@matter/main/clusters/mode-base';
12
12
  import { RvcRunMode } from '@matter/main/clusters/rvc-run-mode';
13
13
  import { RvcOperationalState } from '@matter/main/clusters/rvc-operational-state';
14
+ import { ServiceArea } from '@matter/main/clusters/service-area';
14
15
  import { IdentifyServer } from '@matter/main/behaviors/identify';
15
16
  import { OnOffServer } from '@matter/main/behaviors/on-off';
16
17
  import { LevelControlServer } from '@matter/main/behaviors/level-control';
@@ -28,7 +29,6 @@ import { RvcRunModeServer } from '@matter/main/behaviors/rvc-run-mode';
28
29
  import { RvcCleanModeServer } from '@matter/main/behaviors/rvc-clean-mode';
29
30
  import { RvcOperationalStateServer } from '@matter/main/behaviors/rvc-operational-state';
30
31
  import { ServiceAreaServer } from '@matter/main/behaviors/service-area';
31
- import { ServiceArea } from '@matter/main/clusters/service-area';
32
32
  export class MatterbridgeServerDevice {
33
33
  log;
34
34
  commandHandler;
@@ -2,7 +2,7 @@ import { AnsiLogger, BLUE, CYAN, YELLOW, db, debugStringify, er, hk, or, zb } fr
2
2
  import { bridgedNode } from './matterbridgeDeviceTypes.js';
3
3
  import { isValidNumber, isValidObject, isValidString } from './utils/export.js';
4
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, } from './matterbridgeEndpointHelpers.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';
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';
@@ -216,6 +216,9 @@ export class MatterbridgeEndpoint extends Endpoint {
216
216
  async executeCommandHandler(command, request) {
217
217
  await this.commandHandler.executeHandler(command, { request });
218
218
  }
219
+ async invokeBehaviorCommand(cluster, command, params) {
220
+ await invokeBehaviorCommand(this, cluster, command, params);
221
+ }
219
222
  addRequiredClusterServers() {
220
223
  addRequiredClusterServers(this);
221
224
  return this;
@@ -224,6 +224,21 @@ export function getBehavior(endpoint, cluster) {
224
224
  }
225
225
  return behavior;
226
226
  }
227
+ export async function invokeBehaviorCommand(endpoint, cluster, command, params) {
228
+ const behaviorId = getBehavior(endpoint, cluster)?.id;
229
+ if (!behaviorId) {
230
+ endpoint.log.error(`invokeBehaviorCommand error: command ${hk}${command}${er} not found on endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er}`);
231
+ return;
232
+ }
233
+ await endpoint.act((agent) => {
234
+ const behavior = agent[behaviorId];
235
+ if (!(command in behavior) || typeof behavior[command] !== 'function') {
236
+ endpoint.log.error(`invokeBehaviorCommand error: command ${hk}${command}${er} not found on agent for endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er}`);
237
+ return;
238
+ }
239
+ behavior[command](params);
240
+ });
241
+ }
227
242
  export function addRequiredClusterServers(endpoint) {
228
243
  const requiredServerList = [];
229
244
  endpoint.log.debug(`addRequiredClusterServers for ${CYAN}${endpoint.maybeId}${db}`);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.0.2-dev-20250508-7214e17",
3
+ "version": "3.0.2-dev-20250510-ae61aa7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge",
9
- "version": "3.0.2-dev-20250508-7214e17",
9
+ "version": "3.0.2-dev-20250510-ae61aa7",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@matter/main": "0.13.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.0.2-dev-20250508-7214e17",
3
+ "version": "3.0.2-dev-20250510-ae61aa7",
4
4
  "description": "Matterbridge plugin manager for Matter",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",