matterbridge 1.1.8 → 1.1.9

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.
@@ -25,7 +25,7 @@ import { NodeStorageManager } from 'node-persist-manager';
25
25
  import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, er, nf, rs, wr } from 'node-ansi-logger';
26
26
  import { fileURLToPath, pathToFileURL } from 'url';
27
27
  import { promises as fs } from 'fs';
28
- import { execSync } from 'child_process';
28
+ import { exec, execSync } from 'child_process';
29
29
  import express from 'express';
30
30
  import os from 'os';
31
31
  import path from 'path';
@@ -61,7 +61,9 @@ export class Matterbridge {
61
61
  homeDirectory = '';
62
62
  rootDirectory = '';
63
63
  matterbridgeDirectory = '';
64
+ matterbridgePluginDirectory = '';
64
65
  matterbridgeVersion = '';
66
+ matterbridgeLatestVersion = '';
65
67
  globalModulesDir = '';
66
68
  bridgeMode = '';
67
69
  debugEnabled = false;
@@ -71,7 +73,8 @@ export class Matterbridge {
71
73
  registeredDevices = [];
72
74
  nodeStorage;
73
75
  nodeContext;
74
- app;
76
+ expressApp;
77
+ expressServer;
75
78
  storageManager;
76
79
  matterbridgeContext;
77
80
  mattercontrollerContext;
@@ -81,26 +84,24 @@ export class Matterbridge {
81
84
  commissioningController;
82
85
  static instance;
83
86
  constructor() {
84
- // we load asynchroneously the instance
87
+ // We load asyncronously
85
88
  }
86
89
  /**
87
90
  * Loads an instance of the Matterbridge class.
88
91
  * If an instance already exists, return that instance.
89
92
  * @returns The loaded instance of the Matterbridge class.
90
93
  */
91
- static async loadInstance(cli = false) {
92
- // eslint-disable-next-line no-console
93
- console.error('loadInstance cli:', cli);
94
+ static async loadInstance(initialize = false) {
94
95
  if (!Matterbridge.instance) {
95
96
  // eslint-disable-next-line no-console
96
- console.error('Matterbridge instance does not exists');
97
+ console.log('Matterbridge instance does not exists');
97
98
  Matterbridge.instance = new Matterbridge();
98
- if (cli)
99
+ if (initialize)
99
100
  await Matterbridge.instance.initialize();
100
101
  }
101
102
  else {
102
103
  // eslint-disable-next-line no-console
103
- console.error('Matterbridge instance already exists');
104
+ console.log('Matterbridge instance already exists');
104
105
  }
105
106
  return Matterbridge.instance;
106
107
  }
@@ -161,13 +162,13 @@ export class Matterbridge {
161
162
  this.registeredPlugins = await this.nodeContext.get('plugins', []);
162
163
  for (const plugin of this.registeredPlugins) {
163
164
  this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
164
- plugin.nodeContext = await this.nodeStorage?.createStorage(plugin.name);
165
- await plugin.nodeContext?.set('name', plugin.name);
166
- await plugin.nodeContext?.set('type', plugin.type);
167
- await plugin.nodeContext?.set('path', plugin.path);
168
- await plugin.nodeContext?.set('version', plugin.version);
169
- await plugin.nodeContext?.set('description', plugin.description);
170
- await plugin.nodeContext?.set('author', plugin.author);
165
+ plugin.nodeContext = await this.nodeStorage.createStorage(plugin.name);
166
+ await plugin.nodeContext.set('name', plugin.name);
167
+ await plugin.nodeContext.set('type', plugin.type);
168
+ await plugin.nodeContext.set('path', plugin.path);
169
+ await plugin.nodeContext.set('version', plugin.version);
170
+ await plugin.nodeContext.set('description', plugin.description);
171
+ await plugin.nodeContext.set('author', plugin.author);
171
172
  }
172
173
  // Parse command line
173
174
  this.parseCommandLine();
@@ -275,7 +276,7 @@ export class Matterbridge {
275
276
  this.log.debug(`Package.json not found at ${packageJsonPath}`);
276
277
  this.log.debug(`Trying at ${this.globalModulesDir}`);
277
278
  packageJsonPath = path.join(this.globalModulesDir, pluginPath);
278
- this.log.debug(`Got ${packageJsonPath}`);
279
+ //this.log.debug(`Got ${packageJsonPath}`);
279
280
  }
280
281
  try {
281
282
  // Load the package.json of the plugin
@@ -360,7 +361,6 @@ export class Matterbridge {
360
361
  this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
361
362
  }
362
363
  }
363
- //}
364
364
  }
365
365
  catch (err) {
366
366
  this.log.error(`Failed to load plugin from ${plg}${packageJsonPath}${er}: ${err}`);
@@ -371,21 +371,32 @@ export class Matterbridge {
371
371
  * When either of these signals are received, the cleanup method is called with an appropriate message.
372
372
  */
373
373
  async registerSignalHandlers() {
374
- process.on('SIGINT', async () => {
374
+ process.once('SIGINT', async () => {
375
375
  await this.cleanup('SIGINT received, cleaning up...');
376
376
  });
377
- process.on('SIGTERM', async () => {
377
+ process.once('SIGTERM', async () => {
378
378
  await this.cleanup('SIGTERM received, cleaning up...');
379
379
  });
380
380
  }
381
+ /**
382
+ * Restarts the process by spawning a new process and exiting the current process.
383
+ */
384
+ async restartProcess() {
385
+ //this.log.info('Restarting still not implemented');
386
+ //return;
387
+ await this.cleanup('Matterbridge is restarting...', true);
388
+ this.hasCleanupStarted = false;
389
+ }
381
390
  /**
382
391
  * Performs cleanup operations before shutting down Matterbridge.
383
392
  * @param message - The reason for the cleanup.
384
393
  */
385
- async cleanup(message) {
394
+ async cleanup(message, restart = false) {
386
395
  if (!this.hasCleanupStarted) {
387
396
  this.hasCleanupStarted = true;
388
397
  this.log.info(message);
398
+ process.removeAllListeners('SIGINT');
399
+ process.removeAllListeners('SIGTERM');
389
400
  // Callint the shutdown functions with a reason
390
401
  for (const plugin of this.registeredPlugins) {
391
402
  if (plugin.platform)
@@ -408,21 +419,47 @@ export class Matterbridge {
408
419
  if (this.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') registeredDevice.device.setBridgedDeviceReachability(false);
409
420
  });
410
421
  */
422
+ // Close the express server
423
+ if (this.expressServer) {
424
+ this.expressServer.close();
425
+ this.expressServer = undefined;
426
+ }
427
+ // Remove listeners
428
+ if (this.expressApp) {
429
+ this.expressApp.removeAllListeners();
430
+ this.expressApp = undefined;
431
+ }
411
432
  setTimeout(async () => {
412
433
  // Closing matter
413
434
  await this.stopMatter();
414
435
  // Closing storage
415
436
  await this.stopStorage();
416
437
  // Serialize registeredDevices
417
- const serializedRegisteredDevices = [];
418
- this.registeredDevices.forEach((registeredDevice) => {
419
- serializedRegisteredDevices.push(registeredDevice.device.serialize(registeredDevice.plugin));
420
- });
421
- //console.log('serializedRegisteredDevices:', serializedRegisteredDevices);
422
- await this.nodeContext?.set('devices', serializedRegisteredDevices);
423
- setTimeout(() => {
438
+ if (this.nodeContext) {
439
+ this.log.info('Saving registered devices...');
440
+ const serializedRegisteredDevices = [];
441
+ this.registeredDevices.forEach((registeredDevice) => {
442
+ serializedRegisteredDevices.push(registeredDevice.device.serialize(registeredDevice.plugin));
443
+ });
444
+ //console.log('serializedRegisteredDevices:', serializedRegisteredDevices);
445
+ await this.nodeContext.set('devices', serializedRegisteredDevices);
446
+ this.log.info('Saved registered devices');
447
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
448
+ this.nodeContext = undefined;
449
+ this.nodeStorage = undefined;
450
+ }
451
+ else {
452
+ this.log.error('Error saving registered devices: nodeContext not found!');
453
+ }
454
+ this.registeredPlugins = [];
455
+ this.registeredDevices = [];
456
+ setTimeout(async () => {
424
457
  this.log.info('Cleanup completed.');
425
- process.exit(0);
458
+ //if (restart) console.log(this);
459
+ if (restart)
460
+ await this.initialize();
461
+ else
462
+ process.exit(0);
426
463
  }, 2 * 1000);
427
464
  }, 3 * 1000);
428
465
  }
@@ -448,16 +485,20 @@ export class Matterbridge {
448
485
  * @returns A Promise that resolves when the device is added successfully.
449
486
  */
450
487
  async addDevice(pluginName, device) {
488
+ if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
489
+ this.log.error(`Adding device ${dev}${device.name}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
490
+ return;
491
+ }
451
492
  this.log.debug(`Adding device ${dev}${device.name}${db} for plugin ${plg}${pluginName}${db}`);
452
493
  // Check if the plugin is registered
453
494
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
454
495
  if (!plugin) {
455
- this.log.error(`addDevice error: device ${dev}${device.name}${nf} plugin ${plg}${pluginName}${er} not found`);
496
+ this.log.error(`Error adding device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
456
497
  return;
457
498
  }
458
499
  // Register and add the device to matterbridge aggregator in bridge mode
459
500
  if (this.bridgeMode === 'bridge') {
460
- this.matterAggregator.addBridgedDevice(device);
501
+ this.matterAggregator?.addBridgedDevice(device);
461
502
  this.registeredDevices.push({ plugin: pluginName, device, added: true });
462
503
  if (plugin.registeredDevices !== undefined)
463
504
  plugin.registeredDevices++;
@@ -480,16 +521,20 @@ export class Matterbridge {
480
521
  * @returns {Promise<void>} - A promise that resolves when the storage process is started.
481
522
  */
482
523
  async addBridgedDevice(pluginName, device) {
524
+ if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
525
+ this.log.error(`Adding bridged device ${dev}${device.name}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
526
+ return;
527
+ }
483
528
  this.log.debug(`Adding bridged device ${db}${device.name}${nf} for plugin ${plg}${pluginName}${db}`);
484
529
  // Check if the plugin is registered
485
530
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
486
531
  if (!plugin) {
487
- this.log.error(`addBridgedDevice error: device ${dev}${device.name}${nf} plugin ${plg}${pluginName}${er} not found`);
532
+ this.log.error(`Error adding bridged device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
488
533
  return;
489
534
  }
490
535
  // Register and add the device to matterbridge aggregator in bridge mode
491
536
  if (this.bridgeMode === 'bridge') {
492
- this.matterAggregator.addBridgedDevice(device);
537
+ this.matterAggregator?.addBridgedDevice(device);
493
538
  this.registeredDevices.push({ plugin: pluginName, device, added: true });
494
539
  if (plugin.registeredDevices !== undefined)
495
540
  plugin.registeredDevices++;
@@ -512,18 +557,22 @@ export class Matterbridge {
512
557
  * @returns {Promise<void>} - A promise that resolves when the storage process is started.
513
558
  */
514
559
  async startStorage(storageType, storageName) {
515
- if (!storageName.endsWith('.json')) {
516
- storageName += '.json';
517
- }
518
560
  this.log.debug(`Starting storage ${storageType} ${storageName}`);
519
561
  if (storageType === 'disk') {
520
562
  const storageDisk = new StorageBackendDisk(storageName);
521
563
  this.storageManager = new StorageManager(storageDisk);
522
564
  }
523
- if (storageType === 'json') {
565
+ else if (storageType === 'json') {
566
+ if (!storageName.endsWith('.json'))
567
+ storageName += '.json';
524
568
  const storageJson = new StorageBackendJsonFile(storageName);
525
569
  this.storageManager = new StorageManager(storageJson);
526
570
  }
571
+ else {
572
+ this.log.error(`Unsupported storage type ${storageType}`);
573
+ await this.cleanup('Unsupported storage type');
574
+ return;
575
+ }
527
576
  try {
528
577
  await this.storageManager.initialize();
529
578
  this.log.debug('Storage initialized');
@@ -533,7 +582,7 @@ export class Matterbridge {
533
582
  }
534
583
  catch (error) {
535
584
  this.log.error('Storage initialize() error!');
536
- process.exit(1);
585
+ await this.cleanup('Storage initialize() error!');
537
586
  }
538
587
  }
539
588
  /**
@@ -568,8 +617,11 @@ export class Matterbridge {
568
617
  */
569
618
  async stopStorage() {
570
619
  this.log.debug('Stopping storage');
571
- await this.storageManager.close();
620
+ await this.storageManager?.close();
572
621
  this.log.debug('Storage closed');
622
+ this.storageManager = undefined;
623
+ this.matterbridgeContext = undefined;
624
+ this.mattercontrollerContext = undefined;
573
625
  }
574
626
  async testStartMatterBridge() {
575
627
  /*
@@ -734,13 +786,27 @@ export class Matterbridge {
734
786
  * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
735
787
  */
736
788
  async startMatterBridge() {
789
+ if (!this.storageManager) {
790
+ this.log.error('No storage manager initialized');
791
+ await this.cleanup('No storage manager initialized');
792
+ return;
793
+ }
737
794
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
738
795
  this.createMatterServer(this.storageManager);
796
+ if (!this.matterServer) {
797
+ this.log.error('No matter server initialized');
798
+ await this.cleanup('No matter server initialized');
799
+ return;
800
+ }
739
801
  if (this.bridgeMode === 'bridge') {
740
802
  // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
741
- // Plugins are started and configured by callback when Matterbridge is commissioned and plugin.started is set to true
803
+ // Plugins are started and configured by callback when Matterbridge is commissioned
742
804
  this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
743
805
  this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
806
+ if (!this.matterbridgeContext) {
807
+ this.log.error(`Error creating storage context${er}`);
808
+ return;
809
+ }
744
810
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
745
811
  this.commissioningServer = this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
746
812
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
@@ -754,17 +820,22 @@ export class Matterbridge {
754
820
  this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
755
821
  }
756
822
  if (this.bridgeMode === 'childbridge') {
757
- // Plugins are loaded and started by loadPlugin on startup and plugin.loaded is set to true and plugin.started is set to true
823
+ // Plugins are loaded and started by loadPlugin on startup
758
824
  // addDevice and addBridgedDeevice just register the devices that are added here to the plugin commissioning server for Accessory Platform
759
825
  // or to the plugin aggregator for Dynamic Platform after the commissioning is done
760
826
  // Plugins are configured by callback when the plugin is commissioned
761
- this.registeredPlugins.forEach(async (plugin) => {
827
+ this.registeredPlugins.forEach((plugin) => {
762
828
  if (!plugin.enabled)
763
829
  return;
764
830
  // Start the interval to check if the plugins is started
765
831
  // TODO set a counter or a timeout
766
832
  this.log.debug(`*Starting startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
767
833
  const startInterval = setInterval(async () => {
834
+ if (!this.matterServer) {
835
+ this.log.error('No matter server initialized');
836
+ await this.cleanup('No matter server initialized');
837
+ return;
838
+ }
768
839
  if (!plugin.loaded || !plugin.started) {
769
840
  this.log.info(`**Waiting in startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
770
841
  return;
@@ -774,7 +845,11 @@ export class Matterbridge {
774
845
  .filter((registeredDevice) => registeredDevice.plugin === plugin.name)
775
846
  .forEach((registeredDevice) => {
776
847
  if (!plugin.storageContext)
777
- plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device); // Generate serialNumber and uniqueId
848
+ plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device);
849
+ if (!plugin.storageContext) {
850
+ this.log.error(`Error importing storage context for plugin ${plg}${plugin.name}${er}`);
851
+ return;
852
+ }
778
853
  if (!plugin.commissioningServer)
779
854
  plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
780
855
  this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
@@ -784,9 +859,11 @@ export class Matterbridge {
784
859
  await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
785
860
  }
786
861
  if (plugin.type === 'DynamicPlatform') {
787
- plugin.storageContext = this.createCommissioningServerContext(
788
- // Generate serialNumber and uniqueId
789
- plugin.name, 'Matterbridge Dynamic Platform', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Dynamic Platform');
862
+ plugin.storageContext = this.createCommissioningServerContext(plugin.name, 'Matterbridge Dynamic Platform', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Dynamic Platform');
863
+ if (!plugin.storageContext) {
864
+ this.log.error(`Error creating storage context for plugin ${plg}${plugin.name}${er}`);
865
+ return;
866
+ }
790
867
  plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
791
868
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
792
869
  plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
@@ -840,6 +917,11 @@ export class Matterbridge {
840
917
  }
841
918
  }
842
919
  async startMatterServer() {
920
+ if (!this.matterServer) {
921
+ this.log.error('No matter server initialized');
922
+ await this.cleanup('No matter server initialized');
923
+ return;
924
+ }
843
925
  this.log.debug('Starting matter server');
844
926
  await this.matterServer.start();
845
927
  this.log.debug('Started matter server');
@@ -879,6 +961,10 @@ export class Matterbridge {
879
961
  * @returns The storage context for the commissioning server.
880
962
  */
881
963
  createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) {
964
+ if (!this.storageManager) {
965
+ this.log.error('No storage manager initialized');
966
+ process.exit(1);
967
+ }
882
968
  this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
883
969
  const random = 'CS' + CryptoNode.getRandomData(8).toHex();
884
970
  const storageContext = this.storageManager.createContext(pluginName);
@@ -1088,6 +1174,7 @@ export class Matterbridge {
1088
1174
  createMatterServer(storageManager) {
1089
1175
  this.log.debug('Creating matter server');
1090
1176
  this.matterServer = new MatterServer(storageManager, { mdnsAnnounceInterface: undefined });
1177
+ this.log.debug('Created matter server');
1091
1178
  }
1092
1179
  /**
1093
1180
  * Creates a Matter Aggregator.
@@ -1135,6 +1222,27 @@ export class Matterbridge {
1135
1222
  this.log.debug('Stopping matter server');
1136
1223
  await this.matterServer?.close();
1137
1224
  this.log.debug('Matter server closed');
1225
+ this.commissioningController = undefined;
1226
+ this.commissioningServer = undefined;
1227
+ this.matterAggregator = undefined;
1228
+ this.matterServer = undefined;
1229
+ }
1230
+ /**
1231
+ * Retrieves the latest version of a package from the npm registry.
1232
+ * @param packageName - The name of the package.
1233
+ * @returns A Promise that resolves to the latest version of the package.
1234
+ */
1235
+ async getLatestVersion(packageName) {
1236
+ return new Promise((resolve, reject) => {
1237
+ exec(`npm view ${packageName} version`, (error, stdout) => {
1238
+ if (error) {
1239
+ reject(error);
1240
+ }
1241
+ else {
1242
+ resolve(stdout.trim());
1243
+ }
1244
+ });
1245
+ });
1138
1246
  }
1139
1247
  /**
1140
1248
  * Logs the node and system information.
@@ -1204,13 +1312,55 @@ export class Matterbridge {
1204
1312
  await fs.access(this.matterbridgeDirectory);
1205
1313
  }
1206
1314
  catch (err) {
1207
- await fs.mkdir(this.matterbridgeDirectory);
1315
+ if (err instanceof Error) {
1316
+ const nodeErr = err;
1317
+ if (nodeErr.code === 'ENOENT') {
1318
+ try {
1319
+ await fs.mkdir(this.matterbridgeDirectory, { recursive: true });
1320
+ this.log.info(`Created Matterbridge Directory: ${this.matterbridgeDirectory}`);
1321
+ }
1322
+ catch (err) {
1323
+ this.log.error(`Error creating directory: ${err}`);
1324
+ }
1325
+ }
1326
+ else {
1327
+ this.log.error(`Error accessing directory: ${err}`);
1328
+ }
1329
+ }
1208
1330
  }
1209
1331
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
1332
+ // Create the data directory .matterbridge in the home directory
1333
+ this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
1334
+ try {
1335
+ await fs.access(this.matterbridgePluginDirectory);
1336
+ }
1337
+ catch (err) {
1338
+ if (err instanceof Error) {
1339
+ const nodeErr = err;
1340
+ if (nodeErr.code === 'ENOENT') {
1341
+ try {
1342
+ await fs.mkdir(this.matterbridgePluginDirectory, { recursive: true });
1343
+ this.log.info(`Created Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
1344
+ }
1345
+ catch (err) {
1346
+ this.log.error(`Error creating directory: ${err}`);
1347
+ }
1348
+ }
1349
+ else {
1350
+ this.log.error(`Error accessing directory: ${err}`);
1351
+ }
1352
+ }
1353
+ }
1354
+ this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
1210
1355
  // Matterbridge version
1211
1356
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
1212
1357
  this.matterbridgeVersion = packageJson.version;
1213
1358
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1359
+ this.matterbridgeLatestVersion = await this.getLatestVersion('matterbridge');
1360
+ this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1361
+ if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
1362
+ this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
1363
+ }
1214
1364
  // Current working directory
1215
1365
  const currentDir = process.cwd();
1216
1366
  this.log.debug(`Current Working Directory: ${currentDir}`);
@@ -1262,14 +1412,16 @@ export class Matterbridge {
1262
1412
  */
1263
1413
  async initializeFrontend(port = 3000) {
1264
1414
  this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
1265
- this.app = express();
1415
+ this.expressApp = express();
1266
1416
  // Serve React build directory
1267
- this.app.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
1417
+ this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
1268
1418
  // Endpoint to provide QR pairing code
1269
- this.app.get('/api/qr-code', (req, res) => {
1419
+ this.expressApp.get('/api/qr-code', (req, res) => {
1270
1420
  this.log.debug('The frontend sent /api/qr-code');
1271
- if (!this.matterbridgeContext)
1272
- this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1421
+ if (!this.matterbridgeContext) {
1422
+ res.json([]);
1423
+ return;
1424
+ }
1273
1425
  try {
1274
1426
  const qrData = { qrPairingCode: this.matterbridgeContext.get('qrPairingCode'), manualPairingCode: this.matterbridgeContext.get('manualPairingCode') };
1275
1427
  res.json(qrData);
@@ -1281,17 +1433,17 @@ export class Matterbridge {
1281
1433
  }
1282
1434
  });
1283
1435
  // Endpoint to provide system information
1284
- this.app.get('/api/system-info', (req, res) => {
1436
+ this.expressApp.get('/api/system-info', (req, res) => {
1285
1437
  this.log.debug('The frontend sent /api/system-info');
1286
1438
  res.json(this.systemInformation);
1287
1439
  });
1288
1440
  // Endpoint to provide plugins
1289
- this.app.get('/api/plugins', (req, res) => {
1441
+ this.expressApp.get('/api/plugins', (req, res) => {
1290
1442
  this.log.debug('The frontend sent /api/plugins');
1291
1443
  res.json(this.getBaseRegisteredPlugins());
1292
1444
  });
1293
1445
  // Endpoint to provide devices
1294
- this.app.get('/api/devices', (req, res) => {
1446
+ this.expressApp.get('/api/devices', (req, res) => {
1295
1447
  this.log.debug('The frontend sent /api/devices');
1296
1448
  const data = [];
1297
1449
  this.registeredDevices.forEach((registeredDevice) => {
@@ -1318,7 +1470,7 @@ export class Matterbridge {
1318
1470
  res.json(data);
1319
1471
  });
1320
1472
  // Endpoint to provide the cluster servers of the devices
1321
- this.app.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
1473
+ this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
1322
1474
  const selectedPluginName = req.params.selectedPluginName;
1323
1475
  const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
1324
1476
  this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
@@ -1360,12 +1512,49 @@ export class Matterbridge {
1360
1512
  });
1361
1513
  res.json(data);
1362
1514
  });
1515
+ // Endpoint to receive commands
1516
+ this.expressApp.post('/api/command/:command/:param', (req, res) => {
1517
+ const command = req.params.command;
1518
+ const param = req.params.param;
1519
+ this.log.debug(`The frontend sent /api/command/${command}/${param}`);
1520
+ if (!command) {
1521
+ res.status(400).json({ error: 'No command provided' });
1522
+ return;
1523
+ }
1524
+ this.log.info(`***Received command: ${command}:${param}`);
1525
+ // Handle the command debugLevel
1526
+ if (command === 'setloglevel') {
1527
+ if (param === 'Debug') {
1528
+ this.log.setLogDebug(true);
1529
+ this.debugEnabled = true;
1530
+ Logger.defaultLogLevel = Level.DEBUG;
1531
+ }
1532
+ else if (param === 'Info') {
1533
+ this.log.setLogDebug(false);
1534
+ this.debugEnabled = false;
1535
+ Logger.defaultLogLevel = Level.INFO;
1536
+ }
1537
+ else if (param === 'Warn') {
1538
+ this.log.setLogDebug(false);
1539
+ this.debugEnabled = false;
1540
+ Logger.defaultLogLevel = Level.WARN;
1541
+ }
1542
+ this.registeredPlugins.forEach((plugin) => {
1543
+ plugin.platform?.log.setLogDebug(this.debugEnabled);
1544
+ });
1545
+ }
1546
+ // Handle the command debugLevel
1547
+ if (command === 'restart') {
1548
+ this.restartProcess();
1549
+ }
1550
+ res.json({ message: 'Command received' });
1551
+ });
1363
1552
  // Fallback for routing
1364
- this.app.get('*', (req, res) => {
1553
+ this.expressApp.get('*', (req, res) => {
1365
1554
  this.log.warn('The frontend sent *', req.url);
1366
1555
  res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
1367
1556
  });
1368
- this.app.listen(port, () => {
1557
+ this.expressServer = this.expressApp.listen(port, () => {
1369
1558
  this.log.info(`The frontend is running on ${UNDERLINE}http://localhost:${port}${UNDERLINEOFF}${rs}`);
1370
1559
  });
1371
1560
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
@@ -1413,6 +1602,23 @@ export class Matterbridge {
1413
1602
  }
1414
1603
  /*
1415
1604
  TO IMPLEMENT
1605
+
1606
+ import { spawn } from 'child_process';
1607
+
1608
+ function restartProcess() {
1609
+ // Spawn a new process
1610
+ const newProcess = spawn(process.argv[0], process.argv.slice(1), {
1611
+ detached: true,
1612
+ stdio: 'inherit',
1613
+ });
1614
+
1615
+ // Unreference the new process so that the current process can exit
1616
+ newProcess.unref();
1617
+
1618
+ // Exit the current process
1619
+ process.exit();
1620
+ }
1621
+
1416
1622
  import * as WebSocket from 'ws';
1417
1623
  const globalModulesDir = require('global-modules');
1418
1624