matterbridge 1.7.2 → 2.0.0-edge1

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 (76) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/cli.js +3 -13
  3. package/dist/cli.js.map +1 -1
  4. package/dist/deviceManager.d.ts +7 -7
  5. package/dist/deviceManager.d.ts.map +1 -1
  6. package/dist/deviceManager.js +2 -2
  7. package/dist/deviceManager.js.map +1 -1
  8. package/dist/frontend.d.ts +98 -0
  9. package/dist/frontend.d.ts.map +1 -0
  10. package/dist/frontend.js +1377 -0
  11. package/dist/frontend.js.map +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -6
  14. package/dist/index.js.map +1 -1
  15. package/dist/matter/export.d.ts.map +1 -1
  16. package/dist/matter/export.js +0 -1
  17. package/dist/matter/export.js.map +1 -1
  18. package/dist/matterbridge.d.ts +82 -208
  19. package/dist/matterbridge.d.ts.map +1 -1
  20. package/dist/matterbridge.js +777 -2310
  21. package/dist/matterbridge.js.map +1 -1
  22. package/dist/matterbridgeBehaviors.d.ts +32 -851
  23. package/dist/matterbridgeBehaviors.d.ts.map +1 -1
  24. package/dist/matterbridgeBehaviors.js +22 -2
  25. package/dist/matterbridgeBehaviors.js.map +1 -1
  26. package/dist/matterbridgeEndpoint.d.ts +100 -9089
  27. package/dist/matterbridgeEndpoint.d.ts.map +1 -1
  28. package/dist/matterbridgeEndpoint.js +59 -31
  29. package/dist/matterbridgeEndpoint.js.map +1 -1
  30. package/dist/matterbridgePlatform.d.ts +14 -28
  31. package/dist/matterbridgePlatform.d.ts.map +1 -1
  32. package/dist/matterbridgePlatform.js +7 -23
  33. package/dist/matterbridgePlatform.js.map +1 -1
  34. package/dist/matterbridgeTypes.d.ts.map +1 -1
  35. package/dist/matterbridgeTypes.js +4 -0
  36. package/dist/matterbridgeTypes.js.map +1 -1
  37. package/dist/pluginManager.d.ts +1 -1
  38. package/dist/pluginManager.d.ts.map +1 -1
  39. package/dist/pluginManager.js +5 -11
  40. package/dist/pluginManager.js.map +1 -1
  41. package/dist/utils/utils.d.ts +1 -1
  42. package/dist/utils/utils.d.ts.map +1 -1
  43. package/dist/utils/utils.js +6 -6
  44. package/dist/utils/utils.js.map +1 -1
  45. package/frontend/build/asset-manifest.json +3 -3
  46. package/frontend/build/index.html +1 -1
  47. package/frontend/build/static/js/{main.08241820.js → main.ea28015b.js} +3 -3
  48. package/frontend/build/static/js/main.ea28015b.js.map +1 -0
  49. package/npm-shrinkwrap.json +9 -9
  50. package/package.json +2 -3
  51. package/dist/cli.d.ts +0 -25
  52. package/dist/cluster/export.d.ts +0 -2
  53. package/dist/defaultConfigSchema.d.ts +0 -27
  54. package/dist/index.d.ts +0 -40
  55. package/dist/logger/export.d.ts +0 -2
  56. package/dist/matter/export.d.ts +0 -11
  57. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -39
  58. package/dist/matterbridgeDevice.d.ts +0 -7077
  59. package/dist/matterbridgeDevice.d.ts.map +0 -1
  60. package/dist/matterbridgeDevice.js +0 -2736
  61. package/dist/matterbridgeDevice.js.map +0 -1
  62. package/dist/matterbridgeDynamicPlatform.d.ts +0 -39
  63. package/dist/matterbridgeEdge.d.ts +0 -91
  64. package/dist/matterbridgeEdge.d.ts.map +0 -1
  65. package/dist/matterbridgeEdge.js +0 -1077
  66. package/dist/matterbridgeEdge.js.map +0 -1
  67. package/dist/matterbridgeTypes.d.ts +0 -172
  68. package/dist/matterbridgeWebsocket.d.ts +0 -49
  69. package/dist/matterbridgeWebsocket.d.ts.map +0 -1
  70. package/dist/matterbridgeWebsocket.js +0 -325
  71. package/dist/matterbridgeWebsocket.js.map +0 -1
  72. package/dist/storage/export.d.ts +0 -2
  73. package/dist/utils/colorUtils.d.ts +0 -61
  74. package/dist/utils/export.d.ts +0 -3
  75. package/frontend/build/static/js/main.08241820.js.map +0 -1
  76. /package/frontend/build/static/js/{main.08241820.js.LICENSE.txt → main.ea28015b.js.LICENSE.txt} +0 -0
@@ -24,34 +24,24 @@
24
24
  import { fileURLToPath } from 'url';
25
25
  import { promises as fs } from 'fs';
26
26
  import { exec, spawn } from 'child_process';
27
- import { createServer } from 'http';
28
27
  import EventEmitter from 'events';
29
28
  import os from 'os';
30
29
  import path from 'path';
31
30
  import { randomBytes } from 'crypto';
32
- // Package modules
33
- import https from 'https';
34
- import express from 'express';
35
- import WebSocket, { WebSocketServer } from 'ws';
36
31
  // NodeStorage and AnsiLogger modules
37
32
  import { NodeStorageManager } from 'node-persist-manager';
