@matterbridge/core 3.10.14-dev-20261010-607ee18 → 3.10.14-dev-20261013-e87a0c8

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.
Files changed (77) hide show
  1. package/dist/backendExpress.js +14 -9
  2. package/dist/backendWsServer.d.ts +1 -5
  3. package/dist/backendWsServer.js +4 -35
  4. package/dist/behaviors/bindingServer.d.ts +1 -1
  5. package/dist/behaviors/booleanStateConfigurationServer.js +2 -1
  6. package/dist/behaviors/cameraAvSettingsUserLevelManagementServer.d.ts +1 -1
  7. package/dist/behaviors/cameraAvSettingsUserLevelManagementServer.js +2 -1
  8. package/dist/behaviors/cameraAvStreamManagementServer.d.ts +1 -2
  9. package/dist/behaviors/cameraAvStreamManagementServer.js +2 -1
  10. package/dist/behaviors/chimeServer.js +2 -1
  11. package/dist/behaviors/deviceEnergyManagementServer.js +2 -1
  12. package/dist/behaviors/fanControlServer.js +2 -1
  13. package/dist/behaviors/pumpConfigurationAndControlServer.js +2 -1
  14. package/dist/behaviors/smokeCoAlarmServer.js +2 -1
  15. package/dist/behaviors/temperatureAlarmServer.js +2 -1
  16. package/dist/behaviors/thermostatServer.js +2 -1
  17. package/dist/behaviors/valveConfigurationAndControlServer.js +2 -1
  18. package/dist/behaviors/webRtcTransportProviderServer.js +3 -1
  19. package/dist/chipTests.js +3 -2
  20. package/dist/cli.js +36 -4
  21. package/dist/cliEmitter.d.ts +0 -3
  22. package/dist/deviceManager.d.ts +0 -1
  23. package/dist/deviceManager.js +15 -47
  24. package/dist/devices/airConditioner.d.ts +1 -1
  25. package/dist/devices/audioDoorbell.d.ts +1 -1
  26. package/dist/devices/audioDoorbell.js +1 -1
  27. package/dist/devices/batteryStorage.d.ts +1 -1
  28. package/dist/devices/camera.d.ts +1 -2
  29. package/dist/devices/camera.js +1 -1
  30. package/dist/devices/closure.d.ts +2 -2
  31. package/dist/devices/closure.js +1 -1
  32. package/dist/devices/closurePanel.js +1 -1
  33. package/dist/devices/cooktop.d.ts +2 -1
  34. package/dist/devices/dishwasher.d.ts +2 -2
  35. package/dist/devices/dishwasher.js +2 -1
  36. package/dist/devices/electricalUtilityMeter.d.ts +1 -1
  37. package/dist/devices/electricalUtilityMeter.js +1 -1
  38. package/dist/devices/evse.d.ts +1 -1
  39. package/dist/devices/evse.js +2 -1
  40. package/dist/devices/extractorHood.d.ts +1 -1
  41. package/dist/devices/floodlightCamera.d.ts +1 -1
  42. package/dist/devices/floodlightCamera.js +1 -1
  43. package/dist/devices/heatPump.d.ts +1 -1
  44. package/dist/devices/intercom.d.ts +1 -1
  45. package/dist/devices/intercom.js +1 -1
  46. package/dist/devices/irrigationSystem.d.ts +1 -1
  47. package/dist/devices/laundryDryer.d.ts +2 -2
  48. package/dist/devices/laundryDryer.js +2 -1
  49. package/dist/devices/laundryWasher.d.ts +2 -2
  50. package/dist/devices/laundryWasher.js +2 -1
  51. package/dist/devices/microwaveOven.d.ts +2 -2
  52. package/dist/devices/microwaveOven.js +2 -1
  53. package/dist/devices/oven.d.ts +2 -1
  54. package/dist/devices/refrigerator.d.ts +2 -1
  55. package/dist/devices/roboticVacuumCleaner.d.ts +1 -1
  56. package/dist/devices/snapshotCamera.d.ts +1 -1
  57. package/dist/devices/snapshotCamera.js +1 -1
  58. package/dist/devices/solarPower.d.ts +1 -1
  59. package/dist/devices/temperatureControl.js +2 -1
  60. package/dist/devices/videoDoorbell.d.ts +1 -1
  61. package/dist/devices/videoDoorbell.js +1 -1
  62. package/dist/devices/waterHeater.d.ts +1 -1
  63. package/dist/frontend.d.ts +1 -8
  64. package/dist/frontend.js +23 -82
  65. package/dist/matterNode.d.ts +4 -2
  66. package/dist/matterNode.js +116 -67
  67. package/dist/matterNodeHelpers.d.ts +3 -1
  68. package/dist/matterNodeHelpers.js +48 -0
  69. package/dist/matterbridge.js +14 -24
  70. package/dist/matterbridgeDeviceTypes.d.ts +1 -1
  71. package/dist/matterbridgeEndpoint.js +43 -29
  72. package/dist/matterbridgePlatform.js +39 -3
  73. package/dist/pluginManager.js +10 -1
  74. package/dist/runners/threadBackend.js +20 -6
  75. package/dist/runners/threadRootNode.js +32 -12
  76. package/dist/runners/threadTracker.js +23 -6
  77. package/package.json +5 -5
@@ -10,11 +10,11 @@ import { BridgedDeviceBasicInformationServer } from '@matter/node/behaviors/brid
10
10
  import { PowerSourceServer } from '@matter/node/behaviors/power-source';