38
- import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, idn, or, hk, BLUE } from 'node-ansi-logger';
33
+ import { AnsiLogger, TimestampFormat, LogLevel, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from 'node-ansi-logger';
39
34
  // Matterbridge
40
- import { MatterbridgeDevice } from './matterbridgeDevice.js';
41
- import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
42
- import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
35
+ import { logInterfaces, wait, waiter, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
43
36
  import { PluginManager } from './pluginManager.js';
44
37
  import { DeviceManager } from './deviceManager.js';
45
38
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
39
+ import { bridge } from './matterbridgeDeviceTypes.js';
46
40
  // @matter
47
- import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageManager, EndpointServer, StorageService, Environment } from '@matter/main';
48
- import { BasicInformationCluster, BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, SwitchCluster, ThreadNetworkDiagnosticsCluster, UserLabelCluster, } from '@matter/main/clusters';
49
- import { getClusterNameById, ManualPairingCodeCodec, QrCodeSchema } from '@matter/main/types';
50
- import { StorageBackendDisk, StorageBackendJsonFile } from '@matter/nodejs';
51
- // @project-chip
52
- import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter.js';
53
- import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter.js/device';
54
- import { aggregator } from './matterbridgeDeviceTypes.js';
41
+ import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
42
+ import { FabricAction, PaseClient } from '@matter/main/protocol';
43
+ import { AggregatorEndpoint } from '@matter/main/endpoints';
44
+ import { Frontend } from './frontend.js';
55
45
  // Default colors
56
46
  const plg = '\u001B[38;5;33m';
57
47
  const dev = '\u001B[38;5;79m';
@@ -92,10 +82,9 @@ export class Matterbridge extends EventEmitter {
92
82
  matterbridgeConnected: false,
93
83
  bridgeMode: '',
94
84
  restartMode: '',
95
- edge: hasParameter('edge'),
96
85
  readOnly: hasParameter('readonly'),
97
86
  profile: getParameter('profile'),
98
- loggerLevel: "info" /* LogLevel.INFO */,
87
+ loggerLevel: LogLevel.INFO,
99
88
  fileLogger: false,
100
89
  matterLoggerLevel: MatterLogLevel.INFO,
101
90
  matterFileLogger: false,
@@ -124,15 +113,16 @@ export class Matterbridge extends EventEmitter {
124
113
  bridgeMode = '';
125
114
  restartMode = '';
126
115
  profile = getParameter('profile');
127
- edge = hasParameter('edge');
116
+ edge = true;
128
117
  log;
129
118
  matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
130
119
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
131
120
  plugins;
132
121
  devices;
122
+ frontend = new Frontend(this);
123
+ // Matterbridge storage
133
124
  nodeStorage;
134
125
  nodeContext;
135
- matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
136
126
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
137
127
  // Cleanup
138
128
  hasCleanupStarted = false;
@@ -146,37 +136,33 @@ export class Matterbridge extends EventEmitter {
146
136
  sigtermHandler;
147
137
  exceptionHandler;
148
138
  rejectionHandler;
149
- // Frontend
150
- expressApp;
151
- httpServer;
152
- httpsServer;
153
- webSocketServer;
154
- // Matter
139
+ // Matter environment
140
+ environment = Environment.default;
141
+ // Matter storage
142
+ matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
143
+ matterStorageService;
144
+ matterStorageManager;
145
+ matterbridgeContext;
146
+ mattercontrollerContext;
147
+ // Matter parameters
155
148
  mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
156
149
  ipv4address; // matter commissioning server listeningAddressIpv4
157
150
  ipv6address; // matter commissioning server listeningAddressIpv6
158
- port = 5540; // first commissioning server port
151
+ port; // first commissioning server port
159
152
  passcode; // first commissioning server passcode
160
153
  discriminator; // first commissioning server discriminator
161
- storageManager;
162
- matterbridgeContext;
163
- mattercontrollerContext;
164
- matterServer;
165
- matterAggregator;
166
- commissioningServer;
167
- commissioningController;
154
+ serverNode;
155
+ aggregatorNode;
168
156
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
169
157
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
170
158
  static instance;
171
159
  // We load asyncronously so is private
172
160
  constructor() {
173
161
  super();
174
- // Bind the handler to the instance
175
- this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
176
162
  }
177
163
  /**
178
164
  * Retrieves the list of Matterbridge devices.
179
- * @returns {MatterbridgeDevice[]} An array of MatterbridgeDevice objects.
165
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
180
166
  */
181
167
  getDevices() {
182
168
  return this.devices.array();
@@ -188,7 +174,6 @@ export class Matterbridge extends EventEmitter {
188
174
  getPlugins() {
189
175
  return this.plugins.array();
190
176
  }
191
- matterbridgeMessageHandler;
192
177
  /** ***********************************************************************************************************************************/
193
178
  /** loadInstance() and cleanup() methods */
194
179
  /** ***********************************************************************************************************************************/
@@ -241,8 +226,16 @@ export class Matterbridge extends EventEmitter {
241
226
  // Set the matterbridge directory
242
227
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
243
228
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
229
+ // Setup matter environment
230
+ this.environment.vars.set('log.level', MatterLogLevel.INFO);
231
+ this.environment.vars.set('log.format', MatterLogFormat.ANSI);
232
+ this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
233
+ this.environment.vars.set('runtime.signals', false);
234
+ this.environment.vars.set('runtime.exitcode', false);
244
235
  // Create matterbridge logger
245
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
236
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: TimestampFormat.TIME_MILLIS, logLevel: hasParameter('debug') ? LogLevel.DEBUG : LogLevel.INFO });
237
+ // Register process handlers
238
+ this.registerProcessHandlers();
246
239
  // Initialize nodeStorage and nodeContext
247
240
  try {
248
241
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
@@ -293,40 +286,40 @@ export class Matterbridge extends EventEmitter {
293
286
  // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
294
287
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
295
288
  // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
296
- this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
289
+ this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
297
290
  // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
298
- this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
291
+ this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
299
292
  this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
300
293
  // Set matterbridge logger level (context: matterbridgeLogLevel)
301
294
  if (hasParameter('logger')) {
302
295
  const level = getParameter('logger');
303
296
  if (level === 'debug') {
304
- this.log.logLevel = "debug" /* LogLevel.DEBUG */;
297
+ this.log.logLevel = LogLevel.DEBUG;
305
298
  }
306
299
  else if (level === 'info') {
307
- this.log.logLevel = "info" /* LogLevel.INFO */;
300
+ this.log.logLevel = LogLevel.INFO;
308
301
  }
309
302
  else if (level === 'notice') {
310
- this.log.logLevel = "notice" /* LogLevel.NOTICE */;
303
+ this.log.logLevel = LogLevel.NOTICE;
311
304
  }
312
305
  else if (level === 'warn') {
313
- this.log.logLevel = "warn" /* LogLevel.WARN */;
306
+ this.log.logLevel = LogLevel.WARN;
314
307
  }
315
308
  else if (level === 'error') {
316
- this.log.logLevel = "error" /* LogLevel.ERROR */;
309
+ this.log.logLevel = LogLevel.ERROR;
317
310
  }
318
311
  else if (level === 'fatal') {
319
- this.log.logLevel = "fatal" /* LogLevel.FATAL */;
312
+ this.log.logLevel = LogLevel.FATAL;
320
313
  }
321
314
  else {
322
315
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
323
- this.log.logLevel = "info" /* LogLevel.INFO */;
316
+ this.log.logLevel = LogLevel.INFO;
324
317
  }
325
318
  }
326
319
  else {
327
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
320
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', LogLevel.INFO);
328
321
  }
329
- MatterbridgeDevice.logLevel = this.log.logLevel;
322
+ MatterbridgeEndpoint.logLevel = this.log.logLevel;
330
323
  // Create the file logger for matterbridge (context: matterbridgeFileLog)
331
324
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
332
325
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
@@ -374,21 +367,35 @@ export class Matterbridge extends EventEmitter {
374
367
  });
375
368
  }
376
369
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
377
- // Set the interface to use for the matter server mdnsInterface
370
+ // Set the interface to use for matter server node mdnsInterface
378
371
  if (hasParameter('mdnsinterface')) {
379
372
  this.mdnsInterface = getParameter('mdnsinterface');
380
373
  }
381
374
  else {
382
- this.mdnsInterface = await this.nodeContext?.get('mattermdnsinterface', undefined);
375
+ this.mdnsInterface = await this.nodeContext.get('mattermdnsinterface', undefined);
383
376
  if (this.mdnsInterface === '')
384
377
  this.mdnsInterface = undefined;
385
378
  }
379
+ // Validate mdnsInterface
380
+ if (this.mdnsInterface) {
381
+ const networkInterfaces = os.networkInterfaces();
382
+ const availableInterfaces = Object.keys(networkInterfaces);
383
+ if (!availableInterfaces.includes(this.mdnsInterface)) {
384
+ this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
385
+ this.mdnsInterface = undefined;
386
+ }
387
+ else {
388
+ this.log.info(`Using mdnsInterface '${this.mdnsInterface}' for the Matter server MdnsBroadcaster.`);
389
+ }
390
+ }
391
+ if (this.mdnsInterface)
392
+ this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
386
393
  // Set the listeningAddressIpv4 for the matter commissioning server
387
394
  if (hasParameter('ipv4address')) {
388
395
  this.ipv4address = getParameter('ipv4address');
389
396
  }
390
397
  else {
391
- this.ipv4address = await this.nodeContext?.get('matteripv4address', undefined);
398
+ this.ipv4address = await this.nodeContext.get('matteripv4address', undefined);
392
399
  if (this.ipv4address === '')
393
400
  this.ipv4address = undefined;
394
401
  }
@@ -409,20 +416,19 @@ export class Matterbridge extends EventEmitter {
409
416
  // Get the plugins from node storage and create the plugins node storage contexts
410
417
  for (const plugin of this.plugins) {
411
418
  const packageJson = await this.plugins.parse(plugin);
412
- if (packageJson === null && !hasParameter('add')) {
419
+ if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
413
420
  // Try to reinstall the plugin from npm (for Docker pull and external plugins)
414
- // We don't do this when the add parameter is set because we shut down the process after adding the plugin
421
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
415
422
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
416
423
  try {
417
424
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
418
425
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
419
426
  plugin.error = false;
420
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
421
427
  }
422
428
  catch (error) {
423
429
  plugin.error = true;
424
430
  plugin.enabled = false;
425
- this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled.`);
431
+ this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled.`, error instanceof Error ? error.message : error);
426
432
  }
427
433
  }
428
434
  this.log.debug(`Creating node storage context for plugin ${plg}${plugin.name}${db}`);
@@ -450,8 +456,6 @@ export class Matterbridge extends EventEmitter {
450
456
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
451
457
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
452
458
  }
453
- // Register process handlers
454
- this.registerProcessHandlers();
455
459
  // Parse command line
456
460
  await this.parseCommandLine();
457
461
  this.initialized = true;
@@ -567,75 +571,35 @@ export class Matterbridge extends EventEmitter {
567
571
  return;
568
572
  }
569
573
  if (hasParameter('factoryreset')) {
570
- try {
571
- // Delete old matter storage file
572
- const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
573
- this.log.info(`Unlinking old matter storage file: ${file}`);
574
- await fs.unlink(file);
575
- }
576
- catch (err) {
577
- if (err instanceof Error && err.code !== 'ENOENT') {
578
- this.log.error(`Error unlinking old matter storage file: ${err}`);
579
- }
580
- }
581
- try {
582
- // Delete matter node storage directory with its subdirectories
583
- const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
584
- this.log.info(`Removing matter node storage directory: ${dir}`);
585
- await fs.rm(dir, { recursive: true });
586
- }
587
- catch (err) {
588
- if (err instanceof Error && err.code !== 'ENOENT') {
589
- this.log.error(`Error removing matter storage directory: ${err}`);
590
- }
591
- }
592
- try {
593
- // Delete node storage directory with its subdirectories
594
- const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
595
- this.log.info(`Removing storage directory: ${dir}`);
596
- await fs.rm(dir, { recursive: true });
597
- }
598
- catch (err) {
599
- if (err instanceof Error && err.code !== 'ENOENT') {
600
- this.log.error(`Error removing storage directory: ${err}`);
601
- }
602
- }
603
- this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
604
- this.nodeContext = undefined;
605
- this.nodeStorage = undefined;
606
- this.plugins.clear();
607
- this.devices.clear();
608
- this.emit('shutdown');
574
+ await this.shutdownProcessAndFactoryReset();
609
575
  return;
610
576
  }
611
577
  // Start the matter storage and create the matterbridge context
612
578
  try {
613
- await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
579
+ await this.startMatterStorage();
614
580
  }
615
581
  catch (error) {
616
582
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
617
583
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
618
584
  }
619
585
  // Clear the matterbridge context if the reset parameter is set
620
- if (!this.edge && hasParameter('reset') && getParameter('reset') === undefined) {
621
- this.log.info('Resetting Matterbridge commissioning information...');
622
- await this.matterbridgeContext?.clearAll();
623
- await this.stopMatterStorage();
624
- this.log.info('Reset done! Remove the device from the controller.');
625
- this.emit('shutdown');
586
+ if (hasParameter('reset') && getParameter('reset') === undefined) {
587
+ await this.shutdownProcessAndReset();
626
588
  return;
627
589
  }
628
590
  // Clear matterbridge plugin context if the reset parameter is set
629
- if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
591
+ if (hasParameter('reset') && getParameter('reset') !== undefined) {
630
592
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
631
593
  const plugin = this.plugins.get(getParameter('reset'));
632
594
  if (plugin) {
633
- if (!this.storageManager)
595
+ const matterStorageManager = await this.matterStorageService?.open(plugin.name);
596
+ if (!matterStorageManager)
634
597
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
635
- const context = this.storageManager?.createContext(plugin.name);
636
- if (!context)
637
- this.log.error(`Plugin ${plg}${plugin.name}${er} context not found`);
638
- await context?.clearAll();
598
+ await matterStorageManager?.createContext('events')?.clearAll();
599
+ await matterStorageManager?.createContext('fabrics')?.clearAll();
600
+ await matterStorageManager?.createContext('root')?.clearAll();
601
+ await matterStorageManager?.createContext('sessions')?.clearAll();
602
+ await matterStorageManager?.createContext('persist')?.clearAll();
639
603
  this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
640
604
  }
641
605
  else {
@@ -647,18 +611,19 @@ export class Matterbridge extends EventEmitter {
647
611
  }
648
612
  // Initialize frontend
649
613
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
650
- await this.initializeFrontend(getIntParameter('frontend'));
614
+ await this.frontend.start(getIntParameter('frontend'));
651
615
  // Check each 60 minutes the latest versions
652
616
  this.checkUpdateInterval = setInterval(() => {
653
617
  this.getMatterbridgeLatestVersion();
654
618
  for (const plugin of this.plugins) {
655
619
  this.getPluginLatestVersion(plugin);
656
620
  }
621
+ this.frontend.wssSendRefreshRequired();
657
622
  }, 60 * 60 * 1000);
658
623
  // Start the matterbridge in mode test
659
624
  if (hasParameter('test')) {
660
625
  this.bridgeMode = 'bridge';
661
- MatterbridgeDevice.bridgeMode = 'bridge';
626
+ MatterbridgeEndpoint.bridgeMode = 'bridge';
662
627
  return;
663
628
  }
664
629
  // Start the matterbridge in mode controller
@@ -675,7 +640,6 @@ export class Matterbridge extends EventEmitter {
675
640
  // Start matterbridge in bridge mode
676
641
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
677
642
  this.bridgeMode = 'bridge';
678
- MatterbridgeDevice.bridgeMode = 'bridge';
679
643
  MatterbridgeEndpoint.bridgeMode = 'bridge';
680
644
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
681
645
  await this.startBridge();
@@ -684,7 +648,6 @@ export class Matterbridge extends EventEmitter {
684
648
  // Start matterbridge in childbridge mode
685
649
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
686
650
  this.bridgeMode = 'childbridge';
687
- MatterbridgeDevice.bridgeMode = 'childbridge';
688
651
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
689
652
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
690
653
  await this.startChildbridge();
@@ -695,7 +658,7 @@ export class Matterbridge extends EventEmitter {
695
658
  * Asynchronously loads and starts the registered plugins.
696
659
  *
697
660
  * This method is responsible for initializing and staarting all enabled plugins.
698
- * It ensures that each plugin is properly loaded and started before the ridge starts.
661
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
699
662
  *
700
663
  * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
701
664
  */
@@ -729,7 +692,7 @@ export class Matterbridge extends EventEmitter {
729
692
  plugin.manualPairingCode = undefined;
730
693
  this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
731
694
  }
732
- this.wssSendRefreshRequired();
695
+ this.frontend.wssSendRefreshRequired();
733
696
  }
734
697
  /**
735
698
  * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
@@ -784,9 +747,13 @@ export class Matterbridge extends EventEmitter {
784
747
  async logNodeAndSystemInfo() {
785
748
  // IP address information
786
749
  const networkInterfaces = os.networkInterfaces();
750
+ this.systemInformation.interfaceName = '';
787
751
  this.systemInformation.ipv4Address = '';
788
752
  this.systemInformation.ipv6Address = '';
789
753
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
754
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
755
+ if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
756
+ continue;
790
757
  if (!interfaceDetails) {
791
758
  break;
792
759
  }
@@ -802,7 +769,7 @@ export class Matterbridge extends EventEmitter {
802
769
  this.systemInformation.macAddress = detail.mac;
803
770
  }
804
771
  }
805
- if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
772
+ if (this.systemInformation.ipv4Address !== '' || this.systemInformation.ipv6Address !== '') {
806
773
  this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
807
774
  this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
808
775
  this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
@@ -1033,32 +1000,32 @@ export class Matterbridge extends EventEmitter {
1033
1000
  * @returns {Function} The MatterLogger function.
1034
1001
  */
1035
1002
  createMatterLogger() {
1036
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
1003
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: TimestampFormat.TIME_MILLIS, logLevel: LogLevel.DEBUG });
1037
1004
  return (_level, formattedLog) => {
1038
1005
  const logger = formattedLog.slice(44, 44 + 20).trim();
1039
1006
  const message = formattedLog.slice(65);
1040
1007
  matterLogger.logName = logger;
1041
1008
  switch (_level) {
1042
1009
  case MatterLogLevel.DEBUG:
1043
- matterLogger.log("debug" /* LogLevel.DEBUG */, message);
1010
+ matterLogger.log(LogLevel.DEBUG, message);
1044
1011
  break;
1045
1012
  case MatterLogLevel.INFO:
1046
- matterLogger.log("info" /* LogLevel.INFO */, message);
1013
+ matterLogger.log(LogLevel.INFO, message);
1047
1014
  break;
1048
1015
  case MatterLogLevel.NOTICE:
1049
- matterLogger.log("notice" /* LogLevel.NOTICE */, message);
1016
+ matterLogger.log(LogLevel.NOTICE, message);
1050
1017
  break;
1051
1018
  case MatterLogLevel.WARN:
1052
- matterLogger.log("warn" /* LogLevel.WARN */, message);
1019
+ matterLogger.log(LogLevel.WARN, message);
1053
1020
  break;
1054
1021
  case MatterLogLevel.ERROR:
1055
- matterLogger.log("error" /* LogLevel.ERROR */, message);
1022
+ matterLogger.log(LogLevel.ERROR, message);
1056
1023
  break;
1057
1024
  case MatterLogLevel.FATAL:
1058
- matterLogger.log("fatal" /* LogLevel.FATAL */, message);
1025
+ matterLogger.log(LogLevel.FATAL, message);
1059
1026
  break;
1060
1027
  default:
1061
- matterLogger.log("debug" /* LogLevel.DEBUG */, message);
1028
+ matterLogger.log(LogLevel.DEBUG, message);
1062
1029
  break;
1063
1030
  }
1064
1031
  };
@@ -1121,13 +1088,7 @@ export class Matterbridge extends EventEmitter {
1121
1088
  };
1122
1089
  }
1123
1090
  /**
1124
- * Update matterbridge and cleanup.
1125
- */
1126
- async updateProcess() {
1127
- await this.cleanup('updating...', false);
1128
- }
1129
- /**
1130
- * Restarts the process by spawning a new process and exiting the current process.
1091
+ * Restarts the process by exiting the current instance and loading a new instance.
1131
1092
  */
1132
1093
  async restartProcess() {
1133
1094
  await this.cleanup('restarting...', true);
@@ -1139,28 +1100,95 @@ export class Matterbridge extends EventEmitter {
1139
1100
  await this.cleanup('shutting down...', false);
1140
1101
  }
1141
1102
  /**
1142
- * Shut down the process and reset.
1103
+ * Update matterbridge and and shut down the process.
1104
+ */
1105
+ async updateProcess() {
1106
+ this.log.info('Updating matterbridge...');
1107
+ try {
1108
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
1109
+ this.log.info('Matterbridge has been updated. Full restart required.');
1110
+ }
1111
+ catch (error) {
1112
+ this.log.error('Error updating matterbridge:', error instanceof Error ? error.message : error);
1113
+ }
1114
+ this.frontend.wssSendRestartRequired();
1115
+ await this.cleanup('updating...', false);
1116
+ }
1117
+ /**
1118
+ * Unregister all devices and shut down the process.
1143
1119
  */
1144
1120
  async unregisterAndShutdownProcess() {
1145
1121
  this.log.info('Unregistering all devices and shutting down...');
1146
- for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
1147
- if (this.edge)
1148
- await this.removeAllBridgedEndpoints(plugin.name);
1149
- else
1150
- await this.removeAllBridgedDevices(plugin.name);
1122
+ for (const plugin of this.plugins) {
1123
+ await this.removeAllBridgedEndpoints(plugin.name);
1151
1124
  }
1152
1125
  await this.cleanup('unregistered all devices and shutting down...', false);
1153
1126
  }
1154
1127
  /**
1155
- * Shut down the process and reset.
1128
+ * Reset commissioning and shut down the process.
1156
1129
  */
1157
1130
  async shutdownProcessAndReset() {
1131
+ this.log.info('Resetting Matterbridge commissioning information...');
1132
+ await this.matterStorageManager?.createContext('events')?.clearAll();
1133
+ await this.matterStorageManager?.createContext('fabrics')?.clearAll();
1134
+ await this.matterStorageManager?.createContext('root')?.clearAll();
1135
+ await this.matterStorageManager?.createContext('sessions')?.clearAll();
1136
+ await this.matterbridgeContext?.clearAll();
1137
+ await this.stopMatterStorage();
1138
+ this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1158
1139
  await this.cleanup('shutting down with reset...', false);
1159
1140
  }
1160
1141
  /**
1161
- * Shut down the process and factory reset.
1142
+ * Factory reset and shut down the process.
1162
1143
  */
1163
1144
  async shutdownProcessAndFactoryReset() {
1145
+ try {
1146
+ // Delete old matter storage file and backup
1147
+ const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1148
+ this.log.info(`Unlinking old matter storage file: ${file}`);
1149
+ await fs.unlink(file);
1150
+ const backup = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup.json');
1151
+ this.log.info(`Unlinking old matter storage backup file: ${backup}`);
1152
+ await fs.unlink(backup);
1153
+ }
1154
+ catch (err) {
1155
+ if (err instanceof Error && err.code !== 'ENOENT') {
1156
+ this.log.error(`Error unlinking old matter storage file: ${err}`);
1157
+ }
1158
+ }
1159
+ try {
1160
+ // Delete matter node storage directory with its subdirectories and backup
1161
+ const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1162
+ this.log.info(`Removing matter node storage directory: ${dir}`);
1163
+ await fs.rm(dir, { recursive: true });
1164
+ const backup = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
1165
+ this.log.info(`Removing matter node storage backup directory: ${backup}`);
1166
+ await fs.rm(backup, { recursive: true });
1167
+ }
1168
+ catch (err) {
1169
+ if (err instanceof Error && err.code !== 'ENOENT') {
1170
+ this.log.error(`Error removing matter storage directory: ${err}`);
1171
+ }
1172
+ }
1173
+ try {
1174
+ // Delete node storage directory with its subdirectories and backup
1175
+ const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1176
+ this.log.info(`Removing storage directory: ${dir}`);
1177
+ await fs.rm(dir, { recursive: true });
1178
+ const backup = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
1179
+ this.log.info(`Removing storage backup directory: ${backup}`);
1180
+ await fs.rm(backup, { recursive: true });
1181
+ }
1182
+ catch (err) {
1183
+ if (err instanceof Error && err.code !== 'ENOENT') {
1184
+ this.log.error(`Error removing storage directory: ${err}`);
1185
+ }
1186
+ }
1187
+ this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1188
+ this.nodeContext = undefined;
1189
+ this.nodeStorage = undefined;
1190
+ this.plugins.clear();
1191
+ this.devices.clear();
1164
1192
  await this.cleanup('shutting down with factory reset...', false);
1165
1193
  }
1166
1194
  /**
@@ -1173,8 +1201,6 @@ export class Matterbridge extends EventEmitter {
1173
1201
  if (this.initialized && !this.hasCleanupStarted) {
1174
1202
  this.hasCleanupStarted = true;
1175
1203
  this.log.info(message);
1176
- // Deregisters the process handlers
1177
- this.deregisterProcesslHandlers();
1178
1204
  // Clear the start matter interval
1179
1205
  if (this.startMatterInterval) {
1180
1206
  clearInterval(this.startMatterInterval);
@@ -1199,7 +1225,7 @@ export class Matterbridge extends EventEmitter {
1199
1225
  this.reachabilityTimeout = undefined;
1200
1226
  this.log.debug('Matterbridge reachability timeout cleared');
1201
1227
  }
1202
- // Calling the shutdown method of each plugin and clear the reachability timeout
1228
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
1203
1229
  for (const plugin of this.plugins) {
1204
1230
  if (!plugin.enabled || plugin.error)
1205
1231
  continue;
@@ -1210,60 +1236,29 @@ export class Matterbridge extends EventEmitter {
1210
1236
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1211
1237
  }
1212
1238
  }
1213
- // Convert the matter storage to the new format
1214
- if (!hasParameter('nostorageconversion') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
1215
- if (this.bridgeMode === 'bridge') {
1216
- await this.convertStorage(this.matterbridgeContext, 'Matterbridge');
1217
- }
1218
- else if (this.bridgeMode === 'childbridge') {
1219
- for (const plugin of this.plugins) {
1220
- if (plugin.storageContext) {
1221
- await this.convertStorage(plugin.storageContext, plugin.name);
1222
- }
1223
- }
1239
+ // Stop the frontend
1240
+ this.frontend.stop();
1241
+ // Stopping matter server nodes
1242
+ this.log.info(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1243
+ if (this.bridgeMode === 'bridge') {
1244
+ if (this.serverNode) {
1245
+ await this.stopServerNode(this.serverNode);
1246
+ this.log.info(`Stopped matter server node for Matterbridge`);
1224
1247
  }
1225
1248
  }
1226
- // Close the http server
1227
- if (this.httpServer) {
1228
- this.httpServer.close();
1229
- this.httpServer.removeAllListeners();
1230
- this.httpServer = undefined;
1231
- this.log.debug('Frontend http server closed successfully');
1232
- }
1233
- // Close the https server
1234
- if (this.httpsServer) {
1235
- this.httpsServer.close();
1236
- this.httpsServer.removeAllListeners();
1237
- this.httpsServer = undefined;
1238
- this.log.debug('Frontend https server closed successfully');
1239
- }
1240
- // Remove listeners from the express app
1241
- if (this.expressApp) {
1242
- this.expressApp.removeAllListeners();
1243
- this.expressApp = undefined;
1244
- this.log.debug('Frontend app closed successfully');
1245
- }
1246
- // Close the WebSocket server
1247
- if (this.webSocketServer) {
1248
- // Close all active connections
1249
- this.webSocketServer.clients.forEach((client) => {
1250
- if (client.readyState === WebSocket.OPEN) {
1251
- client.close();
1252
- }
1253
- });
1254
- this.webSocketServer.close((error) => {
1255
- if (error) {
1256
- this.log.error(`Error closing WebSocket server: ${error}`);
1257
- }
1258
- else {
1259
- this.log.debug('WebSocket server closed successfully');
1249
+ if (this.bridgeMode === 'childbridge') {
1250
+ for (const plugin of this.plugins.array()) {
1251
+ if (plugin.serverNode) {
1252
+ await this.stopServerNode(plugin.serverNode);
1253
+ this.log.info(`Stopped matter server node for ${plugin.name}`);
1260
1254
  }
1261
- });
1262
- this.webSocketServer = undefined;
1255
+ }
1263
1256
  }
1264
- // Closing matter
1265
- await this.stopMatterServer();
1266
- // Closing matter storage
1257
+ this.log.info('Stopped matter server nodes');
1258
+ // Stop matter MdnsService
1259
+ // await this.environment.get(MdnsService)[Symbol.asyncDispose]();
1260
+ // this.log.info('Stopped MdnsService');
1261
+ // Stop matter storage
1267
1262
  await this.stopMatterStorage();
1268
1263
  // Remove the matterfilelogger
1269
1264
  try {
@@ -1306,11 +1301,13 @@ export class Matterbridge extends EventEmitter {
1306
1301
  }
1307
1302
  this.plugins.clear();
1308
1303
  this.devices.clear();
1304
+ // Deregisters the process handlers
1305
+ this.deregisterProcesslHandlers();
1309
1306
  if (restart) {
1310
1307
  if (message === 'updating...') {
1311
1308
  this.log.info('Cleanup completed. Updating...');
1312
1309
  Matterbridge.instance = undefined;
1313
- this.emit('update');
1310
+ this.emit('update'); // Restart the process but the update has been done before
1314
1311
  }
1315
1312
  else if (message === 'restarting...') {
1316
1313
  this.log.info('Cleanup completed. Restarting...');
@@ -1319,38 +1316,6 @@ export class Matterbridge extends EventEmitter {
1319
1316
  }
1320
1317
  }
1321
1318
  else {
1322
- if (message === 'shutting down with reset...' || message === 'shutting down with factory reset...') {
1323
- try {
1324
- // Delete old matter storage file
1325
- const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1326
- this.log.info(`Unlinking old matter storage file: ${file}`);
1327
- await fs.unlink(file);
1328
- }
1329
- catch (error) {
1330
- this.log.debug(`Error resetting old matter storage file: ${error}`);
1331
- }
1332
- try {
1333
- // Delete matter node storage directory with its subdirectories
1334
- const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1335
- this.log.info(`Removing matter node storage directory: ${dir}`);
1336
- await fs.rm(dir, { recursive: true });
1337
- }
1338
- catch (error) {
1339
- this.log.debug(`Error resetting matter node storage file: ${error}`);
1340
- }
1341
- this.log.info('Reset done! Remove all paired fabrics from the controllers.');
1342
- }
1343
- if (message === 'shutting down with factory reset...') {
1344
- try {
1345
- // Delete node storage directory with its subdirectories
1346
- this.log.info('Resetting Matterbridge storage...');
1347
- await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1348
- }
1349
- catch (error) {
1350
- this.log.debug(`Error resetting Matterbridge storage: ${error}`);
1351
- }
1352
- this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1353
- }
1354
1319
  this.log.notice('Cleanup completed. Shutting down...');
1355
1320
  Matterbridge.instance = undefined;
1356
1321
  this.emit('shutdown');
@@ -1359,163 +1324,42 @@ export class Matterbridge extends EventEmitter {
1359
1324
  this.initialized = false;
1360
1325
  }
1361
1326
  }
1362
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1363
- async addBridgedEndpoint(pluginName, device) {
1364
- // Nothing to do here
1365
- }
1366
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1367
- async removeBridgedEndpoint(pluginName, device) {
1368
- // Nothing to do here
1369
- }
1370
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1371
- async removeAllBridgedEndpoints(pluginName) {
1372
- // Nothing to do here
1373
- }
1374
- /**
1375
- * Adds a bridged device to the Matterbridge.
1376
- * @param pluginName - The name of the plugin.
1377
- * @param device - The bridged device to add.
1378
- * @returns {Promise<void>} - A promise that resolves when the device is added.
1379
- */
1380
- async addBridgedDevice(pluginName, device) {
1381
- this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1382
- // Check if the plugin is registered
1383
- const plugin = this.plugins.get(pluginName);
1384
- if (!plugin) {
1385
- this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1386
- return;
1387
- }
1388
- // Register and add the device to matterbridge aggregator in bridge mode
1389
- if (this.bridgeMode === 'bridge') {
1390
- if (!this.matterAggregator) {
1391
- this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
1392
- return;
1393
- }
1394
- this.matterAggregator.addBridgedDevice(device);
1395
- }
1396
- // The first time create the commissioning server and the aggregator for DynamicPlatform
1397
- // Register and add the device in childbridge mode
1398
- if (this.bridgeMode === 'childbridge') {
1399
- if (plugin.type === 'AccessoryPlatform') {
1400
- // Check if the plugin is locked with the commissioning server
1401
- if (!plugin.locked) {
1402
- plugin.locked = true;
1403
- plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
1404
- this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
1405
- plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1406
- this.log.debug(`Adding device ${dev}${device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
1407
- plugin.commissioningServer.addDevice(device);
1408
- plugin.device = device;
1409
- this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
1410
- await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1411
- }
1412
- }
1413
- if (plugin.type === 'DynamicPlatform') {
1414
- // Check if the plugin is locked with the commissioning server and the aggregator
1415
- if (!plugin.locked) {
1416
- plugin.locked = true;
1417
- this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
1418
- plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, plugin.description);
1419
- this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
1420
- plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1421
- this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
1422
- plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name);
1423
- this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
1424
- plugin.commissioningServer.addDevice(plugin.aggregator);
1425
- this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
1426
- await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1427
- }
1428
- plugin.aggregator?.addBridgedDevice(device);
1429
- }
1430
- }
1431
- if (plugin.registeredDevices !== undefined)
1432
- plugin.registeredDevices++;
1433
- if (plugin.addedDevices !== undefined)
1434
- plugin.addedDevices++;
1435
- // Add the device to the DeviceManager
1436
- this.devices.set(device);
1437
- this.log.info(`Added and registered bridged device (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1438
- }
1439
1327
  /**
1440
- * Removes a bridged device from the Matterbridge.
1441
- * @param pluginName - The name of the plugin.
1442
- * @param device - The device to be removed.
1443
- * @returns A Promise that resolves when the device is successfully removed.
1328
+ * Creates and configures the server node for an accessory plugin for a given device.
1329
+ *
1330
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1331
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1332
+ * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1333
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1444
1334
  */
1445
- async removeBridgedDevice(pluginName, device) {
1446
- this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1447
- // Check if the plugin is registered
1448
- const plugin = this.plugins.get(pluginName);
1449
- if (!plugin) {
1450
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1451
- return;
1452
- }
1453
- // Remove the device from matterbridge aggregator in bridge mode
1454
- if (this.bridgeMode === 'bridge') {
1455
- if (!this.matterAggregator) {
1456
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
1457
- return;
1458
- }
1459
- if (device.number !== undefined) {
1460
- device.setBridgedDeviceReachability(false);
1461
- device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1462
- }
1463
- // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1464
- // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1465
- this.matterAggregator?.removeBridgedDevice(device);
1466
- this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1467
- if (plugin.registeredDevices !== undefined)
1468
- plugin.registeredDevices--;
1469
- if (plugin.addedDevices !== undefined)
1470
- plugin.addedDevices--;
1335
+ async createAccessoryPlugin(plugin, device, start = false) {
1336
+ if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1337
+ plugin.locked = true;
1338
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
1339
+ plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1340
+ this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node`);
1341
+ await plugin.serverNode.add(device);
1342
+ if (start)
1343
+ await this.startServerNode(plugin.serverNode);
1471
1344
  }
1472
- // Remove the device in childbridge mode
1473
- if (this.bridgeMode === 'childbridge') {
1474
- if (plugin.type === 'AccessoryPlatform') {
1475
- if (!plugin.commissioningServer) {
1476
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
1477
- return;
1478
- }
1479
- }
1480
- else if (plugin.type === 'DynamicPlatform') {
1481
- if (!plugin.aggregator) {
1482
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
1483
- return;
1484
- }
1485
- if (device.number !== undefined) {
1486
- device.setBridgedDeviceReachability(false);
1487
- device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1488
- }
1489
- plugin.aggregator.removeBridgedDevice(device);
1490
- }
1491
- this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1492
- if (plugin.registeredDevices !== undefined)
1493
- plugin.registeredDevices--;
1494
- if (plugin.addedDevices !== undefined)
1495
- plugin.addedDevices--;
1496
- // Remove the commissioning server
1497
- if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1498
- this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1499
- plugin.commissioningServer = undefined;
1500
- this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1501
- }
1502
- }
1503
- // Remove the device from the DeviceManager
1504
- this.devices.remove(device);
1505
1345
  }
1506
1346
  /**
1507
- * Removes all bridged devices associated with a specific plugin.
1347
+ * Creates and configures the server node for a dynamic plugin.
1508
1348
  *
1509
- * @param pluginName - The name of the plugin.
1510
- * @returns A promise that resolves when all devices have been removed.
1349
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1350
+ * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1351
+ * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1511
1352
  */
1512
- async removeAllBridgedDevices(pluginName) {
1513
- this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1514
- this.devices.forEach(async (device) => {
1515
- if (device.plugin === pluginName) {
1516
- await this.removeBridgedDevice(pluginName, device);
1517
- }
1518
- });
1353
+ async createDynamicPlugin(plugin, start = false) {
1354
+ if (!plugin.locked) {
1355
+ plugin.locked = true;
1356
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
1357
+ plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1358
+ plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
1359
+ await plugin.serverNode.add(plugin.aggregatorNode);
1360
+ if (start)
1361
+ await this.startServerNode(plugin.serverNode);
1362
+ }
1519
1363
  }
1520
1364
  /**
1521
1365
  * Starts the Matterbridge in bridge mode.
@@ -1524,25 +1368,18 @@ export class Matterbridge extends EventEmitter {
1524
1368
  */
1525
1369
  async startBridge() {
1526
1370
  // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1527
- if (!this.storageManager)
1371
+ if (!this.matterStorageManager)
1528
1372
  throw new Error('No storage manager initialized');
1529
1373
  if (!this.matterbridgeContext)
1530
1374
  throw new Error('No storage context initialized');
1531
- this.matterServer = await this.createMatterServer(this.storageManager);
1532
- this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1533
- this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1534
- this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1535
- this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
1536
- this.log.debug('Adding matterbridge aggregator to commissioning server');
1537
- this.commissioningServer.addDevice(this.matterAggregator);
1538
- this.log.debug('Adding matterbridge commissioning server to matter server');
1539
- await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1375
+ this.serverNode = await this.createServerNode(this.matterbridgeContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1376
+ this.aggregatorNode = await this.createAggregatorNode(this.matterbridgeContext);
1377
+ await this.serverNode.add(this.aggregatorNode);
1540
1378
  await this.startPlugins();
1541
1379
  this.log.debug('Starting start matter interval in bridge mode');
1542
1380
  let failCount = 0;
1543
1381
  this.startMatterInterval = setInterval(async () => {
1544
1382
  for (const plugin of this.plugins) {
1545
- // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1546
1383
  if (!plugin.enabled)
1547
1384
  continue;
1548
1385
  if (plugin.error) {
@@ -1567,11 +1404,8 @@ export class Matterbridge extends EventEmitter {
1567
1404
  clearInterval(this.startMatterInterval);
1568
1405
  this.startMatterInterval = undefined;
1569
1406
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1570
- // Start the Matter server
1571
- await this.startMatterServer();
1572
- this.log.notice('Matter server started');
1573
- // Show the QR code for commissioning or log the already commissioned message
1574
- await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1407
+ // Start the Matter server node
1408
+ this.startServerNode(this.serverNode);
1575
1409
  // Configure the plugins
1576
1410
  this.configureTimeout = setTimeout(async () => {
1577
1411
  for (const plugin of this.plugins) {
@@ -1585,15 +1419,16 @@ export class Matterbridge extends EventEmitter {
1585
1419
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1586
1420
  }
1587
1421
  }
1588
- this.wssSendRefreshRequired();
1422
+ this.frontend.wssSendRefreshRequired();
1589
1423
  }, 30 * 1000);
1590
1424
  // Setting reachability to true
1591
1425
  this.reachabilityTimeout = setTimeout(() => {
1592
1426
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1593
- if (this.commissioningServer)
1594
- this.setCommissioningServerReachability(this.commissioningServer, true);
1595
- if (this.matterAggregator)
1596
- this.setAggregatorReachability(this.matterAggregator, true);
1427
+ if (this.serverNode)
1428
+ this.setServerNodeReachability(this.serverNode, true);
1429
+ if (this.aggregatorNode)
1430
+ this.setAggregatorReachability(this.aggregatorNode, true);
1431
+ this.frontend.wssSendRefreshRequired();
1597
1432
  }, 60 * 1000);
1598
1433
  }, 1000);
1599
1434
  }
@@ -1605,16 +1440,25 @@ export class Matterbridge extends EventEmitter {
1605
1440
  async startChildbridge() {
1606
1441
  // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1607
1442
  // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1608
- if (!this.storageManager)
1443
+ if (!this.matterStorageManager)
1609
1444
  throw new Error('No storage manager initialized');
1610
- this.matterServer = await this.createMatterServer(this.storageManager);
1445
+ for (const plugin of this.plugins) {
1446
+ if (!plugin.enabled)
1447
+ continue;
1448
+ if (plugin.type === 'DynamicPlatform') {
1449
+ plugin.locked = true;
1450
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
1451
+ plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1452
+ plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
1453
+ await plugin.serverNode.add(plugin.aggregatorNode);
1454
+ }
1455
+ }
1611
1456
  await this.startPlugins();
1612
1457
  this.log.debug('Starting start matter interval in childbridge mode...');
1613
1458
  let failCount = 0;
1614
1459
  this.startMatterInterval = setInterval(async () => {
1615
1460
  let allStarted = true;
1616
1461
  for (const plugin of this.plugins) {
1617
- // Prevents to start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1618
1462
  if (!plugin.enabled)
1619
1463
  continue;
1620
1464
  if (plugin.error) {
@@ -1642,9 +1486,6 @@ export class Matterbridge extends EventEmitter {
1642
1486
  clearInterval(this.startMatterInterval);
1643
1487
  this.startMatterInterval = undefined;
1644
1488
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1645
- // Start the Matter server
1646
- await this.startMatterServer();
1647
- this.log.notice('Matter server started');
1648
1489
  // Configure the plugins
1649
1490
  this.configureTimeout = setTimeout(async () => {
1650
1491
  for (const plugin of this.plugins) {
@@ -1658,7 +1499,7 @@ export class Matterbridge extends EventEmitter {
1658
1499
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1659
1500
  }
1660
1501
  }
1661
- this.wssSendRefreshRequired();
1502
+ this.frontend.wssSendRefreshRequired();
1662
1503
  }, 30 * 1000);
1663
1504
  for (const plugin of this.plugins) {
1664
1505
  if (!plugin.enabled || plugin.error)
@@ -1667,8 +1508,8 @@ export class Matterbridge extends EventEmitter {
1667
1508
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't add any devices to Matterbridge. Verify the plugin configuration.`);
1668
1509
  continue;
1669
1510
  }
1670
- if (!plugin.commissioningServer) {
1671
- this.log.error(`Commissioning server not found for plugin ${plg}${plugin.name}${er}`);
1511
+ if (!plugin.serverNode) {
1512
+ this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1672
1513
  continue;
1673
1514
  }
1674
1515
  if (!plugin.storageContext) {
@@ -1679,16 +1520,18 @@ export class Matterbridge extends EventEmitter {
1679
1520
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1680
1521
  continue;
1681
1522
  }
1682
- await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1523
+ // Start the Matter server node
1524
+ this.startServerNode(plugin.serverNode);
1683
1525
  // Setting reachability to true
1684
1526
  plugin.reachabilityTimeout = setTimeout(() => {
1685
1527
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1686
- if (plugin.commissioningServer)
1687
- this.setCommissioningServerReachability(plugin.commissioningServer, true);
1528
+ if (plugin.serverNode)
1529
+ this.setServerNodeReachability(plugin.serverNode, true);
1688
1530
  if (plugin.type === 'AccessoryPlatform' && plugin.device)
1689
1531
  this.setDeviceReachability(plugin.device, true);
1690
- if (plugin.type === 'DynamicPlatform' && plugin.aggregator)
1691
- this.setAggregatorReachability(plugin.aggregator, true);
1532
+ if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1533
+ this.setAggregatorReachability(plugin.aggregatorNode, true);
1534
+ this.frontend.wssSendRefreshRequired();
1692
1535
  }, 60 * 1000);
1693
1536
  }
1694
1537
  }, 1000);
@@ -1699,747 +1542,252 @@ export class Matterbridge extends EventEmitter {
1699
1542
  * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1700
1543
  */
1701
1544
  async startController() {
1545
+ /*
1702
1546
  if (!this.storageManager) {
1703
- this.log.error('No storage manager initialized');
1704
- await this.cleanup('No storage manager initialized');
1705
- return;
1547
+ this.log.error('No storage manager initialized');
1548
+ await this.cleanup('No storage manager initialized');
1549
+ return;
1706
1550
  }
1707
1551
  this.log.info('Creating context: mattercontrollerContext');
1708
1552
  this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
1709
1553
  if (!this.mattercontrollerContext) {
1710
- this.log.error('No storage context mattercontrollerContext initialized');
1711
- await this.cleanup('No storage context mattercontrollerContext initialized');
1712
- return;
1554
+ this.log.error('No storage context mattercontrollerContext initialized');
1555
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1556
+ return;
1713
1557
  }
1558
+
1714
1559
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1715
1560
  this.matterServer = await this.createMatterServer(this.storageManager);
1716
1561
  this.log.info('Creating matter commissioning controller');
1717
1562
  this.commissioningController = new CommissioningController({
1718
- autoConnect: false,
1563
+ autoConnect: false,
1719
1564
  });
1720
1565
  this.log.info('Adding matter commissioning controller to matter server');
1721
1566
  await this.matterServer.addCommissioningController(this.commissioningController);
1567
+
1722
1568
  this.log.info('Starting matter server');
1723
1569
  await this.matterServer.start();
1724
1570
  this.log.info('Matter server started');
1571
+
1725
1572
  if (hasParameter('pairingcode')) {
1726
- this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1727
- const pairingCode = getParameter('pairingcode');
1728
- const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get('ip') : undefined;
1729
- const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get('port') : undefined;
1730
- let longDiscriminator, setupPin, shortDiscriminator;
1731
- if (pairingCode !== undefined) {
1732
- const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1733
- shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1734
- longDiscriminator = undefined;
1735
- setupPin = pairingCodeCodec.passcode;
1736
- this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1737
- }
1738
- else {
1739
- longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1740
- if (longDiscriminator > 4095)
1741
- throw new Error('Discriminator value must be less than 4096');
1742
- setupPin = this.mattercontrollerContext.get('pin', 20202021);
1743
- }
1744
- if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1745
- throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1746
- }
1747
- const commissioningOptions = {
1748
- regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1749
- regulatoryCountryCode: 'XX',
1750
- };
1751
- const options = {
1752
- commissioning: commissioningOptions,
1753
- discovery: {
1754
- knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1755
- identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1756
- },
1757
- passcode: setupPin,
1758
- };
1759
- this.log.info('Commissioning with options:', options);
1760
- const nodeId = await this.commissioningController.commissionNode(options);
1761
- this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1762
- this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1573
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1574
+ const pairingCode = getParameter('pairingcode');
1575
+ const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
1576
+ const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
1577
+
1578
+ let longDiscriminator, setupPin, shortDiscriminator;
1579
+ if (pairingCode !== undefined) {
1580
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1581
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1582
+ longDiscriminator = undefined;
1583
+ setupPin = pairingCodeCodec.passcode;
1584
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1585
+ } else {
1586
+ longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1587
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1588
+ setupPin = this.mattercontrollerContext.get('pin', 20202021);
1589
+ }
1590
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1591
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1592
+ }
1593
+
1594
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1595
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1596
+ regulatoryCountryCode: 'XX',
1597
+ };
1598
+ const options = {
1599
+ commissioning: commissioningOptions,
1600
+ discovery: {
1601
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1602
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1603
+ },
1604
+ passcode: setupPin,
1605
+ } as NodeCommissioningOptions;
1606
+ this.log.info('Commissioning with options:', options);
1607
+ const nodeId = await this.commissioningController.commissionNode(options);
1608
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1609
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1763
1610
  } // (hasParameter('pairingcode'))
1611
+
1764
1612
  if (hasParameter('unpairall')) {
1765
- this.log.info('***Commissioning controller unpairing all nodes...');
1766
- const nodeIds = this.commissioningController.getCommissionedNodes();
1767
- for (const nodeId of nodeIds) {
1768
- this.log.info('***Commissioning controller unpairing node:', nodeId);
1769
- await this.commissioningController.removeNode(nodeId);
1770
- }
1771
- return;
1613
+ this.log.info('***Commissioning controller unpairing all nodes...');
1614
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1615
+ for (const nodeId of nodeIds) {
1616
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1617
+ await this.commissioningController.removeNode(nodeId);
1618
+ }
1619
+ return;
1772
1620
  }
1621
+
1773
1622
  if (hasParameter('discover')) {
1774
- // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1775
- // console.log(discover);
1623
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1624
+ // console.log(discover);
1776
1625
  }
1626
+
1777
1627
  if (!this.commissioningController.isCommissioned()) {
1778
- this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1779
- return;
1628
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1629
+ return;
1780
1630
  }
1631
+
1781
1632
  const nodeIds = this.commissioningController.getCommissionedNodes();
1782
1633
  this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1783
1634
  for (const nodeId of nodeIds) {
1784
- this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1785
- const node = await this.commissioningController.connectNode(nodeId, {
1786
- autoSubscribe: false,
1787
- attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) => this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1788
- eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) => this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1789
- stateInformationCallback: (peerNodeId, info) => {
1790
- switch (info) {
1791
- case NodeStateInformation.Connected:
1792
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1793
- break;
1794
- case NodeStateInformation.Disconnected:
1795
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1796
- break;
1797
- case NodeStateInformation.Reconnecting:
1798
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1799
- break;
1800
- case NodeStateInformation.WaitingForDeviceDiscovery:
1801
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1802
- break;
1803
- case NodeStateInformation.StructureChanged:
1804
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1805
- break;
1806
- case NodeStateInformation.Decommissioned:
1807
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1808
- break;
1809
- default:
1810
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1811
- break;
1812
- }
1813
- },
1814
- });
1815
- node.logStructure();
1816
- // Get the interaction client
1817
- this.log.info('Getting the interaction client');
1818
- const interactionClient = await node.getInteractionClient();
1819
- let cluster;
1820
- let attributes;
1821
- // Log BasicInformationCluster
1822
- cluster = BasicInformationCluster;
1823
- attributes = await interactionClient.getMultipleAttributes({
1824
- attributes: [{ clusterId: cluster.id }],
1825
- });
1826
- if (attributes.length > 0)
1827
- this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1828
- attributes.forEach((attribute) => {
1829
- this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1830
- });
1831
- // Log PowerSourceCluster
1832
- cluster = PowerSourceCluster;
1833
- attributes = await interactionClient.getMultipleAttributes({
1834
- attributes: [{ clusterId: cluster.id }],
1835
- });
1836
- if (attributes.length > 0)
1837
- this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1838
- attributes.forEach((attribute) => {
1839
- this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1840
- });
1841
- // Log ThreadNetworkDiagnostics
1842
- cluster = ThreadNetworkDiagnosticsCluster;
1843
- attributes = await interactionClient.getMultipleAttributes({
1844
- attributes: [{ clusterId: cluster.id }],
1845
- });
1846
- if (attributes.length > 0)
1847
- this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1848
- attributes.forEach((attribute) => {
1849
- this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1850
- });
1851
- // Log SwitchCluster
1852
- cluster = SwitchCluster;
1853
- attributes = await interactionClient.getMultipleAttributes({
1854
- attributes: [{ clusterId: cluster.id }],
1855
- });
1856
- if (attributes.length > 0)
1857
- this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1858
- attributes.forEach((attribute) => {
1859
- this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1860
- });
1861
- this.log.info('Subscribing to all attributes and events');
1862
- await node.subscribeAllAttributesAndEvents({
1863
- ignoreInitialTriggers: false,
1864
- attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) => this.log.info(`***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`),
1865
- eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1866
- this.log.info(`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`);
1867
- },
1868
- });
1869
- this.log.info('Subscribed to all attributes and events');
1635
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1636
+
1637
+ const node = await this.commissioningController.connectNode(nodeId, {
1638
+ autoSubscribe: false,
1639
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1640
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1641
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1642
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1643
+ stateInformationCallback: (peerNodeId, info) => {
1644
+ switch (info) {
1645
+ case NodeStateInformation.Connected:
1646
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1647
+ break;
1648
+ case NodeStateInformation.Disconnected:
1649
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1650
+ break;
1651
+ case NodeStateInformation.Reconnecting:
1652
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1653
+ break;
1654
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1655
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1656
+ break;
1657
+ case NodeStateInformation.StructureChanged:
1658
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1659
+ break;
1660
+ case NodeStateInformation.Decommissioned:
1661
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1662
+ break;
1663
+ default:
1664
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1665
+ break;
1666
+ }
1667
+ },
1668
+ });
1669
+
1670
+ node.logStructure();
1671
+
1672
+ // Get the interaction client
1673
+ this.log.info('Getting the interaction client');
1674
+ const interactionClient = await node.getInteractionClient();
1675
+ let cluster;
1676
+ let attributes;
1677
+
1678
+ // Log BasicInformationCluster
1679
+ cluster = BasicInformationCluster;
1680
+ attributes = await interactionClient.getMultipleAttributes({
1681
+ attributes: [{ clusterId: cluster.id }],
1682
+ });
1683
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1684
+ attributes.forEach((attribute) => {
1685
+ this.log.info(
1686
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1687
+ );
1688
+ });
1689
+
1690
+ // Log PowerSourceCluster
1691
+ cluster = PowerSourceCluster;
1692
+ attributes = await interactionClient.getMultipleAttributes({
1693
+ attributes: [{ clusterId: cluster.id }],
1694
+ });
1695
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1696
+ attributes.forEach((attribute) => {
1697
+ this.log.info(
1698
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1699
+ );
1700
+ });
1701
+
1702
+ // Log ThreadNetworkDiagnostics
1703
+ cluster = ThreadNetworkDiagnosticsCluster;
1704
+ attributes = await interactionClient.getMultipleAttributes({
1705
+ attributes: [{ clusterId: cluster.id }],
1706
+ });
1707
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1708
+ attributes.forEach((attribute) => {
1709
+ this.log.info(
1710
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1711
+ );
1712
+ });
1713
+
1714
+ // Log SwitchCluster
1715
+ cluster = SwitchCluster;
1716
+ attributes = await interactionClient.getMultipleAttributes({
1717
+ attributes: [{ clusterId: cluster.id }],
1718
+ });
1719
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1720
+ attributes.forEach((attribute) => {
1721
+ this.log.info(
1722
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1723
+ );
1724
+ });
1725
+
1726
+ this.log.info('Subscribing to all attributes and events');
1727
+ await node.subscribeAllAttributesAndEvents({
1728
+ ignoreInitialTriggers: false,
1729
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1730
+ this.log.info(
1731
+ `***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`,
1732
+ ),
1733
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1734
+ this.log.info(
1735
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1736
+ );
1737
+ },
1738
+ });
1739
+ this.log.info('Subscribed to all attributes and events');
1870
1740
  }
1741
+ */
1871
1742
  }
1872
1743
  /** ***********************************************************************************************************************************/
1873
1744
  /** Matter.js methods */
1874
1745
  /** ***********************************************************************************************************************************/
1875
1746
  /**
1876
1747
  * Starts the matter storage process based on the specified storage type and name.
1877
- * @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
1878
- * @param {string} storageName - The name of the storage file.
1879
1748
  * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1880
1749
  */
1881
- async startMatterStorage(storageType, storageName) {
1882
- this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1883
- if (storageType === 'disk') {
1884
- const storageDisk = new StorageBackendDisk(storageName);
1885
- this.storageManager = new StorageManager(storageDisk);
1886
- }
1887
- else if (storageType === 'json') {
1888
- if (!storageName.endsWith('.json'))
1889
- storageName += '.json';
1890
- const storageJson = new StorageBackendJsonFile(storageName);
1891
- this.storageManager = new StorageManager(storageJson);
1892
- }
1893
- else {
1894
- this.log.error(`Unsupported matter storage type ${storageType}`);
1895
- await this.cleanup('Unsupported matter storage type');
1896
- return;
1897
- }
1898
- try {
1899
- await this.storageManager.initialize();
1900
- this.log.debug('Matter storage initialized');
1901
- if (storageType === 'json') {
1902
- await this.backupMatterStorage(storageName, storageName.replace('.json', '') + '.backup.json');
1903
- }
1904
- }
1905
- catch (error) {
1906
- this.log.error(`Matter storage initialize error! The file .matterbridge/${storageName} may be corrupted: ${error instanceof Error ? error.message : error}`);
1907
- if (hasParameter('norestore')) {
1908
- this.log.fatal(`Please delete it and rename ${storageName.replace('.json', '.backup.json')} to ${storageName} and try to restart Matterbridge.`);
1909
- await this.cleanup('Matter storage initialize error and -norestore parameter found!');
1910
- return;
1911
- }
1912
- await this.restoreMatterStorage(storageName.replace('.json', '') + '.backup.json', storageName);
1913
- try {
1914
- await this.storageManager.initialize();
1915
- this.log.notice('Matter storage initialized from the backup file');
1916
- }
1917
- catch (error) {
1918
- this.log.error(`Matter storage initialize error! The backup file for .matterbridge/${storageName} may be corrupted too:`, error instanceof Error ? error.message : error);
1919
- await this.cleanup('Matter storage initialize error from backup!');
1920
- return;
1921
- }
1922
- }
1923
- this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1924
- this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', aggregator.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1925
- await this.matterbridgeContext.set('port', this.port);
1926
- await this.matterbridgeContext.set('passcode', this.passcode);
1927
- await this.matterbridgeContext.set('discriminator', this.discriminator);
1750
+ async startMatterStorage() {
1751
+ // Setup Matter storage
1752
+ this.log.info(`Starting matter node storage...`);
1753
+ this.matterStorageService = this.environment.get(StorageService);
1754
+ this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
1755
+ this.matterStorageManager = await this.matterStorageService.open('Matterbridge');
1756
+ this.log.info('Matter node storage manager "Matterbridge" created');
1757
+ this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
1758
+ this.log.info('Matter node storage started');
1759
+ // Backup matter storage since it is created/opened correctly
1760
+ await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1928
1761
  }
1929
1762
  /**
1930
- * Convert the old API matter storage to the new API format.
1931
- * @param {StorageContext} context - The context of Matterbridge or of the plugin.
1932
- * @param {string} pluginName - The name of the plugin or Matterbridge.
1933
- * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1934
- */
1935
- async convertStorage(context, pluginName) {
1936
- if (this.edge !== false)
1937
- return;
1938
- try {
1939
- const storageService = Environment.default.get(StorageService);
1940
- Environment.default.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage' + (this.profile ? '.' + this.profile : '')));
1941
- const nodeStorage = await storageService.open(pluginName);
1942
- if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount', -1)) >= 0) {
1943
- this.log.info(`Matter node storage already converted to Matterbridge edge for ${plg}${pluginName}${nf}`);
1944
- return;
1945
- }
1946
- else {
1947
- this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
1948
- }
1949
- // Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
1950
- const fabricManagerContext = context.createContext('FabricManager');
1951
- const fabrics = (await fabricManagerContext.get('fabrics', []));
1952
- const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
1953
- // Read EventHandler from the old storage
1954
- const eventHandlerContext = context.createContext('EventHandler');
1955
- // Read SessionManager from the old storage
1956
- const sessionManagerContext = context.createContext('SessionManager');
1957
- // Read EndpointStructure from the old storage
1958
- const endpointStructureContext = context.createContext('EndpointStructure');
1959
- // Read generalCommissioning from the old storage
1960
- const generalCommissioningContext = context.createContext('Cluster-0-48');
1961
- // Read basicInformation from the old storage
1962
- const basicInformationContext = context.createContext('Cluster-0-40');
1963
- const fabricInfo = {};
1964
- const fabricInfoArray = [];
1965
- const nocArray = [];
1966
- const trcArray = [];
1967
- const aclArray = [];
1968
- this.log.info(`Found ${CYAN}${fabrics.length}${nf} fabrics (nextFabricIndex ${CYAN}${nextFabricIndex}${nf}) for ${plg}${pluginName}${nf}:`);
1969
- if (fabrics.length === 0 || nextFabricIndex === 0) {
1970
- this.log.notice(`If you want to try out matterbridge edge add -edge to the command line and pair it to your controller(s).`);
1971
- return;
1972
- }
1973
- for (const fabric of fabrics) {
1974
- this.log.info(`- fabricIndex ${CYAN}${fabric.fabricIndex}${nf} fabricId ${CYAN}${fabric.fabricId}${nf} nodeId ${CYAN}${fabric.nodeId}${nf} rootNodeId ${CYAN}${fabric.rootNodeId}${nf} rootVendorId ${CYAN}${fabric.rootVendorId}${nf} label ${CYAN}${fabric.label}${nf}`);
1975
- fabricInfo[fabric.fabricIndex] = {
1976
- fabricIndex: fabric.fabricIndex,
1977
- fabricId: fabric.fabricId,
1978
- nodeId: fabric.nodeId,
1979
- rootNodeId: fabric.rootNodeId,
1980
- rootVendorId: fabric.rootVendorId,
1981
- label: fabric.label,
1982
- };
1983
- fabricInfoArray.push({
1984
- fabricIndex: fabric.fabricIndex,
1985
- fabricId: fabric.fabricId,
1986
- nodeId: fabric.nodeId,
1987
- vendorId: fabric.rootVendorId,
1988
- rootPublicKey: fabric.rootPublicKey,
1989
- label: fabric.label,
1990
- });
1991
- nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
1992
- trcArray.push(fabric.rootCert);
1993
- this.log.info(`- updating ACL for fabricIndex ${fabric.fabricIndex}:`, fabric.scopedClusterData);
1994
- const acl = fabric.scopedClusterData.get(0x1f)?.get('acl');
1995
- if (acl && acl.value.length > 0) {
1996
- aclArray.push(acl.value[0]);
1997
- this.log.info(`- ACL updated to ${debugStringify(acl.value)}${nf} for fabricIndex ${CYAN}${fabric.fabricIndex}${nf}`);
1998
- }
1999
- else {
2000
- const defaultAcl = { fabricIndex: fabric.fabricIndex, privilege: 5, authMode: 2, subjects: [fabric.rootNodeId], targets: null };
2001
- aclArray.push(defaultAcl);
2002
- this.log.info(`- ACL updated to default ${debugStringify(defaultAcl)}${nf} for fabricIndex ${CYAN}${fabric.fabricIndex}${nf}`);
2003
- }
2004
- }
2005
- await nodeStorage.createContext('fabrics').set('fabrics', fabrics);
2006
- await nodeStorage.createContext('fabrics').set('nextFabricIndex', nextFabricIndex);
2007
- await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
2008
- await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
2009
- await nodeStorage.createContext('root').set('__number__', 0);
2010
- await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
2011
- await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
2012
- await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
2013
- await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
2014
- await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
2015
- // operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
2016
- await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
2017
- // operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
2018
- await nodeStorage.createContext('root').createContext('operationalCredentials').set('trustedRootCertificates', trcArray);
2019
- // ACL updated, updating ACL manager { fabricIndex: 3, privilege: 5, authMode: 2, subjects: [ 18446744060825763897 ], targets: null }
2020
- // From fabric.rootNodeId if fabric.scopedClusterData.get(0x1f).get('acl') is empty
2021
- // [{"fabricIndex":3,"privilege":5,"authMode":2,"subjects":["{\"__object__\":\"BigInt\",\"__value__\":\"18446744060825763897\"}"],"targets":null}]
2022
- await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
2023
- await nodeStorage
2024
- .createContext('root')
2025
- .createContext('generalCommissioning')
2026
- .set('breadcrumb', await generalCommissioningContext.get('breadcrumb', BigInt(0)));
2027
- await nodeStorage
2028
- .createContext('root')
2029
- .createContext('basicInformation')
2030
- .set('location', await basicInformationContext.get('location', 'XX'));
2031
- await nodeStorage.createContext('root').createContext('network').set('ble', false);
2032
- await nodeStorage
2033
- .createContext('root')
2034
- .createContext('network')
2035
- .set('operationalPort', await context.get('port', 5540));
2036
- await nodeStorage
2037
- .createContext('root')
2038
- .createContext('productDescription')
2039
- .set('productId', await context.get('productId', 0x8000));
2040
- await nodeStorage
2041
- .createContext('root')
2042
- .createContext('productDescription')
2043
- .set('vendorId', await context.get('vendorId', 0xfff1));
2044
- /*
2045
- "Matterbridge.EndpointStructure": {
2046
- "unique_d60ca095a002f160-index_0": 1,
2047
- "unique_d60ca095a002f160-index_0-custom_Switch0": 2,
2048
- "unique_d60ca095a002f160-index_0-custom_Outlet0": 3,
2049
- "unique_d60ca095a002f160-index_0-custom_Light0": 4,
2050
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa": 2,
2051
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_PowerSource": 3,
2052
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:0": 4,
2053
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:1": 5,
2054
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:2": 6,
2055
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:3": 7,
2056
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:0": 8,
2057
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:1": 9,
2058
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:2": 10,
2059
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:3": 11,
2060
- "nextEndpointId": 5
2061
- },
2062
- */
2063
- const rootDeviceName = (await context.get('deviceName', '')).replace(/[ .]/g, '');
2064
- this.log.info(`Converting ${pluginName}.EndpointStructure to root.parts.${rootDeviceName}...`);
2065
- for (const key of await endpointStructureContext.keys()) {
2066
- if (key === 'nextEndpointId') {
2067
- await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
2068
- continue;
2069
- }
2070
- const parts = key.split('-');
2071
- const number = await endpointStructureContext.get(key);
2072
- if (parts.length === 2) {
2073
- this.log.debug(`Converting bridge Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.__number__:${CYAN}${number}${db}`);
2074
- await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).set('__number__', number);
2075
- }
2076
- else if (parts.length === 3 && parts[2].startsWith('unique_')) {
2077
- const device = this.devices.get(parts[2].replace('unique_', ''));
2078
- if (device && device.deviceName && device.maybeNumber) {
2079
- this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.__number__:${CYAN}${device.maybeNumber}${db}`);
2080
- await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(device.deviceName.replace(/[ .]/g, '')).set('__number__', device.maybeNumber);
2081
- }
2082
- }
2083
- else if (parts.length === 4 && parts[2].startsWith('unique_') && parts[3].startsWith('custom_')) {
2084
- const device = this.devices.get(parts[2].replace('unique_', ''));
2085
- if (device && device.deviceName && device.maybeNumber) {
2086
- const childEndpointName = parts[3].replace('custom_', '');
2087
- const childEndpoint = device.getChildEndpointByName(childEndpointName);
2088
- this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${childEndpoint?.number}${db}`);
2089
- await nodeStorage
2090
- .createContext('root')
2091
- .createContext('parts')
2092
- .createContext(rootDeviceName)
2093
- .createContext('parts')
2094
- .createContext(device.deviceName.replace(/[ .]/g, ''))
2095
- .createContext('parts')
2096
- .createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
2097
- .set('__number__', childEndpoint?.number);
2098
- }
2099
- }
2100
- else if (parts.length === 3 && parts[2].startsWith('custom_')) {
2101
- this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
2102
- await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(parts[2].replace('custom_', '').replace(/[ .]/g, '')).set('__number__', number);
2103
- }
2104
- else if (parts.length === 4 && parts[2].startsWith('custom_') && parts[3].startsWith('custom_')) {
2105
- this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
2106
- await nodeStorage
2107
- .createContext('root')
2108
- .createContext('parts')
2109
- .createContext(rootDeviceName)
2110
- .createContext('parts')
2111
- .createContext(parts[2].replace('custom_', '').replace(/[ .]/g, ''))
2112
- .createContext('parts')
2113
- .createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
2114
- .set('__number__', number);
2115
- }
2116
- }
2117
- await nodeStorage.createContext('persist').set('converted', true);
2118
- await nodeStorage.createContext('persist').set('deviceName', await context.get('deviceName'));
2119
- await nodeStorage.createContext('persist').set('deviceType', await context.get('deviceType'));
2120
- await nodeStorage.createContext('persist').set('vendorId', await context.get('vendorId'));
2121
- await nodeStorage.createContext('persist').set('vendorName', await context.get('vendorName'));
2122
- await nodeStorage.createContext('persist').set('productId', await context.get('productId'));
2123
- await nodeStorage.createContext('persist').set('productName', await context.get('productName'));
2124
- await nodeStorage.createContext('persist').set('nodeLabel', await context.get('nodeLabel'));
2125
- await nodeStorage.createContext('persist').set('productLabel', await context.get('productLabel'));
2126
- await nodeStorage.createContext('persist').set('serialNumber', 'SN' + (await context.get('serialNumber')));
2127
- await nodeStorage.createContext('persist').set('uniqueId', await context.get('uniqueId'));
2128
- await nodeStorage.createContext('persist').set('softwareVersion', await context.get('softwareVersion'));
2129
- await nodeStorage.createContext('persist').set('softwareVersionString', await context.get('softwareVersionString'));
2130
- await nodeStorage.createContext('persist').set('hardwareVersion', await context.get('hardwareVersion'));
2131
- await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
2132
- await context.set('converted', true);
2133
- this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
2134
- this.log.notice(`If you want to try out matterbridge edge add -edge to the command line.`);
2135
- this.log.notice(`All fabrics have been converted to the new storage format.`);
2136
- }
2137
- catch (error) {
2138
- this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
2139
- }
2140
- }
2141
- /**
2142
- * Makes a backup copy of the specified matter JSON storage file.
2143
- *
2144
- * @param storageName - The name of the JSON storage file to be backed up.
2145
- * @param backupName - The name of the backup file to be created.
1763
+ * Makes a backup copy of the specified matter storage directory.
1764
+ *
1765
+ * @param storageName - The name of the storage file to be backed up.
1766
+ * @param backupName - The name of the backup file to be created.
2146
1767
  */
2147
1768
  async backupMatterStorage(storageName, backupName) {
2148
- try {
2149
- this.log.debug(`Making backup copy of ${storageName}`);
2150
- await fs.copyFile(storageName, backupName);
2151
- this.log.debug(`Successfully backed up ${storageName} to ${backupName}`);
2152
- }
2153
- catch (err) {
2154
- if (err instanceof Error && 'code' in err) {
2155
- if (err.code === 'ENOENT') {
2156
- this.log.debug(`No existing file to back up for ${storageName}. This is expected on the first run.`);
2157
- }
2158
- else {
2159
- this.log.error(`Error making backup copy of ${storageName}: ${err.message}`);
2160
- }
2161
- }
2162
- else {
2163
- this.log.error(`An unexpected error occurred during the backup of ${storageName}: ${String(err)}`);
2164
- }
2165
- }
2166
- }
2167
- /**
2168
- * Restore the specified matter JSON storage file.
2169
- *
2170
- * @param backupName - The name of the backup file to restore from.
2171
- * @param storageName - The name of the JSON storage file to restored.
2172
- */
2173
- async restoreMatterStorage(backupName, storageName) {
2174
- try {
2175
- this.log.notice(`Restoring the backup copy of ${storageName}`);
2176
- await fs.copyFile(backupName, storageName);
2177
- this.log.notice(`Successfully restored ${backupName} to ${storageName}`);
2178
- }
2179
- catch (err) {
2180
- if (err instanceof Error && 'code' in err) {
2181
- if (err.code === 'ENOENT') {
2182
- this.log.info(`No existing file to restore: ${backupName}.`);
2183
- }
2184
- else {
2185
- this.log.error(`Error restoring ${backupName}: ${err.message}`);
2186
- }
2187
- }
2188
- else {
2189
- this.log.error(`An unexpected error occurred during the restore of ${backupName}: ${String(err)}`);
2190
- }
2191
- }
1769
+ this.log.info('Creating matter node storage backup...');
1770
+ await copyDirectory(storageName, backupName);
1771
+ this.log.info('Created matter node storage backup');
2192
1772
  }
2193
1773
  /**
2194
1774
  * Stops the matter storage.
2195
1775
  * @returns {Promise<void>} A promise that resolves when the storage is stopped.
2196
1776
  */
2197
1777
  async stopMatterStorage() {
2198
- this.log.debug('Stopping storage');
2199
- await this.storageManager?.close();
2200
- this.log.debug('Storage closed');
2201
- this.storageManager = undefined;
1778
+ this.log.info('Closing matter node storage...');
1779
+ this.matterStorageManager?.close();
1780
+ this.matterStorageService = undefined;
1781
+ this.matterStorageManager = undefined;
2202
1782
  this.matterbridgeContext = undefined;
2203
- this.mattercontrollerContext = undefined;
2204
- }
2205
- /**
2206
- * Creates a Matter server using the provided storage manager and the provided mdnsInterface.
2207
- * @param storageManager The storage manager to be used by the Matter server.
2208
- *
2209
- */
2210
- async createMatterServer(storageManager) {
2211
- this.log.debug('Creating matter server');
2212
- // Validate mdnsInterface
2213
- if (this.mdnsInterface) {
2214
- const networkInterfaces = os.networkInterfaces();
2215
- const availableInterfaces = Object.keys(networkInterfaces);
2216
- if (!availableInterfaces.includes(this.mdnsInterface)) {
2217
- this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
2218
- this.mdnsInterface = undefined;
2219
- }
2220
- else {
2221
- this.log.info(`Using mdnsInterface '${this.mdnsInterface}' for the Matter server MdnsBroadcaster.`);
2222
- }
2223
- }
2224
- const matterServer = new MatterServer(storageManager, { mdnsInterface: this.mdnsInterface });
2225
- this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
2226
- return matterServer;
2227
- }
2228
- /**
2229
- * Starts the Matter server.
2230
- * If the Matter server is not initialized, it logs an error and performs cleanup.
2231
- */
2232
- async startMatterServer() {
2233
- if (!this.matterServer) {
2234
- this.log.error('No matter server initialized');
2235
- await this.cleanup('No matter server initialized');
2236
- return;
2237
- }
2238
- this.log.debug('Starting matter server...');
2239
- await this.matterServer.start();
2240
- this.log.debug('Started matter server');
2241
- // this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
2242
- }
2243
- /**
2244
- * Stops the Matter server, commissioningServer and commissioningController.
2245
- */
2246
- async stopMatterServer() {
2247
- this.log.debug('Stopping matter commissioningServer');
2248
- await this.commissioningServer?.close();
2249
- this.log.debug('Stopping matter commissioningController');
2250
- await this.commissioningController?.close();
2251
- this.log.debug('Stopping matter server');
2252
- await this.matterServer?.close();
2253
- this.log.debug('Matter server closed');
2254
- this.commissioningController = undefined;
2255
- this.commissioningServer = undefined;
2256
- this.matterAggregator = undefined;
2257
- this.matterServer = undefined;
2258
- }
2259
- /**
2260
- * Creates a Matter Aggregator.
2261
- * @param {StorageContext} context - The storage context.
2262
- * @returns {Aggregator} - The created Matter Aggregator.
2263
- */
2264
- async createMatterAggregator(context, pluginName) {
2265
- this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
2266
- const matterAggregator = new Aggregator();
2267
- return matterAggregator;
2268
- }
2269
- /**
2270
- * Creates a matter commissioning server.
2271
- *
2272
- * @param {StorageContext} context - The storage context.
2273
- * @param {string} pluginName - The name of the commissioning server.
2274
- * @returns {CommissioningServer} The created commissioning server.
2275
- */
2276
- async createCommisioningServer(context, pluginName) {
2277
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
2278
- const deviceName = await context.get('deviceName');
2279
- const deviceType = await context.get('deviceType');
2280
- const vendorId = await context.get('vendorId');
2281
- const vendorName = await context.get('vendorName'); // Home app = Manufacturer
2282
- const productId = await context.get('productId');
2283
- const productName = await context.get('productName'); // Home app = Model
2284
- const serialNumber = await context.get('serialNumber');
2285
- const uniqueId = await context.get('uniqueId');
2286
- const softwareVersion = await context.get('softwareVersion', 1);
2287
- const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
2288
- const hardwareVersion = await context.get('hardwareVersion', 1);
2289
- const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
2290
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
2291
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with uniqueId ${uniqueId} serialNumber ${serialNumber}`);
2292
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
2293
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
2294
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${CYAN}${this.port}${db} discriminator ${CYAN}${this.discriminator}${db} passcode ${CYAN}${this.passcode}${db} `);
2295
- // Validate ipv4address
2296
- if (this.ipv4address) {
2297
- const networkInterfaces = os.networkInterfaces();
2298
- const availableAddresses = Object.values(networkInterfaces)
2299
- .flat()
2300
- .filter((iface) => iface !== undefined && iface.family === 'IPv4' && !iface.internal)
2301
- .map((iface) => iface.address);
2302
- if (!availableAddresses.includes(this.ipv4address)) {
2303
- this.log.error(`Invalid ipv4address: ${this.ipv4address}. Available addresses are: ${availableAddresses.join(', ')}. Using all available addresses.`);
2304
- this.mdnsInterface = undefined;
2305
- }
2306
- else {
2307
- this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
2308
- }
2309
- }
2310
- // Validate ipv6address
2311
- if (this.ipv6address) {
2312
- const networkInterfaces = os.networkInterfaces();
2313
- const availableAddresses = Object.values(networkInterfaces)
2314
- .flat()
2315
- .filter((iface) => iface !== undefined && iface.family === 'IPv6' && !iface.internal)
2316
- .map((iface) => iface.address);
2317
- if (!availableAddresses.includes(this.ipv6address)) {
2318
- this.log.error(`Invalid ipv6address: ${this.ipv6address}. Available addresses are: ${availableAddresses.join(', ')}. Using all available addresses.`);
2319
- this.mdnsInterface = undefined;
2320
- }
2321
- else {
2322
- this.log.info(`Using ipv6address '${this.ipv6address}' for the Matter commissioning server.`);
2323
- }
2324
- }
2325
- const commissioningServer = new CommissioningServer({
2326
- port: this.port++,
2327
- listeningAddressIpv4: this.ipv4address,
2328
- listeningAddressIpv6: this.ipv6address,
2329
- passcode: this.passcode,
2330
- discriminator: this.discriminator,
2331
- deviceName,
2332
- deviceType,
2333
- basicInformation: {
2334
- vendorId: VendorId(vendorId),
2335
- vendorName,
2336
- productId,
2337
- productName,
2338
- nodeLabel: productName,
2339
- productLabel: productName,
2340
- softwareVersion,
2341
- softwareVersionString, // Home app = Firmware Revision
2342
- hardwareVersion,
2343
- hardwareVersionString,
2344
- uniqueId,
2345
- serialNumber,
2346
- reachable: true,
2347
- },
2348
- activeSessionsChangedCallback: (fabricIndex) => {
2349
- const sessionInformations = commissioningServer.getActiveSessionInformation(fabricIndex);
2350
- let connected = false;
2351
- sessionInformations.forEach((session) => {
2352
- this.log.info(`Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
2353
- if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
2354
- this.log.notice(`Controller ${zb}${session.fabric?.rootVendorId}${nt} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} connected to ${plg}${pluginName}${nt} on session ${session.name}`);
2355
- connected = true;
2356
- }
2357
- });
2358
- if (connected) {
2359
- if (this.bridgeMode === 'bridge') {
2360
- this.matterbridgePaired = true;
2361
- this.matterbridgeConnected = true;
2362
- this.matterbridgeSessionInformations = this.sanitizeSessionInformation(sessionInformations);
2363
- }
2364
- if (this.bridgeMode === 'childbridge') {
2365
- const plugin = this.plugins.get(pluginName);
2366
- if (plugin) {
2367
- plugin.paired = true;
2368
- plugin.connected = true;
2369
- plugin.sessionInformations = this.sanitizeSessionInformation(sessionInformations);
2370
- }
2371
- }
2372
- }
2373
- else {
2374
- if (this.bridgeMode === 'bridge') {
2375
- this.matterbridgeSessionInformations = [];
2376
- }
2377
- if (this.bridgeMode === 'childbridge') {
2378
- const plugin = this.plugins.get(pluginName);
2379
- if (plugin) {
2380
- plugin.sessionInformations = [];
2381
- }
2382
- }
2383
- }
2384
- this.wssSendRefreshRequired();
2385
- },
2386
- commissioningChangedCallback: async (fabricIndex) => {
2387
- const fabricInfo = commissioningServer.getCommissionedFabricInformation(fabricIndex);
2388
- this.log.debug(`Commissioning changed on fabric ${zb}${fabricIndex}${db} for ${plg}${pluginName}${db}`, debugStringify(fabricInfo));
2389
- if (commissioningServer.getCommissionedFabricInformation().length === 0) {
2390
- this.log.warn(`Commissioning removed from fabric ${zb}${fabricIndex}${wr} for ${plg}${pluginName}${wr}. Resetting the commissioning server ...`);
2391
- await commissioningServer.factoryReset();
2392
- if (pluginName === 'Matterbridge') {
2393
- await this.matterbridgeContext?.clearAll();
2394
- this.matterbridgeFabricInformations = [];
2395
- this.matterbridgeSessionInformations = [];
2396
- this.matterbridgePaired = false;
2397
- this.matterbridgeConnected = false;
2398
- }
2399
- else {
2400
- for (const plugin of this.plugins) {
2401
- if (plugin.name === pluginName) {
2402
- await plugin.platform?.onShutdown('Commissioning removed by the controller');
2403
- plugin.fabricInformations = [];
2404
- plugin.sessionInformations = [];
2405
- plugin.paired = false;
2406
- plugin.connected = false;
2407
- await plugin.storageContext?.clearAll();
2408
- }
2409
- }
2410
- }
2411
- this.log.warn(`Restart to activate the pairing for ${plg}${pluginName}${wr}.`);
2412
- }
2413
- else {
2414
- const fabricInfo = commissioningServer.getCommissionedFabricInformation();
2415
- if (pluginName === 'Matterbridge') {
2416
- this.matterbridgeFabricInformations = this.sanitizeFabricInformations(fabricInfo);
2417
- this.matterbridgePaired = true;
2418
- }
2419
- else {
2420
- const plugin = this.plugins.get(pluginName);
2421
- if (plugin) {
2422
- plugin.fabricInformations = this.sanitizeFabricInformations(fabricInfo);
2423
- plugin.paired = true;
2424
- }
2425
- }
2426
- }
2427
- this.wssSendRefreshRequired();
2428
- },
2429
- });
2430
- if (this.passcode !== undefined)
2431
- this.passcode++;
2432
- if (this.discriminator !== undefined)
2433
- this.discriminator++;
2434
- commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
2435
- return commissioningServer;
1783
+ this.log.info('Matter node storage closed');
2436
1784
  }
2437
1785
  /**
2438
- * Creates a commissioning server storage context.
1786
+ * Creates a server node storage context.
2439
1787
  *
2440
1788
  * @param pluginName - The name of the plugin.
2441
1789
  * @param deviceName - The name of the device.
2442
- * @param deviceType - The type of the device.
1790
+ * @param deviceType - The deviceType of the device.
2443
1791
  * @param vendorId - The vendor ID.
2444
1792
  * @param vendorName - The vendor name.
2445
1793
  * @param productId - The product ID.
@@ -2452,12 +1800,14 @@ export class Matterbridge extends EventEmitter {
2452
1800
  * @param hardwareVersionString - The hardware version string of the device (optional).
2453
1801
  * @returns The storage context for the commissioning server.
2454
1802
  */
2455
- async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
2456
- if (!this.storageManager)
2457
- throw new Error('No storage manager initialized');
2458
- this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
1803
+ async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1804
+ if (!this.matterStorageService)
1805
+ throw new Error('No storage service initialized');
1806
+ this.log.info(`Creating server node storage context "${pluginName}.persist" for ${pluginName}...`);
1807
+ const storageManager = await this.matterStorageService.open(pluginName);
1808
+ const storageContext = storageManager.createContext('persist');
2459
1809
  const random = randomBytes(8).toString('hex');
2460
- const storageContext = this.storageManager.createContext(pluginName);
1810
+ await storageContext.set('storeId', pluginName);
2461
1811
  await storageContext.set('deviceName', deviceName);
2462
1812
  await storageContext.set('deviceType', deviceType);
2463
1813
  await storageContext.set('vendorId', vendorId);
@@ -2466,125 +1816,317 @@ export class Matterbridge extends EventEmitter {
2466
1816
  await storageContext.set('productName', productName.slice(0, 32));
2467
1817
  await storageContext.set('nodeLabel', productName.slice(0, 32));
2468
1818
  await storageContext.set('productLabel', productName.slice(0, 32));
2469
- await storageContext.set('serialNumber', await storageContext.get('serialNumber', random));
2470
- await storageContext.set('uniqueId', await storageContext.get('uniqueId', random));
2471
- await storageContext.set('softwareVersion', this.matterbridgeVersion && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
2472
- await storageContext.set('softwareVersionString', this.matterbridgeVersion ?? '1.0.0');
2473
- await storageContext.set('hardwareVersion', this.systemInformation.osRelease && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
2474
- await storageContext.set('hardwareVersionString', this.systemInformation.osRelease ?? '1.0.0');
2475
- this.log.debug(`Created commissioning server storage context for ${plg}${pluginName}${db}`);
2476
- this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
2477
- this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
1819
+ await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
1820
+ await storageContext.set('uniqueId', await storageContext.get('uniqueId', 'UI' + random));
1821
+ await storageContext.set('softwareVersion', this.matterbridgeVersion !== '' && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
1822
+ await storageContext.set('softwareVersionString', this.matterbridgeVersion !== '' ? this.matterbridgeVersion : '1.0.0');
1823
+ await storageContext.set('hardwareVersion', this.systemInformation.osRelease !== '' && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
1824
+ await storageContext.set('hardwareVersionString', this.systemInformation.osRelease !== '' ? this.systemInformation.osRelease : '1.0.0');
1825
+ this.log.debug(`Created server node storage context "${pluginName}.persist" for ${pluginName}:`);
1826
+ this.log.debug(`- storeId: ${await storageContext.get('storeId')}`);
1827
+ this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
1828
+ this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
1829
+ this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
1830
+ this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
2478
1831
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
2479
1832
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2480
1833
  return storageContext;
2481
1834
  }
2482
- /**
2483
- * Imports the commissioning server context for a specific plugin and device.
2484
- * @param pluginName - The name of the plugin.
2485
- * @param device - The MatterbridgeDevice object representing the device.
2486
- * @returns The commissioning server context.
2487
- * @throws Error if the BasicInformationCluster is not found.
2488
- */
2489
- async importCommissioningServerContext(pluginName, device) {
2490
- this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
2491
- const basic = device.getClusterServer(BasicInformationCluster);
2492
- if (!basic) {
2493
- this.log.error('importCommissioningServerContext error: cannot find the BasicInformationCluster');
2494
- process.exit(1);
2495
- }
2496
- if (!this.storageManager) {
2497
- this.log.error('importCommissioningServerContext error: no storage manager initialized');
2498
- process.exit(1);
2499
- }
2500
- this.log.debug(`Importing commissioning server storage context for ${plg}${pluginName}${db}`);
2501
- const storageContext = this.storageManager.createContext(pluginName);
2502
- await storageContext.set('deviceName', basic.getNodeLabelAttribute());
2503
- await storageContext.set('deviceType', DeviceTypeId(device.deviceType));
2504
- await storageContext.set('vendorId', basic.getVendorIdAttribute());
2505
- await storageContext.set('vendorName', basic.getVendorNameAttribute());
2506
- await storageContext.set('productId', basic.getProductIdAttribute());
2507
- await storageContext.set('productName', basic.getProductNameAttribute());
2508
- await storageContext.set('nodeLabel', basic.getNodeLabelAttribute());
2509
- await storageContext.set('productLabel', basic.getNodeLabelAttribute());
2510
- await storageContext.set('serialNumber', basic.attributes.serialNumber?.getLocal());
2511
- await storageContext.set('uniqueId', basic.attributes.uniqueId?.getLocal());
2512
- await storageContext.set('softwareVersion', basic.getSoftwareVersionAttribute());
2513
- await storageContext.set('softwareVersionString', basic.getSoftwareVersionStringAttribute());
2514
- await storageContext.set('hardwareVersion', basic.getHardwareVersionAttribute());
2515
- await storageContext.set('hardwareVersionString', basic.getHardwareVersionStringAttribute());
2516
- this.log.debug(`Imported commissioning server storage context for ${plg}${pluginName}${db}`);
2517
- this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
2518
- this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
1835
+ async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1836
+ const storeId = await storageContext.get('storeId');
1837
+ this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
1838
+ this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
1839
+ this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
1840
+ this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
1841
+ this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
2519
1842
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
2520
1843
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2521
- return storageContext;
2522
- }
2523
- /**
2524
- * Shows the commissioning server QR code for a given plugin.
2525
- * @param {CommissioningServer} commissioningServer - The commissioning server instance.
2526
- * @param {StorageContext} storageContext - The storage context instance.
2527
- * @param {NodeStorage} nodeContext - The node storage instance.
2528
- * @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
2529
- * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
2530
- */
2531
- async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
2532
- if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
2533
- this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
2534
- await this.cleanup('No storage initialized in showCommissioningQRCode');
2535
- return;
2536
- }
2537
- if (!commissioningServer.isCommissioned()) {
2538
- const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
2539
- const QrCode = new QrCodeSchema();
2540
- this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code:\n\n`);
2541
- // eslint-disable-next-line no-console
2542
- if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
2543
- console.log(`${QrCode.encode(qrPairingCode)}\n`);
2544
- this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
2545
- if (pluginName === 'Matterbridge') {
2546
- this.matterbridgeQrPairingCode = qrPairingCode;
2547
- this.matterbridgeManualPairingCode = manualPairingCode;
1844
+ /**
1845
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1846
+ */
1847
+ const serverNode = await ServerNode.create({
1848
+ // Required: Give the Node a unique ID which is used to store the state of this node
1849
+ id: storeId,
1850
+ // Provide Network relevant configuration like the port
1851
+ // Optional when operating only one device on a host, Default port is 5540
1852
+ network: {
1853
+ listeningAddressIpv4: this.ipv4address,
1854
+ listeningAddressIpv6: this.ipv6address,
1855
+ port,
1856
+ },
1857
+ // Provide Commissioning relevant settings
1858
+ // Optional for development/testing purposes
1859
+ commissioning: {
1860
+ passcode,
1861
+ discriminator,
1862
+ },
1863
+ // Provide Node announcement settings
1864
+ // Optional: If Ommitted some development defaults are used
1865
+ productDescription: {
1866
+ name: await storageContext.get('deviceName'),
1867
+ deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1868
+ },
1869
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1870
+ // Optional: If Omitted some development defaults are used
1871
+ basicInformation: {
1872
+ vendorId: VendorId(await storageContext.get('vendorId')),
1873
+ vendorName: await storageContext.get('vendorName'),
1874
+ productId: await storageContext.get('productId'),
1875
+ productName: await storageContext.get('productName'),
1876
+ productLabel: await storageContext.get('productName'),
1877
+ nodeLabel: await storageContext.get('productName'),
1878
+ serialNumber: await storageContext.get('serialNumber'),
1879
+ uniqueId: await storageContext.get('uniqueId'),
1880
+ softwareVersion: await storageContext.get('softwareVersion'),
1881
+ softwareVersionString: await storageContext.get('softwareVersionString'),
1882
+ hardwareVersion: await storageContext.get('hardwareVersion'),
1883
+ hardwareVersionString: await storageContext.get('hardwareVersionString'),
1884
+ },
1885
+ });
1886
+ const sanitizeFabrics = (fabrics) => {
1887
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1888
+ const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1889
+ this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1890
+ if (this.bridgeMode === 'bridge') {
1891
+ this.matterbridgeFabricInformations = sanitizedFabrics;
1892
+ this.matterbridgeSessionInformations = [];
1893
+ this.matterbridgePaired = true;
1894
+ }
1895
+ if (this.bridgeMode === 'childbridge') {
1896
+ const plugin = this.plugins.get(storeId);
1897
+ if (plugin) {
1898
+ plugin.fabricInformations = sanitizedFabrics;
1899
+ plugin.sessionInformations = [];
1900
+ plugin.paired = true;
1901
+ }
1902
+ }
1903
+ };
1904
+ /**
1905
+ * This event is triggered when the device is initially commissioned successfully.
1906
+ * This means: It is added to the first fabric.
1907
+ */
1908
+ serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
1909
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1910
+ serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1911
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1912
+ serverNode.lifecycle.online.on(() => {
1913
+ this.log.notice(`Server node for ${storeId} is online`);
1914
+ if (!serverNode.lifecycle.isCommissioned) {
1915
+ this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission ...`);
1916
+ const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
1917
+ if (this.bridgeMode === 'bridge') {
1918
+ this.matterbridgeQrPairingCode = qrPairingCode;
1919
+ this.matterbridgeManualPairingCode = manualPairingCode;
1920
+ this.matterbridgeFabricInformations = [];
1921
+ this.matterbridgeSessionInformations = [];
1922
+ this.matterbridgePaired = false;
1923
+ this.matterbridgeConnected = false;
1924
+ this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
1925
+ this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1926
+ }
1927
+ if (this.bridgeMode === 'childbridge') {
1928
+ const plugin = this.plugins.get(storeId);
1929
+ if (plugin) {
1930
+ plugin.qrPairingCode = qrPairingCode;
1931
+ plugin.manualPairingCode = manualPairingCode;
1932
+ plugin.fabricInformations = [];
1933
+ plugin.sessionInformations = [];
1934
+ plugin.paired = false;
1935
+ plugin.connected = false;
1936
+ this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
1937
+ this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1938
+ }
1939
+ }
1940
+ }
1941
+ else {
1942
+ this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
1943
+ sanitizeFabrics(serverNode.state.commissioning.fabrics);
1944
+ }
1945
+ this.frontend.wssSendRefreshRequired();
1946
+ });
1947
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1948
+ serverNode.lifecycle.offline.on(() => {
1949
+ this.log.notice(`Server node for ${storeId} is offline`);
1950
+ if (this.bridgeMode === 'bridge') {
1951
+ this.matterbridgeQrPairingCode = undefined;
1952
+ this.matterbridgeManualPairingCode = undefined;
2548
1953
  this.matterbridgeFabricInformations = [];
2549
1954
  this.matterbridgeSessionInformations = [];
2550
1955
  this.matterbridgePaired = false;
2551
1956
  this.matterbridgeConnected = false;
2552
1957
  }
2553
- if (pluginName !== 'Matterbridge') {
2554
- const plugin = this.plugins.get(pluginName);
1958
+ this.frontend.wssSendRefreshRequired();
1959
+ });
1960
+ /**
1961
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
1962
+ * information is needed.
1963
+ */
1964
+ serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1965
+ let action = '';
1966
+ switch (fabricAction) {
1967
+ case FabricAction.Added:
1968
+ action = 'added';
1969
+ break;
1970
+ case FabricAction.Removed:
1971
+ action = 'removed';
1972
+ break;
1973
+ case FabricAction.Updated:
1974
+ action = 'updated';
1975
+ break;
1976
+ }
1977
+ this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1978
+ sanitizeFabrics(serverNode.state.commissioning.fabrics);
1979
+ this.frontend.wssSendRefreshRequired();
1980
+ });
1981
+ const sanitizeSessions = (sessions) => {
1982
+ const sanitizedSessions = this.sanitizeSessionInformation(sessions.map((session) => ({
1983
+ ...session,
1984
+ secure: session.name.startsWith('secure'),
1985
+ })));
1986
+ this.log.debug(`Sessions: ${debugStringify(sanitizedSessions)}`);
1987
+ if (this.bridgeMode === 'bridge') {
1988
+ this.matterbridgeSessionInformations = sanitizedSessions;
1989
+ }
1990
+ if (this.bridgeMode === 'childbridge') {
1991
+ const plugin = this.plugins.get(storeId);
2555
1992
  if (plugin) {
2556
- plugin.qrPairingCode = qrPairingCode;
2557
- plugin.manualPairingCode = manualPairingCode;
2558
- plugin.fabricInformations = [];
2559
- plugin.sessionInformations = [];
2560
- plugin.paired = false;
2561
- plugin.connected = false;
1993
+ plugin.sessionInformations = sanitizedSessions;
2562
1994
  }
2563
1995
  }
1996
+ };
1997
+ /**
1998
+ * This event is triggered when an operative new session was opened by a Controller.
1999
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2000
+ */
2001
+ serverNode.events.sessions.opened.on((session) => {
2002
+ this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
2003
+ sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
2004
+ this.frontend.wssSendRefreshRequired();
2005
+ });
2006
+ /**
2007
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2008
+ */
2009
+ serverNode.events.sessions.closed.on((session) => {
2010
+ this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
2011
+ sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
2012
+ this.frontend.wssSendRefreshRequired();
2013
+ });
2014
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
2015
+ serverNode.events.sessions.subscriptionsChanged.on((session) => {
2016
+ this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
2017
+ sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
2018
+ this.frontend.wssSendRefreshRequired();
2019
+ });
2020
+ this.log.info(`Created server node for ${storeId}`);
2021
+ return serverNode;
2022
+ }
2023
+ async startServerNode(matterServerNode) {
2024
+ if (!matterServerNode)
2025
+ return;
2026
+ this.log.notice(`Starting ${matterServerNode.id} server node`);
2027
+ await matterServerNode.start();
2028
+ }
2029
+ async stopServerNode(matterServerNode) {
2030
+ if (!matterServerNode)
2031
+ return;
2032
+ this.log.notice(`Closing ${matterServerNode.id} server node`);
2033
+ await matterServerNode.close();
2034
+ }
2035
+ async createAggregatorNode(storageContext) {
2036
+ this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
2037
+ const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
2038
+ return aggregatorNode;
2039
+ }
2040
+ async addBridgedEndpoint(pluginName, device) {
2041
+ // Check if the plugin is registered
2042
+ const plugin = this.plugins.get(pluginName);
2043
+ if (!plugin) {
2044
+ this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
2045
+ return;
2564
2046
  }
2565
- else {
2566
- this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is already commissioned. Waiting for controllers to connect ...`);
2567
- const fabricInfo = commissioningServer.getCommissionedFabricInformation();
2568
- if (fabricInfo.length > 0)
2569
- this.log.info('Commissioned fabric information:');
2570
- fabricInfo?.forEach((info) => {
2571
- this.log.info(`- fabric index ${zb}${info.fabricIndex}${nf} id ${zb}${info.fabricId}${nf} vendor ${zb}${info.rootVendorId}${nf} ${this.getVendorIdName(info.rootVendorId)} ${info.label}`);
2572
- });
2573
- if (pluginName === 'Matterbridge') {
2574
- this.matterbridgeFabricInformations = this.sanitizeFabricInformations(fabricInfo);
2575
- this.matterbridgeSessionInformations = [];
2576
- this.matterbridgePaired = true;
2047
+ // Register and add the device to the matterbridge aggregator node
2048
+ if (this.bridgeMode === 'bridge') {
2049
+ this.log.debug(`Adding ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
2050
+ if (!this.aggregatorNode)
2051
+ this.log.error('Aggregator node not found for Matterbridge');
2052
+ await this.aggregatorNode?.add(device);
2053
+ }
2054
+ else if (this.bridgeMode === 'childbridge') {
2055
+ if (plugin.type === 'AccessoryPlatform') {
2056
+ this.createAccessoryPlugin(plugin, device);
2577
2057
  }
2578
- if (pluginName !== 'Matterbridge') {
2579
- const plugin = this.plugins.get(pluginName);
2580
- if (plugin) {
2581
- plugin.fabricInformations = this.sanitizeFabricInformations(fabricInfo);
2582
- plugin.sessionInformations = [];
2583
- plugin.paired = true;
2058
+ if (plugin.type === 'DynamicPlatform') {
2059
+ plugin.locked = true;
2060
+ this.log.debug(`Adding ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} aggregator node`);
2061
+ if (!plugin.aggregatorNode)
2062
+ this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${db}`);
2063
+ await plugin.aggregatorNode?.add(device);
2064
+ }
2065
+ }
2066
+ if (plugin.registeredDevices !== undefined)
2067
+ plugin.registeredDevices++;
2068
+ if (plugin.addedDevices !== undefined)
2069
+ plugin.addedDevices++;
2070
+ // Add the device to the DeviceManager
2071
+ this.devices.set(device);
2072
+ this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
2073
+ }
2074
+ async removeBridgedEndpoint(pluginName, device) {
2075
+ this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2076
+ // Check if the plugin is registered
2077
+ const plugin = this.plugins.get(pluginName);
2078
+ if (!plugin) {
2079
+ this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
2080
+ return;
2081
+ }
2082
+ // Register and add the device to the matterbridge aggregator node
2083
+ if (this.bridgeMode === 'bridge') {
2084
+ if (!this.aggregatorNode) {
2085
+ this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
2086
+ return;
2087
+ }
2088
+ await device.delete();
2089
+ this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
2090
+ if (plugin.registeredDevices !== undefined)
2091
+ plugin.registeredDevices--;
2092
+ if (plugin.addedDevices !== undefined)
2093
+ plugin.addedDevices--;
2094
+ }
2095
+ else if (this.bridgeMode === 'childbridge') {
2096
+ if (plugin.type === 'AccessoryPlatform') {
2097
+ // Nothing to do here since the server node has no aggregator node but only the device itself
2098
+ }
2099
+ else if (plugin.type === 'DynamicPlatform') {
2100
+ if (!plugin.aggregatorNode) {
2101
+ this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
2102
+ return;
2103
+ }
2104
+ await device.delete();
2105
+ }
2106
+ this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
2107
+ if (plugin.registeredDevices !== undefined)
2108
+ plugin.registeredDevices--;
2109
+ if (plugin.addedDevices !== undefined)
2110
+ plugin.addedDevices--;
2111
+ // Close the server node TODO check if this is correct
2112
+ if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
2113
+ if (plugin.serverNode) {
2114
+ await this.stopServerNode(plugin.serverNode);
2115
+ plugin.locked = false;
2116
+ plugin.aggregatorNode = undefined;
2117
+ plugin.serverNode = undefined;
2118
+ this.log.info(`Stopped server node for plugin ${plg}${pluginName}${nf}`);
2584
2119
  }
2585
2120
  }
2586
2121
  }
2587
- this.wssSendRefreshRequired();
2122
+ // Remove the device from the DeviceManager
2123
+ this.devices.remove(device);
2124
+ }
2125
+ async removeAllBridgedEndpoints(pluginName) {
2126
+ this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
2127
+ for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
2128
+ await this.removeBridgedEndpoint(pluginName, device);
2129
+ }
2588
2130
  }
2589
2131
  /**
2590
2132
  * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
@@ -2639,47 +2181,50 @@ export class Matterbridge extends EventEmitter {
2639
2181
  });
2640
2182
  }
2641
2183
  /**
2642
- * Sets the reachability of a commissioning server and trigger.
2184
+ * Sets the reachability of a matter server node and trigger ReachableChanged event.
2643
2185
  *
2644
- * @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
2186
+ * @param {ServerNode<ServerNode.RootEndpoint>} serverNode - The commissioning server to set the reachability for.
2645
2187
  * @param {boolean} reachable - The new reachability status.
2646
2188
  */
2647
- setCommissioningServerReachability(commissioningServer, reachable) {
2189
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2190
+ setServerNodeReachability(serverNode, reachable) {
2191
+ /*
2648
2192
  const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
2649
- if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
2650
- basicInformationCluster.setReachableAttribute(reachable);
2651
- if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2652
- basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2193
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2194
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2195
+ */
2653
2196
  }
2654
2197
  /**
2655
2198
  * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2656
- * @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
2199
+ * @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The matter aggregator to set the reachability for.
2657
2200
  * @param {boolean} reachable - A boolean indicating the reachability status to set.
2658
2201
  */
2659
- setAggregatorReachability(matterAggregator, reachable) {
2202
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2203
+ setAggregatorReachability(aggregatorNode, reachable) {
2204
+ /*
2660
2205
  const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
2661
- if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
2662
- basicInformationCluster.setReachableAttribute(reachable);
2663
- if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2664
- basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2206
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2207
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2665
2208
  matterAggregator.getBridgedDevices().forEach((device) => {
2666
- this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
2667
- device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
2668
- device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2209
+ this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
2210
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
2211
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2669
2212
  });
2213
+ */
2670
2214
  }
2671
2215
  /**
2672
2216
  * Sets the reachability of a device and trigger.
2673
2217
  *
2674
- * @param {MatterbridgeDevice} device - The device to set the reachability for.
2218
+ * @param {MatterbridgeEndpoint} device - The device to set the reachability for.
2675
2219
  * @param {boolean} reachable - The new reachability status of the device.
2676
2220
  */
2221
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2677
2222
  setDeviceReachability(device, reachable) {
2223
+ /*
2678
2224
  const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
2679
- if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
2680
- basicInformationCluster.setReachableAttribute(reachable);
2681
- if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2682
- basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2225
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2226
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2227
+ */
2683
2228
  }
2684
2229
  getVendorIdName = (vendorId) => {
2685
2230
  if (!vendorId)
@@ -2722,41 +2267,6 @@ export class Matterbridge extends EventEmitter {
2722
2267
  }
2723
2268
  return vendorName;
2724
2269
  };
2725
- /**
2726
- * Retrieves the base registered plugins sanitized for res.json().
2727
- * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
2728
- */
2729
- async getBaseRegisteredPlugins() {
2730
- const baseRegisteredPlugins = [];
2731
- for (const plugin of this.plugins) {
2732
- baseRegisteredPlugins.push({
2733
- path: plugin.path,
2734
- type: plugin.type,
2735
- name: plugin.name,
2736
- version: plugin.version,
2737
- description: plugin.description,
2738
- author: plugin.author,
2739
- latestVersion: plugin.latestVersion,
2740
- locked: plugin.locked,
2741
- error: plugin.error,
2742
- enabled: plugin.enabled,
2743
- loaded: plugin.loaded,
2744
- started: plugin.started,
2745
- configured: plugin.configured,
2746
- paired: plugin.paired,
2747
- connected: plugin.connected,
2748
- fabricInformations: plugin.fabricInformations,
2749
- sessionInformations: plugin.sessionInformations,
2750
- registeredDevices: plugin.registeredDevices,
2751
- addedDevices: plugin.addedDevices,
2752
- qrPairingCode: plugin.qrPairingCode,
2753
- manualPairingCode: plugin.manualPairingCode,
2754
- configJson: plugin.configJson,
2755
- schemaJson: plugin.schemaJson,
2756
- });
2757
- }
2758
- return baseRegisteredPlugins;
2759
- }
2760
2270
  /**
2761
2271
  * Spawns a child process with the given command and arguments.
2762
2272
  * @param {string} command - The command to execute.
@@ -2801,7 +2311,7 @@ export class Matterbridge extends EventEmitter {
2801
2311
  reject(err);
2802
2312
  });
2803
2313
  childProcess.on('close', (code, signal) => {
2804
- this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
2314
+ this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
2805
2315
  if (code === 0) {
2806
2316
  if (cmdLine.startsWith('npm install -g'))
2807
2317
  this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
@@ -2814,7 +2324,7 @@ export class Matterbridge extends EventEmitter {
2814
2324
  }
2815
2325
  });
2816
2326
  childProcess.on('exit', (code, signal) => {
2817
- this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
2327
+ this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
2818
2328
  if (code === 0) {
2819
2329
  this.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
2820
2330
  resolve(true);
@@ -2832,1060 +2342,17 @@ export class Matterbridge extends EventEmitter {
2832
2342
  childProcess.stdout.on('data', (data) => {
2833
2343
  const message = data.toString().trim();
2834
2344
  this.log.debug(`Spawn output (stdout): ${message}`);
2835
- this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
2345
+ this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
2836
2346
  });
2837
2347
  }
2838
2348
  if (childProcess.stderr) {
2839
2349
  childProcess.stderr.on('data', (data) => {
2840
2350
  const message = data.toString().trim();
2841
2351
  this.log.debug(`Spawn verbose (stderr): ${message}`);
2842
- this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
2843
- });
2844
- }
2845
- });
2846
- }
2847
- /**
2848
- * Sends a WebSocket message to all connected clients.
2849
- *
2850
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2851
- * @param {string} time - The time string of the message
2852
- * @param {string} name - The logger name of the message
2853
- * @param {string} message - The content of the message.
2854
- */
2855
- wssSendMessage(level, time, name, message) {
2856
- if (!level || !time || !name || !message)
2857
- return;
2858
- // Remove ANSI escape codes from the message
2859
- // eslint-disable-next-line no-control-regex
2860
- message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2861
- // Remove leading asterisks from the message
2862
- message = message.replace(/^\*+/, '');
2863
- // Replace all occurrences of \t and \n
2864
- message = message.replace(/[\t\n]/g, '');
2865
- // Remove non-printable characters
2866
- // eslint-disable-next-line no-control-regex
2867
- message = message.replace(/[\x00-\x1F\x7F]/g, '');
2868
- // Replace all occurrences of \" with "
2869
- message = message.replace(/\\"/g, '"');
2870
- // Define the maximum allowed length for continuous characters without a space
2871
- const maxContinuousLength = 100;
2872
- const keepStartLength = 20;
2873
- const keepEndLength = 20;
2874
- // Split the message into words
2875
- message = message
2876
- .split(' ')
2877
- .map((word) => {
2878
- // If the word length exceeds the max continuous length, insert spaces and truncate
2879
- if (word.length > maxContinuousLength) {
2880
- return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2881
- }
2882
- return word;
2883
- })
2884
- .join(' ');
2885
- // Send the message to all connected clients
2886
- this.webSocketServer?.clients.forEach((client) => {
2887
- if (client.readyState === WebSocket.OPEN) {
2888
- client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2889
- }
2890
- });
2891
- }
2892
- /**
2893
- * Sends a need to refresh WebSocket message to all connected clients.
2894
- *
2895
- */
2896
- wssSendRefreshRequired() {
2897
- this.matterbridgeInformation.refreshRequired = true;
2898
- // Send the message to all connected clients
2899
- this.webSocketServer?.clients.forEach((client) => {
2900
- if (client.readyState === WebSocket.OPEN) {
2901
- client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
2902
- }
2903
- });
2904
- }
2905
- /**
2906
- * Sends a need to restart WebSocket message to all connected clients.
2907
- *
2908
- */
2909
- wssSendRestartRequired() {
2910
- this.matterbridgeInformation.restartRequired = true;
2911
- // Send the message to all connected clients
2912
- this.webSocketServer?.clients.forEach((client) => {
2913
- if (client.readyState === WebSocket.OPEN) {
2914
- client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
2915
- }
2916
- });
2917
- }
2918
- /**
2919
- * Initializes the frontend of Matterbridge.
2920
- *
2921
- * @param port The port number to run the frontend server on. Default is 8283.
2922
- */
2923
- async initializeFrontend(port = 8283) {
2924
- let initializeError = false;
2925
- this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
2926
- // Create the express app that serves the frontend
2927
- this.expressApp = express();
2928
- // Log all requests to the server for debugging
2929
- /*
2930
- if (hasParameter('homedir')) {
2931
- this.expressApp.use((req, res, next) => {
2932
- this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
2933
- next();
2934
- });
2935
- }
2936
- */
2937
- // Serve static files from '/static' endpoint
2938
- this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2939
- if (!hasParameter('ssl')) {
2940
- // Create an HTTP server and attach the express app
2941
- this.httpServer = createServer(this.expressApp);
2942
- // Listen on the specified port
2943
- if (hasParameter('ingress')) {
2944
- this.httpServer.listen(port, '0.0.0.0', () => {
2945
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
2946
- });
2947
- }
2948
- else {
2949
- this.httpServer.listen(port, () => {
2950
- if (this.systemInformation.ipv4Address !== '')
2951
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
2952
- if (this.systemInformation.ipv6Address !== '')
2953
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2954
- });
2955
- }
2956
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2957
- this.httpServer.on('error', (error) => {
2958
- this.log.error(`Frontend http server error listening on ${port}`);
2959
- switch (error.code) {
2960
- case 'EACCES':
2961
- this.log.error(`Port ${port} requires elevated privileges`);
2962
- break;
2963
- case 'EADDRINUSE':
2964
- this.log.error(`Port ${port} is already in use`);
2965
- break;
2966
- }
2967
- initializeError = true;
2968
- return;
2969
- });
2970
- }
2971
- else {
2972
- // Load the SSL certificate, the private key and optionally the CA certificate
2973
- let cert;
2974
- try {
2975
- cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
2976
- this.log.info(`Loaded certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}`);
2977
- }
2978
- catch (error) {
2979
- this.log.error(`Error reading certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}: ${error}`);
2980
- return;
2981
- }
2982
- let key;
2983
- try {
2984
- key = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
2985
- this.log.info(`Loaded key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}`);
2986
- }
2987
- catch (error) {
2988
- this.log.error(`Error reading key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}: ${error}`);
2989
- return;
2990
- }
2991
- let ca;
2992
- try {
2993
- ca = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
2994
- this.log.info(`Loaded CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')}`);
2995
- }
2996
- catch (error) {
2997
- this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2998
- }
2999
- const serverOptions = { cert, key, ca };
3000
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
3001
- this.httpsServer = https.createServer(serverOptions, this.expressApp);
3002
- // Listen on the specified port
3003
- if (hasParameter('ingress')) {
3004
- this.httpsServer.listen(port, '0.0.0.0', () => {
3005
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
3006
- });
3007
- }
3008
- else {
3009
- this.httpsServer.listen(port, () => {
3010
- if (this.systemInformation.ipv4Address !== '')
3011
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
3012
- if (this.systemInformation.ipv6Address !== '')
3013
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
3014
- });
3015
- }
3016
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3017
- this.httpsServer.on('error', (error) => {
3018
- this.log.error(`Frontend https server error listening on ${port}`);
3019
- switch (error.code) {
3020
- case 'EACCES':
3021
- this.log.error(`Port ${port} requires elevated privileges`);
3022
- break;
3023
- case 'EADDRINUSE':
3024
- this.log.error(`Port ${port} is already in use`);
3025
- break;
3026
- }
3027
- initializeError = true;
3028
- return;
3029
- });
3030
- }
3031
- if (initializeError)
3032
- return;
3033
- // Createe a WebSocket server and attach it to the http or https server
3034
- const wssPort = port;
3035
- const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
3036
- this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
3037
- this.webSocketServer.on('connection', (ws, request) => {
3038
- const clientIp = request.socket.remoteAddress;
3039
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
3040
- this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
3041
- ws.on('message', (message) => {
3042
- this.log.debug(`WebSocket client message: ${message}`);
3043
- this.matterbridgeMessageHandler(ws, message);
3044
- });
3045
- ws.on('ping', () => {
3046
- this.log.debug('WebSocket client ping');
3047
- ws.pong();
3048
- });
3049
- ws.on('pong', () => {
3050
- this.log.debug('WebSocket client pong');
3051
- });
3052
- ws.on('close', () => {
3053
- this.log.info('WebSocket client disconnected');
3054
- if (this.webSocketServer?.clients.size === 0) {
3055
- AnsiLogger.setGlobalCallback(undefined);
3056
- this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
3057
- }
3058
- });
3059
- ws.on('error', (error) => {
3060
- this.log.error(`WebSocket client error: ${error}`);
3061
- });
3062
- });
3063
- this.webSocketServer.on('close', () => {
3064
- this.log.debug(`WebSocketServer closed`);
3065
- });
3066
- this.webSocketServer.on('listening', () => {
3067
- this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
3068
- });
3069
- this.webSocketServer.on('error', (ws, error) => {
3070
- this.log.error(`WebSocketServer error: ${error}`);
3071
- });
3072
- // Endpoint to validate login code
3073
- this.expressApp.post('/api/login', express.json(), async (req, res) => {
3074
- const { password } = req.body;
3075
- this.log.debug('The frontend sent /api/login', password);
3076
- if (!this.nodeContext) {
3077
- this.log.error('/api/login nodeContext not found');
3078
- res.json({ valid: false });
3079
- return;
3080
- }
3081
- try {
3082
- const storedPassword = await this.nodeContext.get('password', '');
3083
- if (storedPassword === '' || password === storedPassword) {
3084
- this.log.debug('/api/login password valid');
3085
- res.json({ valid: true });
3086
- }
3087
- else {
3088
- this.log.warn('/api/login error wrong password');
3089
- res.json({ valid: false });
3090
- }
3091
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3092
- }
3093
- catch (error) {
3094
- this.log.error('/api/login error getting password');
3095
- res.json({ valid: false });
3096
- }
3097
- });
3098
- // Endpoint to provide health check
3099
- this.expressApp.get('/health', (req, res) => {
3100
- this.log.debug('Express received /health');
3101
- const healthStatus = {
3102
- status: 'ok', // Indicate service is healthy
3103
- uptime: process.uptime(), // Server uptime in seconds
3104
- timestamp: new Date().toISOString(), // Current timestamp
3105
- };
3106
- res.status(200).json(healthStatus);
3107
- });
3108
- // Endpoint to provide settings
3109
- this.expressApp.get('/api/settings', express.json(), async (req, res) => {
3110
- this.log.debug('The frontend sent /api/settings');
3111
- this.matterbridgeInformation.bridgeMode = this.bridgeMode;
3112
- this.matterbridgeInformation.restartMode = this.restartMode;
3113
- this.matterbridgeInformation.loggerLevel = this.log.logLevel;
3114
- this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
3115
- this.matterbridgeInformation.mattermdnsinterface = (await this.nodeContext?.get('mattermdnsinterface', '')) || '';
3116
- this.matterbridgeInformation.matteripv4address = (await this.nodeContext?.get('matteripv4address', '')) || '';
3117
- this.matterbridgeInformation.matteripv6address = (await this.nodeContext?.get('matteripv6address', '')) || '';
3118
- this.matterbridgeInformation.matterPort = (await this.nodeContext?.get('matterport', 5540)) ?? 5540;
3119
- this.matterbridgeInformation.matterDiscriminator = await this.nodeContext?.get('matterdiscriminator');
3120
- this.matterbridgeInformation.matterPasscode = await this.nodeContext?.get('matterpasscode');
3121
- this.matterbridgeInformation.matterbridgePaired = this.matterbridgePaired;
3122
- this.matterbridgeInformation.matterbridgeConnected = this.matterbridgeConnected;
3123
- this.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridgeQrPairingCode;
3124
- this.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridgeManualPairingCode;
3125
- this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
3126
- this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
3127
- this.matterbridgeInformation.profile = this.profile;
3128
- const response = { systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
3129
- // this.log.debug('Response:', debugStringify(response));
3130
- res.json(response);
3131
- });
3132
- // Endpoint to provide plugins
3133
- this.expressApp.get('/api/plugins', async (req, res) => {
3134
- this.log.debug('The frontend sent /api/plugins');
3135
- const response = await this.getBaseRegisteredPlugins();
3136
- // this.log.debug('Response:', debugStringify(response));
3137
- res.json(response);
3138
- });
3139
- // Endpoint to provide devices
3140
- this.expressApp.get('/api/devices', (req, res) => {
3141
- this.log.debug('The frontend sent /api/devices');
3142
- const devices = [];
3143
- this.devices.forEach(async (device) => {
3144
- const pluginName = device.plugin ?? 'Unknown';
3145
- if (this.edge)
3146
- device = EndpointServer.forEndpoint(device);
3147
- let name = device.getClusterServer(BasicInformationCluster)?.attributes.nodeLabel?.getLocal();
3148
- if (!name)
3149
- name = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.nodeLabel?.getLocal() ?? 'Unknown';
3150
- let serial = device.getClusterServer(BasicInformationCluster)?.attributes.serialNumber?.getLocal();
3151
- if (!serial)
3152
- serial = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.serialNumber?.getLocal() ?? 'Unknown';
3153
- let productUrl = device.getClusterServer(BasicInformationCluster)?.attributes.productUrl?.getLocal();
3154
- if (!productUrl)
3155
- productUrl = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.productUrl?.getLocal() ?? 'Unknown';
3156
- let uniqueId = device.getClusterServer(BasicInformationCluster)?.attributes.uniqueId?.getLocal();
3157
- if (!uniqueId)
3158
- uniqueId = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.uniqueId?.getLocal() ?? 'Unknown';
3159
- const cluster = this.getClusterTextFromDevice(device);
3160
- devices.push({
3161
- pluginName,
3162
- type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
3163
- endpoint: device.number,
3164
- name,
3165
- serial,
3166
- productUrl,
3167
- configUrl: device.configUrl,
3168
- uniqueId,
3169
- cluster: cluster,
2352
+ this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
3170
2353
  });
3171
- });
3172
- // this.log.debug('Response:', debugStringify(data));
3173
- res.json(devices);
3174
- });
3175
- // Endpoint to provide the cluster servers of the devices
3176
- this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
3177
- const selectedPluginName = req.params.selectedPluginName;
3178
- const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
3179
- this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
3180
- if (selectedPluginName === 'none') {
3181
- res.json([]);
3182
- return;
3183
- }
3184
- const data = [];
3185
- this.devices.forEach(async (device) => {
3186
- const pluginName = device.plugin;
3187
- if (this.edge)
3188
- device = EndpointServer.forEndpoint(device);
3189
- if (pluginName === selectedPluginName && device.number === selectedDeviceEndpoint) {
3190
- const clusterServers = device.getAllClusterServers();
3191
- clusterServers.forEach((clusterServer) => {
3192
- Object.entries(clusterServer.attributes).forEach(([key, value]) => {
3193
- if (clusterServer.name === 'EveHistory')
3194
- return;
3195
- // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
3196
- let attributeValue;
3197
- try {
3198
- if (typeof value.getLocal() === 'object')
3199
- attributeValue = stringify(value.getLocal());
3200
- else
3201
- attributeValue = value.getLocal().toString();
3202
- }
3203
- catch (error) {
3204
- attributeValue = 'Fabric-Scoped';
3205
- this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3206
- // console.log(error);
3207
- }
3208
- data.push({
3209
- endpoint: device.number ? device.number.toString() : '...',
3210
- clusterName: clusterServer.name,
3211
- clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
3212
- attributeName: key,
3213
- attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
3214
- attributeValue,
3215
- });
3216
- });
3217
- });
3218
- device.getChildEndpoints().forEach((childEndpoint) => {
3219
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3220
- const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
3221
- const clusterServers = childEndpoint.getAllClusterServers();
3222
- clusterServers.forEach((clusterServer) => {
3223
- Object.entries(clusterServer.attributes).forEach(([key, value]) => {
3224
- if (clusterServer.name === 'EveHistory')
3225
- return;
3226
- // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
3227
- let attributeValue;
3228
- try {
3229
- if (typeof value.getLocal() === 'object')
3230
- attributeValue = stringify(value.getLocal());
3231
- else
3232
- attributeValue = value.getLocal().toString();
3233
- }
3234
- catch (error) {
3235
- attributeValue = 'Unavailable';
3236
- this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3237
- // console.log(error);
3238
- }
3239
- data.push({
3240
- endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
3241
- clusterName: clusterServer.name,
3242
- clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
3243
- attributeName: key,
3244
- attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
3245
- attributeValue,
3246
- });
3247
- });
3248
- });
3249
- });
3250
- }
3251
- });
3252
- res.json(data);
3253
- });
3254
- // Endpoint to view the log
3255
- this.expressApp.get('/api/view-log', async (req, res) => {
3256
- this.log.debug('The frontend sent /api/log');
3257
- try {
3258
- const data = await fs.readFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'utf8');
3259
- res.type('text/plain');
3260
- res.send(data);
3261
- }
3262
- catch (error) {
3263
- this.log.error(`Error reading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
3264
- res.status(500).send('Error reading log file');
3265
- }
3266
- });
3267
- // Endpoint to download the matterbridge log
3268
- this.expressApp.get('/api/download-mblog', async (req, res) => {
3269
- this.log.debug('The frontend sent /api/download-mblog');
3270
- try {
3271
- await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
3272
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3273
- }
3274
- catch (error) {
3275
- fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
3276
- }
3277
- res.download(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'matterbridge.log', (error) => {
3278
- if (error) {
3279
- this.log.error(`Error downloading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
3280
- res.status(500).send('Error downloading the matterbridge log file');
3281
- }
3282
- });
3283
- });
3284
- // Endpoint to download the matter log
3285
- this.expressApp.get('/api/download-mjlog', async (req, res) => {
3286
- this.log.debug('The frontend sent /api/download-mjlog');
3287
- try {
3288
- await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
3289
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3290
- }
3291
- catch (error) {
3292
- fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
3293
- }
3294
- res.download(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'matter.log', (error) => {
3295
- if (error) {
3296
- this.log.error(`Error downloading log file ${this.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
3297
- res.status(500).send('Error downloading the matter log file');
3298
- }
3299
- });
3300
- });
3301
- // Endpoint to download the matter storage file
3302
- this.expressApp.get('/api/download-mjstorage', (req, res) => {
3303
- this.log.debug('The frontend sent /api/download-mjstorage');
3304
- res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
3305
- if (error) {
3306
- this.log.error(`Error downloading log file ${this.matterStorageName}: ${error instanceof Error ? error.message : error}`);
3307
- res.status(500).send('Error downloading the matter storage file');
3308
- }
3309
- });
3310
- });
3311
- // Endpoint to download the matterbridge storage directory
3312
- this.expressApp.get('/api/download-mbstorage', async (req, res) => {
3313
- this.log.debug('The frontend sent /api/download-mbstorage');
3314
- await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
3315
- res.download(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), `matterbridge.${this.nodeStorageName}.zip`, (error) => {
3316
- if (error) {
3317
- this.log.error(`Error downloading file ${`matterbridge.${this.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
3318
- res.status(500).send('Error downloading the matterbridge storage file');
3319
- }
3320
- });
3321
- });
3322
- // Endpoint to download the matterbridge plugin directory
3323
- this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
3324
- this.log.debug('The frontend sent /api/download-pluginstorage');
3325
- await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
3326
- res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
3327
- if (error) {
3328
- this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
3329
- res.status(500).send('Error downloading the matterbridge plugin storage file');
3330
- }
3331
- });
3332
- });
3333
- // Endpoint to download the matterbridge plugin config files
3334
- this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
3335
- this.log.debug('The frontend sent /api/download-pluginconfig');
3336
- await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3337
- // await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3338
- res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
3339
- if (error) {
3340
- this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
3341
- res.status(500).send('Error downloading the matterbridge plugin storage file');
3342
- }
3343
- });
3344
- });
3345
- // Endpoint to download the matterbridge plugin config files
3346
- this.expressApp.get('/api/download-backup', async (req, res) => {
3347
- this.log.debug('The frontend sent /api/download-backup');
3348
- res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
3349
- if (error) {
3350
- this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
3351
- res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
3352
- }
3353
- });
3354
- });
3355
- // Endpoint to receive commands
3356
- this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
3357
- const command = req.params.command;
3358
- let param = req.params.param;
3359
- this.log.debug(`The frontend sent /api/command/${command}/${param}`);
3360
- if (!command) {
3361
- res.status(400).json({ error: 'No command provided' });
3362
- return;
3363
- }
3364
- this.log.debug(`Received frontend command: ${command}:${param}`);
3365
- // Handle the command setpassword from Settings
3366
- if (command === 'setpassword') {
3367
- const password = param.slice(1, -1); // Remove the first and last characters
3368
- this.log.debug('setpassword', param, password);
3369
- await this.nodeContext?.set('password', password);
3370
- res.json({ message: 'Command received' });
3371
- return;
3372
- }
3373
- // Handle the command setbridgemode from Settings
3374
- if (command === 'setbridgemode') {
3375
- this.log.debug(`setbridgemode: ${param}`);
3376
- this.wssSendRestartRequired();
3377
- await this.nodeContext?.set('bridgeMode', param);
3378
- res.json({ message: 'Command received' });
3379
- return;
3380
- }
3381
- // Handle the command backup from Settings
3382
- if (command === 'backup') {
3383
- this.log.notice(`Prepairing the backup...`);
3384
- await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
3385
- this.log.notice(`Backup ready to be downloaded.`);
3386
- res.json({ message: 'Command received' });
3387
- return;
3388
- }
3389
- // Handle the command setmbloglevel from Settings
3390
- if (command === 'setmbloglevel') {
3391
- this.log.debug('Matterbridge log level:', param);
3392
- if (param === 'Debug') {
3393
- this.log.logLevel = "debug" /* LogLevel.DEBUG */;
3394
- }
3395
- else if (param === 'Info') {
3396
- this.log.logLevel = "info" /* LogLevel.INFO */;
3397
- }
3398
- else if (param === 'Notice') {
3399
- this.log.logLevel = "notice" /* LogLevel.NOTICE */;
3400
- }
3401
- else if (param === 'Warn') {
3402
- this.log.logLevel = "warn" /* LogLevel.WARN */;
3403
- }
3404
- else if (param === 'Error') {
3405
- this.log.logLevel = "error" /* LogLevel.ERROR */;
3406
- }
3407
- else if (param === 'Fatal') {
3408
- this.log.logLevel = "fatal" /* LogLevel.FATAL */;
3409
- }
3410
- await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
3411
- MatterbridgeDevice.logLevel = this.log.logLevel;
3412
- this.plugins.logLevel = this.log.logLevel;
3413
- for (const plugin of this.plugins) {
3414
- if (!plugin.platform || !plugin.platform.config)
3415
- continue;
3416
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
3417
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
3418
- }
3419
- res.json({ message: 'Command received' });
3420
- return;
3421
- }
3422
- // Handle the command setmbloglevel from Settings
3423
- if (command === 'setmjloglevel') {
3424
- this.log.debug('Matter.js log level:', param);
3425
- if (param === 'Debug') {
3426
- Logger.defaultLogLevel = MatterLogLevel.DEBUG;
3427
- }
3428
- else if (param === 'Info') {
3429
- Logger.defaultLogLevel = MatterLogLevel.INFO;
3430
- }
3431
- else if (param === 'Notice') {
3432
- Logger.defaultLogLevel = MatterLogLevel.NOTICE;
3433
- }
3434
- else if (param === 'Warn') {
3435
- Logger.defaultLogLevel = MatterLogLevel.WARN;
3436
- }
3437
- else if (param === 'Error') {
3438
- Logger.defaultLogLevel = MatterLogLevel.ERROR;
3439
- }
3440
- else if (param === 'Fatal') {
3441
- Logger.defaultLogLevel = MatterLogLevel.FATAL;
3442
- }
3443
- await this.nodeContext?.set('matterLogLevel', Logger.defaultLogLevel);
3444
- res.json({ message: 'Command received' });
3445
- return;
3446
- }
3447
- // Handle the command setmdnsinterface from Settings
3448
- if (command === 'setmdnsinterface') {
3449
- param = param.slice(1, -1); // Remove the first and last characters *mdns*
3450
- this.matterbridgeInformation.mattermdnsinterface = param;
3451
- this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
3452
- await this.nodeContext?.set('mattermdnsinterface', param);
3453
- res.json({ message: 'Command received' });
3454
- return;
3455
- }
3456
- // Handle the command setipv4address from Settings
3457
- if (command === 'setipv4address') {
3458
- param = param.slice(1, -1); // Remove the first and last characters *ip*
3459
- this.matterbridgeInformation.matteripv4address = param;
3460
- this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
3461
- await this.nodeContext?.set('matteripv4address', param);
3462
- res.json({ message: 'Command received' });
3463
- return;
3464
- }
3465
- // Handle the command setipv6address from Settings
3466
- if (command === 'setipv6address') {
3467
- param = param.slice(1, -1); // Remove the first and last characters *ip*
3468
- this.matterbridgeInformation.matteripv6address = param;
3469
- this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
3470
- await this.nodeContext?.set('matteripv6address', param);
3471
- res.json({ message: 'Command received' });
3472
- return;
3473
- }
3474
- // Handle the command setmatterport from Settings
3475
- if (command === 'setmatterport') {
3476
- const port = Math.min(Math.max(parseInt(param), 5540), 5560);
3477
- this.matterbridgeInformation.matterPort = port;
3478
- this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
3479
- await this.nodeContext?.set('matterport', port);
3480
- res.json({ message: 'Command received' });
3481
- return;
3482
- }
3483
- // Handle the command setmatterdiscriminator from Settings
3484
- if (command === 'setmatterdiscriminator') {
3485
- const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
3486
- this.matterbridgeInformation.matterDiscriminator = discriminator;
3487
- this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
3488
- await this.nodeContext?.set('matterdiscriminator', discriminator);
3489
- res.json({ message: 'Command received' });
3490
- return;
3491
- }
3492
- // Handle the command setmatterpasscode from Settings
3493
- if (command === 'setmatterpasscode') {
3494
- const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
3495
- this.matterbridgeInformation.matterPasscode = passcode;
3496
- this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
3497
- await this.nodeContext?.set('matterpasscode', passcode);
3498
- res.json({ message: 'Command received' });
3499
- return;
3500
- }
3501
- // Handle the command setmbloglevel from Settings
3502
- if (command === 'setmblogfile') {
3503
- this.log.debug('Matterbridge file log:', param);
3504
- this.matterbridgeInformation.fileLogger = param === 'true';
3505
- await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
3506
- // Create the file logger for matterbridge
3507
- if (param === 'true')
3508
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
3509
- else
3510
- AnsiLogger.setGlobalLogfile(undefined);
3511
- res.json({ message: 'Command received' });
3512
- return;
3513
- }
3514
- // Handle the command setmbloglevel from Settings
3515
- if (command === 'setmjlogfile') {
3516
- this.log.debug('Matter file log:', param);
3517
- this.matterbridgeInformation.matterFileLogger = param === 'true';
3518
- await this.nodeContext?.set('matterFileLog', param === 'true');
3519
- if (param === 'true') {
3520
- try {
3521
- Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
3522
- defaultLogLevel: MatterLogLevel.DEBUG,
3523
- logFormat: MatterLogFormat.PLAIN,
3524
- });
3525
- }
3526
- catch (error) {
3527
- this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
3528
- }
3529
- }
3530
- else {
3531
- try {
3532
- Logger.removeLogger('matterfilelogger');
3533
- }
3534
- catch (error) {
3535
- this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
3536
- }
3537
- }
3538
- res.json({ message: 'Command received' });
3539
- return;
3540
- }
3541
- // Handle the command unregister from Settings
3542
- if (command === 'unregister') {
3543
- await this.unregisterAndShutdownProcess();
3544
- res.json({ message: 'Command received' });
3545
- return;
3546
- }
3547
- // Handle the command reset from Settings
3548
- if (command === 'reset') {
3549
- await this.shutdownProcessAndReset();
3550
- res.json({ message: 'Command received' });
3551
- return;
3552
- }
3553
- // Handle the command factoryreset from Settings
3554
- if (command === 'factoryreset') {
3555
- await this.shutdownProcessAndFactoryReset();
3556
- res.json({ message: 'Command received' });
3557
- return;
3558
- }
3559
- // Handle the command shutdown from Header
3560
- if (command === 'shutdown') {
3561
- await this.shutdownProcess();
3562
- res.json({ message: 'Command received' });
3563
- return;
3564
- }
3565
- // Handle the command restart from Header
3566
- if (command === 'restart') {
3567
- await this.restartProcess();
3568
- res.json({ message: 'Command received' });
3569
- return;
3570
- }
3571
- // Handle the command update from Header
3572
- if (command === 'update') {
3573
- this.log.info('Updating matterbridge...');
3574
- try {
3575
- await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
3576
- this.log.info('Matterbridge has been updated. Full restart required.');
3577
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3578
- }
3579
- catch (error) {
3580
- this.log.error('Error updating matterbridge');
3581
- }
3582
- await this.updateProcess();
3583
- this.wssSendRestartRequired();
3584
- res.json({ message: 'Command received' });
3585
- return;
3586
- }
3587
- // Handle the command saveconfig from Home
3588
- if (command === 'saveconfig') {
3589
- param = param.replace(/\*/g, '\\');
3590
- this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
3591
- // console.log('Req.body:', JSON.stringify(req.body, null, 2));
3592
- if (!this.plugins.has(param)) {
3593
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
3594
- }
3595
- else {
3596
- const plugin = this.plugins.get(param);
3597
- if (!plugin)
3598
- return;
3599
- this.plugins.saveConfigFromJson(plugin, req.body);
3600
- }
3601
- this.wssSendRestartRequired();
3602
- res.json({ message: 'Command received' });
3603
- return;
3604
- }
3605
- // Handle the command installplugin from Home
3606
- if (command === 'installplugin') {
3607
- param = param.replace(/\*/g, '\\');
3608
- this.log.info(`Installing plugin ${plg}${param}${nf}...`);
3609
- try {
3610
- await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
3611
- this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3612
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3613
- }
3614
- catch (error) {
3615
- this.log.error(`Error installing plugin ${plg}${param}${er}`);
3616
- }
3617
- this.wssSendRestartRequired();
3618
- param = param.split('@')[0];
3619
- // Also add the plugin to matterbridge so no return!
3620
- if (param === 'matterbridge') {
3621
- // If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
3622
- res.json({ message: 'Command received' });
3623
- return;
3624
- }
3625
- }
3626
- // Handle the command addplugin from Home
3627
- if (command === 'addplugin' || command === 'installplugin') {
3628
- param = param.replace(/\*/g, '\\');
3629
- const plugin = await this.plugins.add(param);
3630
- if (plugin) {
3631
- this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
3632
- }
3633
- res.json({ message: 'Command received' });
3634
- this.wssSendRefreshRequired();
3635
- return;
3636
- }
3637
- // Handle the command removeplugin from Home
3638
- if (command === 'removeplugin') {
3639
- if (!this.plugins.has(param)) {
3640
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
3641
- }
3642
- else {
3643
- const plugin = this.plugins.get(param);
3644
- await this.plugins.shutdown(plugin, 'The plugin has been removed.', true);
3645
- await this.plugins.remove(param);
3646
- }
3647
- res.json({ message: 'Command received' });
3648
- this.wssSendRefreshRequired();
3649
- return;
3650
- }
3651
- // Handle the command enableplugin from Home
3652
- if (command === 'enableplugin') {
3653
- if (!this.plugins.has(param)) {
3654
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
3655
- }
3656
- else {
3657
- const plugin = this.plugins.get(param);
3658
- if (plugin && !plugin.enabled) {
3659
- plugin.locked = undefined;
3660
- plugin.error = undefined;
3661
- plugin.loaded = undefined;
3662
- plugin.started = undefined;
3663
- plugin.configured = undefined;
3664
- plugin.connected = undefined;
3665
- plugin.platform = undefined;
3666
- plugin.registeredDevices = undefined;
3667
- plugin.addedDevices = undefined;
3668
- await this.plugins.enable(param);
3669
- this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
3670
- }
3671
- }
3672
- res.json({ message: 'Command received' });
3673
- this.wssSendRefreshRequired();
3674
- return;
3675
- }
3676
- // Handle the command disableplugin from Home
3677
- if (command === 'disableplugin') {
3678
- if (!this.plugins.has(param)) {
3679
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
3680
- }
3681
- else {
3682
- const plugin = this.plugins.get(param);
3683
- if (plugin && plugin.enabled) {
3684
- await this.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
3685
- await this.plugins.disable(param);
3686
- }
3687
- }
3688
- res.json({ message: 'Command received' });
3689
- this.wssSendRefreshRequired();
3690
- return;
3691
- }
3692
- });
3693
- // Fallback for routing (must be the last route)
3694
- this.expressApp.get('*', (req, res) => {
3695
- this.log.debug('The frontend sent:', req.url);
3696
- this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
3697
- res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
3698
- });
3699
- this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
3700
- }
3701
- /**
3702
- * Retrieves the cluster text description from a given device.
3703
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
3704
- * @returns {string} The attributes description of the cluster servers in the device.
3705
- */
3706
- getClusterTextFromDevice(device) {
3707
- const stringifyUserLabel = (endpoint) => {
3708
- const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
3709
- if (!labelList)
3710
- return;
3711
- const composed = labelList.find((entry) => entry.label === 'composed');
3712
- if (composed)
3713
- return 'Composed: ' + composed.value;
3714
- else
3715
- return '';
3716
- };
3717
- const stringifyFixedLabel = (endpoint) => {
3718
- const labelList = endpoint.getClusterServer(FixedLabelCluster)?.attributes.labelList.getLocal();
3719
- if (!labelList)
3720
- return;
3721
- const composed = labelList.find((entry) => entry.label === 'composed');
3722
- if (composed)
3723
- return 'Composed: ' + composed.value;
3724
- else
3725
- return '';
3726
- };
3727
- let attributes = '';
3728
- // this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3729
- const clusterServers = device.getAllClusterServers();
3730
- clusterServers.forEach((clusterServer) => {
3731
- try {
3732
- // this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3733
- if (clusterServer.name === 'OnOff')
3734
- attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
3735
- if (clusterServer.name === 'Switch')
3736
- attributes += `Position: ${clusterServer.attributes.currentPosition.getLocal()} `;
3737
- if (clusterServer.name === 'WindowCovering')
3738
- attributes += `Cover position: ${clusterServer.attributes.currentPositionLiftPercent100ths.getLocal() / 100}% `;
3739
- if (clusterServer.name === 'DoorLock')
3740
- attributes += `State: ${clusterServer.attributes.lockState.getLocal() === 1 ? 'Locked' : 'Not locked'} `;
3741
- if (clusterServer.name === 'Thermostat')
3742
- attributes += `Temperature: ${clusterServer.attributes.localTemperature.getLocal() / 100}°C `;
3743
- if (clusterServer.name === 'LevelControl')
3744
- attributes += `Level: ${clusterServer.attributes.currentLevel.getLocal()}% `;
3745
- if (clusterServer.name === 'ColorControl' && clusterServer.attributes.currentX)
3746
- attributes += `X: ${Math.round(clusterServer.attributes.currentX.getLocal())} Y: ${Math.round(clusterServer.attributes.currentY.getLocal())} `;
3747
- if (clusterServer.name === 'ColorControl' && clusterServer.attributes.currentHue)
3748
- attributes += `Hue: ${Math.round(clusterServer.attributes.currentHue.getLocal())} Saturation: ${Math.round(clusterServer.attributes.currentSaturation.getLocal())}% `;
3749
- if (clusterServer.name === 'ColorControl' && clusterServer.attributes.colorTemperatureMireds)
3750
- attributes += `ColorTemp: ${Math.round(clusterServer.attributes.colorTemperatureMireds.getLocal())} `;
3751
- if (clusterServer.name === 'BooleanState')
3752
- attributes += `Contact: ${clusterServer.attributes.stateValue.getLocal()} `;
3753
- if (clusterServer.name === 'BooleanStateConfiguration' && clusterServer.attributes.alarmsActive)
3754
- attributes += `Active alarms: ${stringify(clusterServer.attributes.alarmsActive.getLocal())} `;
3755
- if (clusterServer.name === 'SmokeCoAlarm' && clusterServer.attributes.smokeState)
3756
- attributes += `Smoke: ${clusterServer.attributes.smokeState.getLocal()} `;
3757
- if (clusterServer.name === 'SmokeCoAlarm' && clusterServer.attributes.coState)
3758
- attributes += `Co: ${clusterServer.attributes.coState.getLocal()} `;
3759
- if (clusterServer.name === 'FanControl')
3760
- attributes += `Mode: ${clusterServer.attributes.fanMode.getLocal()} Speed: ${clusterServer.attributes.percentCurrent.getLocal()} `;
3761
- if (clusterServer.name === 'FanControl' && clusterServer.attributes.speedCurrent)
3762
- attributes += `MultiSpeed: ${clusterServer.attributes.speedCurrent.getLocal()} `;
3763
- if (clusterServer.name === 'OccupancySensing')
3764
- attributes += `Occupancy: ${clusterServer.attributes.occupancy.getLocal().occupied} `;
3765
- if (clusterServer.name === 'IlluminanceMeasurement')
3766
- attributes += `Illuminance: ${clusterServer.attributes.measuredValue.getLocal()} `;
3767
- if (clusterServer.name === 'AirQuality')
3768
- attributes += `Air quality: ${clusterServer.attributes.airQuality.getLocal()} `;
3769
- if (clusterServer.name === 'TvocMeasurement')
3770
- attributes += `Voc: ${clusterServer.attributes.measuredValue.getLocal()} `;
3771
- if (clusterServer.name === 'TemperatureMeasurement')
3772
- attributes += `Temperature: ${clusterServer.attributes.measuredValue.getLocal() / 100}°C `;
3773
- if (clusterServer.name === 'RelativeHumidityMeasurement')
3774
- attributes += `Humidity: ${clusterServer.attributes.measuredValue.getLocal() / 100}% `;
3775
- if (clusterServer.name === 'PressureMeasurement')
3776
- attributes += `Pressure: ${clusterServer.attributes.measuredValue.getLocal()} `;
3777
- if (clusterServer.name === 'FlowMeasurement')
3778
- attributes += `Flow: ${clusterServer.attributes.measuredValue.getLocal()} `;
3779
- if (clusterServer.name === 'FixedLabel')
3780
- attributes += `${stringifyFixedLabel(device)} `;
3781
- if (clusterServer.name === 'UserLabel')
3782
- attributes += `${stringifyUserLabel(device)} `;
3783
- // this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3784
- }
3785
- catch (error) {
3786
- this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
3787
2354
  }
3788
2355
  });
3789
- // this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3790
- return attributes;
3791
- }
3792
- /**
3793
- * Initializes the Matterbridge instance as extension for zigbee2mqtt.
3794
- * @deprecated This method is deprecated and will be removed in a future version.
3795
- *
3796
- * @returns A Promise that resolves when the initialization is complete.
3797
- */
3798
- async startExtension(dataPath, extensionVersion, port = 5540) {
3799
- // Set the bridge mode
3800
- this.bridgeMode = 'bridge';
3801
- // Set the first port to use
3802
- this.port = port;
3803
- // Set Matterbridge logger
3804
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
3805
- this.log.debug('Matterbridge extension is starting...');
3806
- // Initialize NodeStorage
3807
- this.matterbridgeDirectory = dataPath;
3808
- this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
3809
- this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
3810
- this.log.debug('Creating node storage context for matterbridge: matterbridge');
3811
- this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
3812
- const plugin = {
3813
- path: '',
3814
- type: 'DynamicPlatform',
3815
- name: 'MatterbridgeExtension',
3816
- version: '1.0.0',
3817
- description: 'Matterbridge extension',
3818
- author: 'https://github.com/Luligu',
3819
- enabled: false,
3820
- registeredDevices: 0,
3821
- addedDevices: 0,
3822
- };
3823
- this.plugins.set(plugin);
3824
- this.plugins.saveToStorage();
3825
- // Log system info and create .matterbridge directory
3826
- await this.logNodeAndSystemInfo();
3827
- this.matterbridgeDirectory = dataPath;
3828
- // Set matter.js logger level and format
3829
- Logger.defaultLogLevel = MatterLogLevel.INFO;
3830
- Logger.format = MatterLogFormat.ANSI;
3831
- // Start the storage and create matterbridgeContext
3832
- await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
3833
- if (!this.storageManager)
3834
- return false;
3835
- this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge zigbee2MQTT', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'zigbee2MQTT Matter extension');
3836
- if (!this.matterbridgeContext)
3837
- return false;
3838
- await this.matterbridgeContext.set('softwareVersion', 1);
3839
- await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
3840
- await this.matterbridgeContext.set('hardwareVersion', 1);
3841
- await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
3842
- this.matterServer = await this.createMatterServer(this.storageManager);
3843
- this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
3844
- this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
3845
- this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
3846
- this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
3847
- this.log.debug('Adding matterbridge aggregator to commissioning server');
3848
- this.commissioningServer.addDevice(this.matterAggregator);
3849
- this.log.debug('Adding matterbridge commissioning server to matter server');
3850
- await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
3851
- await this.startMatterServer();
3852
- this.log.info('Matter server started');
3853
- await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
3854
- // Set reachability to true and trigger event after 60 seconds
3855
- setTimeout(() => {
3856
- this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
3857
- if (this.commissioningServer)
3858
- this.setCommissioningServerReachability(this.commissioningServer, true);
3859
- if (this.matterAggregator)
3860
- this.setAggregatorReachability(this.matterAggregator, true);
3861
- }, 60 * 1000);
3862
- return this.commissioningServer.isCommissioned();
3863
- }
3864
- /**
3865
- * Close the Matterbridge instance as extension for zigbee2mqtt.
3866
- * @deprecated This method is deprecated and will be removed in a future version.
3867
- *
3868
- * @returns A Promise that resolves when the initialization is complete.
3869
- */
3870
- async stopExtension() {
3871
- // Closing matter
3872
- await this.stopMatterServer();
3873
- // Clearing the session manager
3874
- // this.matterbridgeContext?.createContext('SessionManager').clear();
3875
- // Closing storage
3876
- await this.stopMatterStorage();
3877
- this.log.info('Matter server stopped');
3878
- }
3879
- /**
3880
- * Checks if the extension is commissioned.
3881
- * @deprecated This method is deprecated and will be removed in a future version.
3882
- *
3883
- * @returns {boolean} Returns true if the extension is commissioned, false otherwise.
3884
- */
3885
- isExtensionCommissioned() {
3886
- if (!this.commissioningServer)
3887
- return false;
3888
- return this.commissioningServer.isCommissioned();
3889
2356
  }
3890
2357
  }
3891
2358
  //# sourceMappingURL=matterbridge.js.map