11
11
  import { AggregatorEndpoint } from '@matter/node/endpoints/aggregator';
12
12
  import { MdnsService, PaseClient } from '@matter/protocol';
13
- import { DeviceTypeId, EndpointNumber, VendorId } from '@matter/types';
14
13
  import { PowerSource } from '@matter/types/clusters/power-source';
14
+ import { DeviceTypeId, EndpointNumber, VendorId } from '@matter/types/datatype';
15
15
  import { BroadcastServer } from '@matterbridge/thread/server';
16
16
  import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from '@matterbridge/types';
17
- import { getIntParameter, getParameter, hasParameter } from '@matterbridge/utils/cli';
17
+ import { getIntParameter, getParameter, hasAnyParameter, hasParameter } from '@matterbridge/utils/cli';
18
18
  import { copyDirectory } from '@matterbridge/utils/copy-dir';
19
19
  import { getErrorMessage, inspectError } from '@matterbridge/utils/error';
20
20
  import { logModuleLoaded } from '@matterbridge/utils/loader';
@@ -22,10 +22,9 @@ import { isValidInteger, isValidString, parseVersionString } from '@matterbridge
22
22
  import { fireAndForget, wait, withTimeout } from '@matterbridge/utils/wait';
23
23
  import { AnsiLogger, BLUE, CYAN, db, debugStringify, er, nf, or, zb } from 'node-ansi-logger';
24
24
  import { NodeStorageManager } from 'node-persist-manager';
25
- import { toBaseDevice } from './deviceManager.js';
26
25
  import { addVirtualDevice } from './helpers.js';
27
26
  import { bridge } from './matterbridgeDeviceTypes.js';
28
- import { deleteAdvertisingNode, getServerNodeData, setAdvertisingNode } from './matterNodeHelpers.js';
27
+ import { deleteAdvertisingNode, getServerNodeData, setAdvertisingNode, toBaseDevice } from './matterNodeHelpers.js';
29
28
  import { PluginManager } from './pluginManager.js';
30
29
  logModuleLoaded('MatterNode');
31
30
  export class MatterNode extends EventEmitter {
@@ -59,14 +58,16 @@ export class MatterNode extends EventEmitter {
59
58
  pluginManager;
60
59
  dependantMatterNodes = new Map();
61
60
  server;
62
- debug = hasParameter('debug') || hasParameter('verbose');
63
- verbose = hasParameter('verbose');
61
+ debug = hasAnyParameter('debug', 'verbose', 'debug-node', 'verbose-node');
62
+ verbose = hasAnyParameter('verbose', 'verbose-node');
64
63
  constructor(matterbridge, pluginName, device) {
65
64
  super();
66
65
  this.matterbridge = matterbridge;
67
66
  this.pluginName = pluginName;
68
67
  this.device = device;
69
68
  this.log.logNameColor = '\x1b[38;5;65m';
69
+ this.log.logLevel = matterbridge.logLevel;
70
+ this.matterLog.logLevel = matterbridge.matterLogLevel;
70
71
  if (this.debug)
71
72
  this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loading...`);
72
73
  this.storeId = this.device?.deviceName ? this.device.deviceName.replace(/[ .]/g, '') : (this.pluginName ?? 'Matterbridge');
@@ -75,12 +76,20 @@ export class MatterNode extends EventEmitter {
75
76
  this.discriminator = matterbridge.discriminator;
76
77
  this.mdnsInterface = matterbridge.mdnsInterface;
77
78
  this.server = new BroadcastServer('matter', this.log);
78
- this.server.on('broadcast_message', this.msgHandler.bind(this));
79
+ this.server.on('broadcast_message', (msg) => fireAndForget(this.msgHandler(msg), this.log, 'Broadcast message handler'));
79
80
  if (this.verbose)
80
81
  this.log.debug(`BroadcastServer is ready`);
81
- this.pluginManager = new PluginManager(this.matterbridge);
82
- this.pluginManager.logLevel = this.debug ? "debug" : "info";
82
+ const injectMatterbridge = {
83
+ ...this.matterbridge,
84
+ getPlatformMatterbridge: () => this.matterbridge,
85
+ addBridgedEndpoint: this.addBridgedEndpoint.bind(this),
86
+ removeBridgedEndpoint: this.removeBridgedEndpoint.bind(this),
87
+ removeAllBridgedEndpoints: this.removeAllBridgedEndpoints.bind(this),
88
+ addVirtualEndpoint: this.addVirtualEndpoint.bind(this),
89
+ };
90
+ this.pluginManager = new PluginManager(injectMatterbridge);
83
91
  this.pluginManager.server.close();
92
+ this.pluginManager.logLevel = this.debug ? "debug" : "info";
84
93
  if (this.verbose)
85
94
  this.log.debug(`PluginManager is ready`);
86
95
  this.environment.vars.set('log.level', MatterLogLevel.DEBUG);
@@ -88,6 +97,7 @@ export class MatterNode extends EventEmitter {
88
97
  this.environment.vars.set('path.root', path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_DIR));
89
98
  this.environment.vars.set('runtime.signals', false);
90
99
  this.environment.vars.set('runtime.exitcode', false);
100
+ this.environment.vars.set('endpoint.validation', 'strict');
91
101
  if (this.mdnsInterface)
92
102
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
93
103
  if (this.verbose)
@@ -106,10 +116,10 @@ export class MatterNode extends EventEmitter {
106
116
  if (this.verbose)
107
117
  this.log.debug(`Matter logLevel: ${levels[Logger.level]} fileLogger: ${matterbridge.matterFileLogger}.`);
108
118
  if (this.debug)
109
- this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loaded`);
119
+ this.log.debug(`MatterNode for ${this.storeId} loaded`);
110
120
  }
111
121
  async msgHandler(msg) {
112
- if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'matter')) {
122
+ if (this.server.isWorkerRequest(msg)) {
113
123
  if (this.verbose)
114
124
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
115
125
  switch (msg.type) {
@@ -159,6 +169,8 @@ export class MatterNode extends EventEmitter {
159
169
  switch (msg.type) {
160
170
  case 'get_log_level':
161
171
  case 'set_log_level':
172
+ case 'matterbridge_platform':
173
+ case 'matterbridge_shared':
162
174
  break;
163
175
  default:
164
176
  if (this.verbose)
@@ -183,11 +195,12 @@ export class MatterNode extends EventEmitter {
183
195
  this.log.debug(`Destroyed MatterNode instance for ${this.storeId}`);
184
196
  }
185
197
  async create() {
186
- this.log.info('Creating Matter node...');
198
+ this.log.info(`Creating Matter node for ${this.storeId}...`);
187
199
  const { result } = await this.server.fetch({ type: 'matterbridge_matterdata', src: this.server.name, dst: 'matterbridge' });
188
200
  this.port = result.port;
189
201
  this.passcode = result.passcode;
190
202
  this.discriminator = result.discriminator;
203
+ this.log.debug(`Loaded Matter node parameters for ${this.storeId}: port ${CYAN}${this.port}${db} passcode ${CYAN}${this.passcode}${db} discriminator ${CYAN}${this.discriminator}${db}`);
191
204
  await this.startMatterStorage();
192
205
  this.pluginManager.matterbridge.nodeStorage = new NodeStorageManager({
193
206
  dir: path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR),
@@ -204,7 +217,16 @@ export class MatterNode extends EventEmitter {
204
217
  this.emit('ready', this.device.deviceName.replace(/[ .]/g, ''));
205
218
  return;
206
219
  }
207
- if (!this.pluginName) {
220
+ if (this.pluginName) {
221
+ this.log.debug(`Creating MatterNode instance for plugin ${CYAN}${this.pluginName}${db}...`);
222
+ this.log.debug(`Loading plugin ${CYAN}${this.pluginName}${db}...`);
223
+ await this.pluginManager.load(this.pluginName);
224
+ this.pluginStatusUpdate(this.pluginName, { loaded: this.pluginManager.get(this.pluginName)?.loaded });
225
+ this.log.debug(`Loaded plugin ${CYAN}${this.pluginName}${db}`);
226
+ this.log.debug(`Created MatterNode instance for plugin ${CYAN}${this.pluginName}${db}`);
227
+ this.emit('ready', this.pluginName);
228
+ }
229
+ else {
208
230
  this.log.debug('Creating MatterNode instance for all plugins...');
209
231
  await this.createMatterbridgeServerNode();
210
232
  this.log.debug('Loading all plugins...');
@@ -213,25 +235,20 @@ export class MatterNode extends EventEmitter {
213
235
  loadPromises.push(this.pluginManager.load(plugin));
214
236
  }
215
237
  await Promise.all(loadPromises);
238
+ for (const plugin of this.pluginManager.array().filter((p) => p.enabled)) {
239
+ this.pluginStatusUpdate(plugin.name, { loaded: plugin.loaded });
240
+ }
216
241
  this.log.debug('Loaded all plugins');
217
242
  this.log.debug('Created MatterNode instance for all plugins');
218
243
  this.emit('ready', 'Matterbridge');
219
244
  }
220
- else {
221
- this.log.debug(`Creating MatterNode instance for plugin ${CYAN}${this.pluginName}${db}...`);
222
- this.log.debug(`Loading plugin ${CYAN}${this.pluginName}${db}...`);
223
- await this.pluginManager.load(this.pluginName);
224
- this.log.debug(`Loaded plugin ${CYAN}${this.pluginName}${db}`);
225
- this.log.debug(`Created MatterNode instance for plugin ${CYAN}${this.pluginName}${db}`);
226
- this.emit('ready', this.pluginName);
227
- }
228
- this.log.info('Created Matter node');
245
+ this.log.info(`Created Matter node for ${this.storeId}`);
229
246
  await this.yieldToNode();
230
247
  }
231
248
  async start() {
232
249
  if (!this.serverNode && !this.pluginName)
233
- throw new Error('Matter server node not created yet. Call create() first.');
234
- this.log.info('Starting MatterNode...');
250
+ throw new Error(`Matter server node for ${this.storeId} not created yet. Call create() first.`);
251
+ this.log.info(`Starting MatterNode for ${this.storeId}...`);
235
252
  if (this.pluginName && this.device?.deviceName) {
236
253
  this.log.debug(`Starting MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
237
254
  await this.startServerNode();
@@ -239,43 +256,56 @@ export class MatterNode extends EventEmitter {
239
256
  return;
240
257
  }
241
258
  if (!this.pluginName) {
242
- this.log.debug('Starting all plugins...');
259
+ this.log.debug(`Starting all plugins for ${this.storeId}...`);
243
260
  const startPromises = [];
244
261
  for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.loaded)) {
245
262
  startPromises.push(this.pluginManager.start(plugin, 'Starting MatterNode'));
246
263
  }
247
264
  await Promise.all(startPromises);
248
- this.log.debug('Started all plugins');
249
- this.log.debug('Starting MatterNode for all plugins...');
265
+ for (const plugin of this.pluginManager.array().filter((p) => p.enabled)) {
266
+ this.pluginStatusUpdate(plugin.name, { started: plugin.started, registeredDevices: plugin.registeredDevices });
267
+ }
268
+ this.log.debug(`Started all plugins for ${this.storeId}`);
269
+ this.log.debug(`Starting MatterNode for ${this.storeId}...`);
250
270
  await this.startServerNode();
251
- this.log.debug('Started MatterNode for all plugins');
252
- this.log.debug('Configuring all plugins...');
271
+ this.log.debug(`Started MatterNode for ${this.storeId}`);
272
+ this.log.debug(`Configuring all plugins for ${this.storeId}...`);
253
273
  const configurePromises = [];
254
274
  for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.started)) {
255
275
  configurePromises.push(this.pluginManager.configure(plugin));
256
276
  }
257
277
  await Promise.all(configurePromises);
258
- this.log.debug('Configured all plugins');
278
+ for (const plugin of this.pluginManager.array().filter((p) => p.enabled)) {
279
+ this.pluginStatusUpdate(plugin.name, { configured: plugin.configured });
280
+ }
281
+ this.log.debug(`Configured all plugins for ${this.storeId}`);
259
282
  }
260
283
  else {
284
+ this.log.debug(`Starting plugin ${this.pluginName}...`);
261
285
  await this.pluginManager.start(this.pluginName, 'Starting MatterNode');
262
- this.log.debug(`Starting MatterNode for plugin ${this.pluginName}...`);
286
+ const plugin = this.pluginManager.get(this.pluginName);
287
+ this.pluginStatusUpdate(this.pluginName, { started: plugin?.started, registeredDevices: plugin?.registeredDevices });
288
+ this.log.debug(`Started plugin ${this.pluginName}...`);
289
+ this.log.debug(`Starting MatterNode for plugin ${this.storeId}...`);
263
290
  await this.startServerNode();
264
- this.log.debug(`Started MatterNode for plugin ${this.pluginName}`);
291
+ this.log.debug(`Started MatterNode for plugin ${this.storeId}`);
292
+ this.log.debug(`Configuring plugin ${this.pluginName}...`);
265
293
  await this.pluginManager.configure(this.pluginName);
294
+ this.pluginStatusUpdate(this.pluginName, { configured: this.pluginManager.get(this.pluginName)?.configured });
295
+ this.log.debug(`Configured plugin ${this.pluginName}...`);
266
296
  }
267
- this.log.debug(`Starting dependant MatterNodes...`);
297
+ this.log.debug(`Starting dependant MatterNodes for ${this.storeId}...`);
268
298
  for (const dependantMatterNode of this.dependantMatterNodes.values()) {
269
299
  await dependantMatterNode.start();
270
300
  }
271
- this.log.debug(`Started dependant MatterNodes`);
272
- this.log.info('Started MatterNode');
301
+ this.log.debug(`Started dependant MatterNodes for ${this.storeId}`);
302
+ this.log.info(`Started MatterNode for ${this.storeId}`);
273
303
  await this.yieldToNode();
274
304
  }
275
305
  async stop() {
276
306
  if (!this.serverNode)
277
- throw new Error('Matter server node not created yet. Call create() first.');
278
- this.log.info('Stopping MatterNode...');
307
+ throw new Error(`Matter server node for ${this.storeId} not created yet. Call create() first.`);
308
+ this.log.info(`Stopping MatterNode for ${this.storeId}...`);
279
309
  if (this.pluginName && this.device?.deviceName) {
280
310
  this.log.debug(`Stopping MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
281
311
  await this.stopServerNode();
@@ -284,34 +314,34 @@ export class MatterNode extends EventEmitter {
284
314
  await this.stopMatterStorage();
285
315
  await this.destroy(false);
286
316
  this.log.debug(`Stopped MatterNode for server device ${this.pluginName}:${this.device.deviceName}`);
287
- this.log.info('Stopped MatterNode');
317
+ this.log.info(`Stopped MatterNode for ${this.storeId}`);
288
318
  await this.yieldToNode();
289
319
  return;
290
320
  }
291
321
  if (!this.pluginName) {
292
- this.log.debug('Stopping all plugins...');
322
+ this.log.debug(`Stopping all plugins for ${this.storeId}...`);
293
323
  const shutdownPromises = [];
294
324
  for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.started)) {
295
325
  shutdownPromises.push(this.pluginManager.shutdown(plugin, 'Stopping MatterNode'));
296
326
  }
297
327
  await Promise.all(shutdownPromises);
298
- this.log.debug('Stopped all plugins');
328
+ this.log.debug(`Stopped all plugins for ${this.storeId}`);
299
329
  }
300
330
  else {
301
331
  this.log.debug(`Stopping plugin ${this.pluginName}...`);
302
332
  await this.pluginManager.shutdown(this.pluginName, 'Stopping MatterNode');
303
333
  this.log.debug(`Stopped plugin ${this.pluginName}`);
304
334
  }
305
- this.log.debug(`Stopping dependant MatterNodes...`);
335
+ this.log.debug(`Stopping dependant MatterNodes for ${this.storeId}...`);
306
336
  for (const dependantMatterNode of this.dependantMatterNodes.values()) {
307
337
  await dependantMatterNode.stop();
308
338
  }
309
- this.log.debug(`Stopped dependant MatterNodes`);
339
+ this.log.debug(`Stopped dependant MatterNodes for ${this.storeId}`);
310
340
  await this.stopServerNode();
311
341
  this.serverNode = undefined;
312
342
  this.aggregatorNode = undefined;
313
343
  await this.stopMatterStorage();
314
- this.log.info('Stopped MatterNode');
344
+ this.log.info(`Stopped MatterNode for ${this.storeId}`);
315
345
  await this.yieldToNode();
316
346
  }
317
347
  createDestinationMatterLogger() {
@@ -375,23 +405,28 @@ export class MatterNode extends EventEmitter {
375
405
  const storageManager = await this.matterStorageService.open(storeId);
376
406
  const storageContext = storageManager.createContext('persist');
377
407
  const random = randomBytes(8).toString('hex');
378
- await storageContext.set('storeId', storeId);
379
- await storageContext.set('deviceName', deviceName);
380
- await storageContext.set('deviceType', deviceType);
381
- await storageContext.set('vendorId', vendorId);
382
- await storageContext.set('vendorName', vendorName.slice(0, 32));
383
- await storageContext.set('productId', productId);
384
- await storageContext.set('productName', productName.slice(0, 32));
385
- await storageContext.set('productLabel', productLabel.replace(vendorName, '').trim().slice(0, 64));
386
- await storageContext.set('productUrl', isValidString(productUrl, 8, 256) && productUrl.startsWith('https://') ? productUrl : 'https://matterbridge.io');
387
- await storageContext.set('nodeLabel', deviceName.slice(0, 32));
388
- await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
389
- await storageContext.set('uniqueId', await storageContext.get('uniqueId', uniqueId ? uniqueId.slice(0, 32) : 'UI' + random));
390
- await storageContext.set('softwareVersion', isValidInteger(softwareVersion, 0, UINT32_MAX) ? softwareVersion : 1);
391
- await storageContext.set('softwareVersionString', isValidString(softwareVersionString, 1, 64) ? softwareVersionString : '1.0.0');
392
- await storageContext.set('hardwareVersion', isValidInteger(hardwareVersion, 0, UINT16_MAX) ? hardwareVersion : 1);
393
- await storageContext.set('hardwareVersionString', isValidString(hardwareVersionString, 1, 64) ? hardwareVersionString : '1.0.0');
394
- await storageContext.set('configurationVersion', isValidInteger(configurationVersion, 1, UINT32_MAX) ? configurationVersion : 1);
408
+ async function setIfDifferent(key, value) {
409
+ if (!(await storageContext.has(key)) || (await storageContext.get(key)) !== value) {
410
+ await storageContext.set(key, value);
411
+ }
412
+ }
413
+ await setIfDifferent('storeId', storeId);
414
+ await setIfDifferent('deviceName', deviceName);
415
+ await setIfDifferent('deviceType', deviceType);
416
+ await setIfDifferent('vendorId', vendorId);
417
+ await setIfDifferent('vendorName', vendorName.slice(0, 32));
418
+ await setIfDifferent('productId', productId);
419
+ await setIfDifferent('productName', productName.slice(0, 32));
420
+ await setIfDifferent('productLabel', productLabel.replace(vendorName, '').trim().slice(0, 64));
421
+ await setIfDifferent('productUrl', isValidString(productUrl, 8, 256) && productUrl.startsWith('https://') ? productUrl : 'https://matterbridge.io');
422
+ await setIfDifferent('nodeLabel', deviceName.slice(0, 32));
423
+ await setIfDifferent('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
424
+ await setIfDifferent('uniqueId', await storageContext.get('uniqueId', uniqueId ? uniqueId.slice(0, 32) : 'UI' + random));
425
+ await setIfDifferent('softwareVersion', isValidInteger(softwareVersion, 0, UINT32_MAX) ? softwareVersion : 1);
426
+ await setIfDifferent('softwareVersionString', isValidString(softwareVersionString, 1, 64) ? softwareVersionString : '1.0.0');
427
+ await setIfDifferent('hardwareVersion', isValidInteger(hardwareVersion, 0, UINT16_MAX) ? hardwareVersion : 1);
428
+ await setIfDifferent('hardwareVersionString', isValidString(hardwareVersionString, 1, 64) ? hardwareVersionString : '1.0.0');
429
+ await setIfDifferent('configurationVersion', isValidInteger(configurationVersion, 1, UINT32_MAX) ? configurationVersion : 1);
395
430
  this.log.debug(`Created server node storage context "${storeId}.persist" for ${storeId}:`);
396
431
  this.log.debug(`- storeId: ${await storageContext.get('storeId')}`);
397
432
  this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
@@ -626,7 +661,7 @@ export class MatterNode extends EventEmitter {
626
661
  async createMatterbridgeServerNode() {
627
662
  this.log.debug(`Creating ${plg}Matterbridge${db} server node...`);
628
663
  this.matterStorageContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId, parseVersionString(this.matterbridge.matterbridgeVersion), this.matterbridge.matterbridgeVersion, parseVersionString(this.matterbridge.systemInformation.osRelease), this.matterbridge.systemInformation.osRelease, 'Matter Bridge', 'https://matterbridge.io', 1);
629
- this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
664
+ this.serverNode = await this.createServerNode(this.port, this.passcode, this.discriminator);
630
665
  this.aggregatorNode = await this.createAggregatorNode();
631
666
  this.log.debug(`Adding ${plg}Matterbridge${db} aggregator node...`);
632
667
  await this.serverNode.add(this.aggregatorNode);
@@ -647,7 +682,7 @@ export class MatterNode extends EventEmitter {
647
682
  plugin.locked = true;
648
683
  this.log.debug(`Creating accessory plugin ${plg}${plugin.name}${db} server node...`);
649
684
  this.matterStorageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), VendorId(device.vendorId), device.vendorName, device.productId, device.productName, device.serialNumber, device.uniqueId, device.softwareVersion, device.softwareVersionString, device.hardwareVersion, device.hardwareVersionString, device.productLabel, device.productUrl, device.configurationVersion);
650
- this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
685
+ this.serverNode = await this.createServerNode(this.port, this.passcode, this.discriminator);
651
686
  this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node...`);
652
687
  await this.serverNode.add(device);
653
688
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node`);
@@ -668,7 +703,7 @@ export class MatterNode extends EventEmitter {
668
703
  plugin.locked = true;
669
704
  this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} server node...`);
670
705
  this.matterStorageContext = await this.createServerNodeContext(plugin.name, plugin.description, this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, undefined, undefined, parseVersionString(this.matterbridge.matterbridgeVersion), this.matterbridge.matterbridgeVersion, parseVersionString(this.matterbridge.systemInformation.osRelease), this.matterbridge.systemInformation.osRelease, plugin.description, plugin.homepage ?? 'https://matterbridge.io', plugin.configurationVersion);
671
- this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
706
+ this.serverNode = await this.createServerNode(this.port, this.passcode, this.discriminator);
672
707
  this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
673
708
  this.aggregatorNode = await this.createAggregatorNode();
674
709
  this.log.debug(`Adding dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
@@ -690,7 +725,7 @@ export class MatterNode extends EventEmitter {
690
725
  if (device.mode === 'server' && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
691
726
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
692
727
  this.matterStorageContext = await this.createServerNodeContext(device.deviceName.replace(/[ .]/g, ''), device.deviceName, DeviceTypeId(device.deviceType), VendorId(device.vendorId), device.vendorName, device.productId, device.productName, device.serialNumber, device.uniqueId, device.softwareVersion, device.softwareVersionString, device.hardwareVersion, device.hardwareVersionString, device.productLabel, device.productUrl, device.configurationVersion);
693
- this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
728
+ this.serverNode = await this.createServerNode(this.port, this.passcode, this.discriminator);
694
729
  this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node...`);
695
730
  await this.serverNode.add(device);
696
731
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
@@ -708,9 +743,6 @@ export class MatterNode extends EventEmitter {
708
743
  try {
709
744
  this.log.debug(`Creating MatterNode for device ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
710
745
  const matterNode = new MatterNode(this.matterbridge, pluginName, device);
711
- matterNode.port = this.port ? this.port++ : undefined;
712
- matterNode.passcode = this.passcode ? this.passcode++ : undefined;
713
- matterNode.discriminator = this.discriminator ? this.discriminator++ : undefined;
714
746
  this.dependantMatterNodes.set(device.id, matterNode);
715
747
  await matterNode.create();
716
748
  this.log.debug(`Created MatterNode for device ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})`);
@@ -1002,6 +1034,23 @@ export class MatterNode extends EventEmitter {
1002
1034
  child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
1003
1035
  }
1004
1036
  }
1037
+ pluginStatusUpdate(pluginName, status) {
1038
+ this.server.request({ type: 'plugins_set', src: 'matter', dst: 'plugins', params: { plugin: { name: pluginName, ...status } } });
1039
+ this.server.request({
1040
+ type: 'frontend_broadcast_message',
1041
+ src: 'matter',
1042
+ dst: 'frontend',
1043
+ params: { msg: { id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'plugin_status_update', success: true, response: { plugin: pluginName, status } } },
1044
+ });
1045
+ }
1046
+ matterbridgeStatusUpdate(status) {
1047
+ this.server.request({
1048
+ type: 'frontend_broadcast_message',
1049
+ src: 'matter',
1050
+ dst: 'frontend',
1051
+ params: { msg: { id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'matterbridge_status_update', success: true, response: { status } } },
1052
+ });
1053
+ }
1005
1054
  async yieldToNode(timeout = 10) {
1006
1055
  await Promise.resolve();
1007
1056
  await new Promise((resolve) => {
@@ -1,7 +1,9 @@
1
1
  import { ServerNode, type SessionsBehavior } from '@matter/node';
2
2
  import type { ExposedFabricInformation } from '@matter/protocol';
3
- import type { ApiMatter, SanitizedExposedFabricInformation, SanitizedSession } from '@matterbridge/types';
3
+ import type { ApiDevice, ApiMatter, BaseDevice, SanitizedExposedFabricInformation, SanitizedSession } from '@matterbridge/types';
4
4
  import type { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
5
+ export declare function toBaseDevice(device: MatterbridgeEndpoint | BaseDevice): BaseDevice;
6
+ export declare function toApiDevice(device: MatterbridgeEndpoint): ApiDevice | undefined;
5
7
  export declare function getReachability(device: MatterbridgeEndpoint): boolean;
6
8
  export declare function getPowerSource(endpoint: MatterbridgeEndpoint): 'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined;
7
9
  export declare function getBatteryLevel(endpoint: MatterbridgeEndpoint): number | undefined;
@@ -20,6 +20,54 @@ import { isValidArray, isValidNumber, isValidObject } from '@matterbridge/utils/
20
20
  import { stringify } from 'node-ansi-logger';
21
21
  import { featuresFor, getAttribute } from './matterbridgeEndpointHelpers.js';
22
22
  const advertisingNodes = new Map();
23
+ export function toBaseDevice(device) {
24
+ return {
25
+ mode: device.mode,
26
+ plugin: device.plugin,
27
+ configUrl: device.configUrl,
28
+ deviceName: device.deviceName,
29
+ serialNumber: device.serialNumber,
30
+ uniqueId: device.uniqueId,
31
+ vendorId: device.vendorId,
32
+ vendorName: device.vendorName,
33
+ productId: device.productId,
34
+ productName: device.productName,
35
+ productLabel: device.productLabel,
36
+ productUrl: device.productUrl,
37
+ configurationVersion: device.configurationVersion,
38
+ softwareVersion: device.softwareVersion,
39
+ softwareVersionString: device.softwareVersionString,
40
+ hardwareVersion: device.hardwareVersion,
41
+ hardwareVersionString: device.hardwareVersionString,
42
+ tagList: device.tagList,
43
+ originalId: device.originalId,
44
+ name: device.name,
45
+ deviceType: device.deviceType,
46
+ deviceTypes: device.deviceTypes instanceof Map ? Array.from(device.deviceTypes.keys()) : device.deviceTypes,
47
+ number: device.number,
48
+ id: device.id,
49
+ };
50
+ }
51
+ export function toApiDevice(device) {
52
+ if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
53
+ return undefined;
54
+ return {
55
+ plugin: device.plugin,
56
+ type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
57
+ number: device.number,
58
+ id: device.id,
59
+ deviceName: device.deviceName,
60
+ serialNumber: device.serialNumber,
61
+ productUrl: device.productUrl,
62
+ configUrl: device.configUrl,
63
+ uniqueId: device.uniqueId,
64
+ reachable: getReachability(device),
65
+ powerSource: getPowerSource(device),
66
+ batteryLevel: getBatteryLevel(device),
67
+ matter: device.mode === 'server' && device.serverNode ? getServerNodeData(device.serverNode) : undefined,
68
+ cluster: getClusterTextFromDevice(device),
69
+ };
70
+ }
23
71
  export function getReachability(device) {
24
72
  if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
25
73
  return false;
@@ -16,7 +16,7 @@ import { DeviceTypeId, EndpointNumber, VendorId } from '@matter/types/datatype';
16
16
  import { ManualPairingCodeCodec } from '@matter/types/schema';
17
17
  import { BroadcastServer } from '@matterbridge/thread/server';
18
18
  import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from '@matterbridge/types';
19
- import { isBun, getGlobalBunModules, memoryFootprint } from '@matterbridge/utils/bun';
19
+ import { constrainedMemory, isBun, getGlobalBunModules, memoryFootprint } from '@matterbridge/utils/bun';
20
20
  import { getIntParameter, getParameter, hasAnyParameter, hasParameter } from '@matterbridge/utils/cli';
21
21
  import { copyDirectory } from '@matterbridge/utils/copy-dir';
22
22
  import { createDirectory } from '@matterbridge/utils/create-dir';
@@ -53,6 +53,7 @@ export class Matterbridge extends EventEmitter {
53
53
  osArch: '',
54
54
  totalMemory: '',
55
55
  freeMemory: '',
56
+ constrainedMemory: '',
56
57
  systemUptime: '',
57
58
  processUptime: '',
58
59
  cpuUsage: '',
@@ -171,6 +172,7 @@ export class Matterbridge extends EventEmitter {
171
172
  }
172
173
  getPlatformMatterbridge() {
173
174
  this.systemInformation.totalMemory = formatBytes(os.totalmem());
175
+ this.systemInformation.constrainedMemory = formatBytes(constrainedMemory());
174
176
  this.systemInformation.freeMemory = formatBytes(os.freemem());
175
177
  this.systemInformation.systemUptime = formatUptime(os.uptime());
176
178
  this.systemInformation.processUptime = formatUptime(Math.floor(process.uptime()));
@@ -201,6 +203,7 @@ export class Matterbridge extends EventEmitter {
201
203
  }
202
204
  getSharedMatterbridge() {
203
205
  this.systemInformation.totalMemory = formatBytes(os.totalmem());
206
+ this.systemInformation.constrainedMemory = formatBytes(constrainedMemory());
204
207
  this.systemInformation.freeMemory = formatBytes(os.freemem());
205
208
  this.systemInformation.systemUptime = formatUptime(os.uptime());
206
209
  this.systemInformation.processUptime = formatUptime(Math.floor(process.uptime()));
@@ -246,6 +249,7 @@ export class Matterbridge extends EventEmitter {
246
249
  }
247
250
  getApiSettings() {
248
251
  this.systemInformation.totalMemory = formatBytes(os.totalmem());
252
+ this.systemInformation.constrainedMemory = formatBytes(constrainedMemory());
249
253
  this.systemInformation.freeMemory = formatBytes(os.freemem());
250
254
  this.systemInformation.systemUptime = formatUptime(os.uptime());
251
255
  this.systemInformation.processUptime = formatUptime(Math.floor(process.uptime()));
@@ -275,9 +279,6 @@ export class Matterbridge extends EventEmitter {
275
279
  bridgeStatus: this.bridgeStatus,
276
280
  profile: this.profile,
277
281
  readOnly: hasParameter('readonly'),
278
- shellyBoard: false,
279
- shellySysUpdate: false,
280
- shellyMainUpdate: false,
281
282
  loggerLevel: this.logLevel,
282
283
  fileLogger: this.fileLogger,
283
284
  matterLoggerLevel: this.matterLogLevel,
@@ -299,7 +300,7 @@ export class Matterbridge extends EventEmitter {
299
300
  };
300
301
  }
301
302
  async msgHandler(msg) {
302
- if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'matterbridge')) {
303
+ if (this.server.isWorkerRequest(msg)) {
303
304
  if (this.verbose)
304
305
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
305
306
  switch (msg.type) {
@@ -355,21 +356,6 @@ export class Matterbridge extends EventEmitter {
355
356
  this.server.respond({ ...msg, result: { success: true } });
356
357
  await this.shutdownProcess();
357
358
  break;
358
- case 'matterbridge_apimatter': {
359
- let serverNode;
360
- if (msg.params.id === 'Matterbridge')
361
- serverNode = this.serverNode;
362
- else
363
- serverNode =
364
- this.plugins.array().find((p) => p.serverNode && p.serverNode.id === msg.params.id)?.serverNode ??
365
- this.devices.array().find((d) => d.serverNode && d.serverNode.id === msg.params.id)?.serverNode;
366
- if (!serverNode) {
367
- this.server.respond({ ...msg, result: { matter: undefined } });
368
- break;
369
- }
370
- this.server.respond({ ...msg, result: { matter: getServerNodeData(serverNode) } });
371
- break;
372
- }
373
359
  case 'matterbridge_apisettings':
374
360
  this.server.respond({ ...msg, result: { data: this.getApiSettings(), success: true } });
375
361
  break;
@@ -474,6 +460,7 @@ export class Matterbridge extends EventEmitter {
474
460
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR));
475
461
  this.environment.vars.set('runtime.signals', false);
476
462
  this.environment.vars.set('runtime.exitcode', false);
463
+ this.environment.vars.set('endpoint.validation', 'strict');
477
464
  this.registerProcessHandlers();
478
465
  try {
479
466
  this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
@@ -1204,6 +1191,7 @@ export class Matterbridge extends EventEmitter {
1204
1191
  this.systemInformation.osPlatform = os.platform();
1205
1192
  this.systemInformation.osArch = os.arch();
1206
1193
  this.systemInformation.totalMemory = formatBytes(os.totalmem());
1194
+ this.systemInformation.constrainedMemory = formatBytes(constrainedMemory());
1207
1195
  this.systemInformation.freeMemory = formatBytes(os.freemem());
1208
1196
  this.systemInformation.systemUptime = formatUptime(os.uptime());
1209
1197
  this.systemInformation.processUptime = formatUptime(process.uptime());
@@ -1226,6 +1214,7 @@ export class Matterbridge extends EventEmitter {
1226
1214
  this.log.debug(`- Architecture: ${this.systemInformation.osArch}`);
1227
1215
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
1228
1216
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
1217
+ this.log.debug(`- Constrained Memory: ${this.systemInformation.constrainedMemory}`);
1229
1218
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1230
1219
  this.log.debug(`- Process Uptime: ${this.systemInformation.processUptime}`);
1231
1220
  this.log.debug(`- RSS: ${this.systemInformation.rss}`);
@@ -1400,6 +1389,7 @@ export class Matterbridge extends EventEmitter {
1400
1389
  this.reachabilityTimeout = undefined;
1401
1390
  this.log.debug('Matterbridge reachability timeout cleared');
1402
1391
  }
1392
+ this.server.request({ type: 'manager_shutdown', src: 'matterbridge', dst: 'manager', params: {} });
1403
1393
  for (const plugin of this.plugins) {
1404
1394
  if (!plugin.enabled || plugin.error)
1405
1395
  continue;
@@ -1530,12 +1520,12 @@ export class Matterbridge extends EventEmitter {
1530
1520
  this.log.info(`Matterbridge has run ${CYAN}${this.runningTimes}${nf} times for a total of ${CYAN}${this.runningDays}${nf} days`);
1531
1521
  this.log.info(`Matterbridge last started at ${CYAN}${new Date(this.startupAt).toLocaleString()}${nf} and shut down at ${CYAN}${new Date(this.shutdownAt).toLocaleString()}${nf}`);
1532
1522
  }
1533
- await this.frontend.stop();
1534
- this.frontend.destroy();
1523
+ if (!hasParameter('experimental-backend')) {
1524
+ await this.frontend.stop();
1525
+ this.frontend.destroy();
1526
+ }
1535
1527
  this.plugins.destroy();
1536
1528
  this.devices.destroy();
1537
- this.server.request({ type: 'manager_shutdown', src: 'matterbridge', dst: 'manager', params: {} });
1538
- await wait(100);
1539
1529
  this.server.close();
1540
1530
  if (this.nodeStorage && this.nodeContext) {
1541
1531
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
@@ -1,4 +1,4 @@
1
- import type { ClusterType } from '@matter/types';
1
+ import type { ClusterType } from '@matter/types/cluster';
2
2
  import { type ClusterId, DeviceTypeId } from '@matter/types/datatype';
3
3
  export declare enum DeviceClasses {
4
4
  Node = "Node",