matterbridge 1.1.9 → 1.1.11

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 (32) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +20 -4
  3. package/dist/cli.js +20 -4
  4. package/dist/cli.js.map +1 -1
  5. package/dist/matterbridge.d.ts +35 -2
  6. package/dist/matterbridge.d.ts.map +1 -1
  7. package/dist/matterbridge.js +293 -51
  8. package/dist/matterbridge.js.map +1 -1
  9. package/dist/matterbridgeAccessoryPlatform.d.ts +9 -0
  10. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
  11. package/dist/matterbridgeAccessoryPlatform.js +13 -0
  12. package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
  13. package/dist/matterbridgeDevice.d.ts +3 -2
  14. package/dist/matterbridgeDevice.d.ts.map +1 -1
  15. package/dist/matterbridgeDevice.js +80 -20
  16. package/dist/matterbridgeDevice.js.map +1 -1
  17. package/dist/matterbridgeDynamicPlatform.d.ts +9 -0
  18. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
  19. package/dist/matterbridgeDynamicPlatform.js +13 -0
  20. package/dist/matterbridgeDynamicPlatform.js.map +1 -1
  21. package/frontend/build/asset-manifest.json +6 -6
  22. package/frontend/build/index.html +1 -1
  23. package/frontend/build/static/css/main.70102d98.css +2 -0
  24. package/frontend/build/static/css/main.70102d98.css.map +1 -0
  25. package/frontend/build/static/js/main.5d39b100.js +3 -0
  26. package/frontend/build/static/js/main.5d39b100.js.map +1 -0
  27. package/package.json +9 -6
  28. package/frontend/build/static/css/main.ce1ee9e7.css +0 -2
  29. package/frontend/build/static/css/main.ce1ee9e7.css.map +0 -1
  30. package/frontend/build/static/js/main.cc840fb3.js +0 -3
  31. package/frontend/build/static/js/main.cc840fb3.js.map +0 -1
  32. /package/frontend/build/static/js/{main.cc840fb3.js.LICENSE.txt → main.5d39b100.js.LICENSE.txt} +0 -0
@@ -25,11 +25,12 @@ 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 { exec, execSync } from 'child_process';
28
+ import { exec, spawn } from 'child_process';
29
+ import EventEmitter from 'events';
29
30
  import express from 'express';
30
31
  import os from 'os';
31
32
  import path from 'path';
32
- import { CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
33
+ import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
33
34
  import { BasicInformationCluster, BridgedDeviceBasicInformationCluster, ClusterServer } from '@project-chip/matter-node.js/cluster';
34
35
  import { DeviceTypeId, VendorId } from '@project-chip/matter-node.js/datatype';
35
36
  import { Aggregator, DeviceTypes } from '@project-chip/matter-node.js/device';
@@ -44,7 +45,7 @@ const typ = '\u001B[38;5;207m';
44
45
  /**
45
46
  * Represents the Matterbridge application.
46
47
  */
47
- export class Matterbridge {
48
+ export class Matterbridge extends EventEmitter {
48
49
  systemInformation = {
49
50
  ipv4Address: '',
50
51
  ipv6Address: '',
@@ -58,13 +59,24 @@ export class Matterbridge {
58
59
  freeMemory: '',
59
60
  systemUptime: '',
60
61
  };
62
+ matterbridgeInformation = {
63
+ homeDirectory: '',
64
+ rootDirectory: '',
65
+ matterbridgeDirectory: '',
66
+ matterbridgePluginDirectory: '',
67
+ globalModulesDirectory: '',
68
+ matterbridgeVersion: '',
69
+ matterbridgeLatestVersion: '',
70
+ bridgeMode: '',
71
+ debugEnabled: false,
72
+ };
61
73
  homeDirectory = '';
62
74
  rootDirectory = '';
63
75
  matterbridgeDirectory = '';
64
76
  matterbridgePluginDirectory = '';
77
+ globalModulesDirectory = '';
65
78
  matterbridgeVersion = '';
66
79
  matterbridgeLatestVersion = '';
67
- globalModulesDir = '';
68
80
  bridgeMode = '';
69
81
  debugEnabled = false;
70
82
  log;
@@ -84,6 +96,7 @@ export class Matterbridge {
84
96
  commissioningController;
85
97
  static instance;
86
98
  constructor() {
99
+ super();
87
100
  // We load asyncronously
88
101
  }
89
102
  /**
@@ -116,6 +129,10 @@ export class Matterbridge {
116
129
  * @returns A Promise that resolves when the initialization is complete.
117
130
  */
118
131
  async initialize() {
132
+ /*
133
+ const wtf = await import('wtfnode');
134
+ wtf.dump();
135
+ */
119
136
  // Display the help
120
137
  if (hasParameter('help')) {
121
138
  // eslint-disable-next-line no-console
@@ -173,6 +190,43 @@ export class Matterbridge {
173
190
  // Parse command line
174
191
  this.parseCommandLine();
175
192
  }
193
+ /**
194
+ * Spawns a child process with the given command and arguments.
195
+ * @param command - The command to execute.
196
+ * @param args - The arguments to pass to the command (default: []).
197
+ * @returns A promise that resolves when the child process exits successfully, or rejects if there is an error.
198
+ */
199
+ async spawnCommand(command, args = []) {
200
+ /*
201
+ npm > npm.cmd on windows
202
+ */
203
+ return new Promise((resolve, reject) => {
204
+ const childProcess = spawn(command, args, {
205
+ stdio: 'inherit',
206
+ });
207
+ childProcess.on('error', (err) => {
208
+ this.log.error(`Failed to start child process: ${err.message}`);
209
+ reject(err); // Reject the promise on error
210
+ });
211
+ childProcess.on('close', (code) => {
212
+ if (code === 0) {
213
+ this.log.info(`Child process stdio streams have closed with code ${code}`);
214
+ resolve();
215
+ }
216
+ else {
217
+ this.log.error(`Child process stdio streams have closed with code ${code}`);
218
+ reject(new Error(`Process exited with code ${code}`));
219
+ }
220
+ });
221
+ // The 'exit' event might be redundant here since 'close' is also being handled
222
+ childProcess.on('exit', (code, signal) => {
223
+ this.log.info(`Child process exited with code ${code} and signal ${signal}`);
224
+ });
225
+ childProcess.on('disconnect', () => {
226
+ this.log.info('Child process has been disconnected from the parent');
227
+ });
228
+ });
229
+ }
176
230
  /**
177
231
  * Parses the command line arguments and performs the corresponding actions.
178
232
  * @private
@@ -191,26 +245,31 @@ export class Matterbridge {
191
245
  this.log.info(`│ └─ ${db}${plugin.path}${db}`);
192
246
  }
193
247
  });
248
+ this.emit('shutdown');
194
249
  process.exit(0);
195
250
  }
196
251
  if (getParameter('add')) {
197
252
  this.log.debug(`Registering plugin ${getParameter('add')}`);
198
253
  await this.executeCommandLine(getParameter('add'), 'add');
254
+ this.emit('shutdown');
199
255
  process.exit(0);
200
256
  }
201
257
  if (getParameter('remove')) {
202
258
  this.log.debug(`Unregistering plugin ${getParameter('remove')}`);
203
259
  await this.executeCommandLine(getParameter('remove'), 'remove');
260
+ this.emit('shutdown');
204
261
  process.exit(0);
205
262
  }
206
263
  if (getParameter('enable')) {
207
264
  this.log.debug(`Enable plugin ${getParameter('enable')}`);
208
265
  await this.executeCommandLine(getParameter('enable'), 'enable');
266
+ this.emit('shutdown');
209
267
  process.exit(0);
210
268
  }
211
269
  if (getParameter('disable')) {
212
270
  this.log.debug(`Disable plugin ${getParameter('disable')}`);
213
271
  await this.executeCommandLine(getParameter('disable'), 'disable');
272
+ this.emit('shutdown');
214
273
  process.exit(0);
215
274
  }
216
275
  // Start the storage (we need it now for frontend and later for matterbridge)
@@ -224,12 +283,20 @@ export class Matterbridge {
224
283
  MatterbridgeDevice.bridgeMode = 'childbridge';
225
284
  await this.testStartMatterBridge(); // No await do it asyncronously
226
285
  }
286
+ if (hasParameter('controller')) {
287
+ this.bridgeMode = 'controller';
288
+ this.log.info('Creating mattercontrollerContext: mattercontrollerContext');
289
+ this.mattercontrollerContext = this.storageManager?.createContext('mattercontrollerContext');
290
+ await this.startMatterBridge();
291
+ }
227
292
  if (hasParameter('bridge')) {
228
293
  this.bridgeMode = 'bridge';
229
294
  MatterbridgeDevice.bridgeMode = 'bridge';
230
295
  for (const plugin of this.registeredPlugins) {
231
- if (!plugin.enabled)
296
+ if (!plugin.enabled) {
297
+ this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
232
298
  continue;
299
+ }
233
300
  plugin.loaded = false;
234
301
  plugin.started = false;
235
302
  plugin.configured = false;
@@ -244,8 +311,10 @@ export class Matterbridge {
244
311
  this.bridgeMode = 'childbridge';
245
312
  MatterbridgeDevice.bridgeMode = 'childbridge';
246
313
  for (const plugin of this.registeredPlugins) {
247
- if (!plugin.enabled)
314
+ if (!plugin.enabled) {
315
+ this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
248
316
  continue;
317
+ }
249
318
  plugin.loaded = false;
250
319
  plugin.started = false;
251
320
  plugin.configured = false;
@@ -274,8 +343,8 @@ export class Matterbridge {
274
343
  }
275
344
  if (!packageJsonExists) {
276
345
  this.log.debug(`Package.json not found at ${packageJsonPath}`);
277
- this.log.debug(`Trying at ${this.globalModulesDir}`);
278
- packageJsonPath = path.join(this.globalModulesDir, pluginPath);
346
+ this.log.debug(`Trying at ${this.globalModulesDirectory}`);
347
+ packageJsonPath = path.join(this.globalModulesDirectory, pluginPath);
279
348
  //this.log.debug(`Got ${packageJsonPath}`);
280
349
  }
281
350
  try {
@@ -384,7 +453,7 @@ export class Matterbridge {
384
453
  async restartProcess() {
385
454
  //this.log.info('Restarting still not implemented');
386
455
  //return;
387
- await this.cleanup('Matterbridge is restarting...', true);
456
+ await this.cleanup('restarting...', true);
388
457
  this.hasCleanupStarted = false;
389
458
  }
390
459
  /**
@@ -397,7 +466,7 @@ export class Matterbridge {
397
466
  this.log.info(message);
398
467
  process.removeAllListeners('SIGINT');
399
468
  process.removeAllListeners('SIGTERM');
400
- // Callint the shutdown functions with a reason
469
+ // Calling the shutdown functions with a reason
401
470
  for (const plugin of this.registeredPlugins) {
402
471
  if (plugin.platform)
403
472
  await plugin.platform.onShutdown('Matterbridge is closing: ' + message);
@@ -429,23 +498,26 @@ export class Matterbridge {
429
498
  this.expressApp.removeAllListeners();
430
499
  this.expressApp = undefined;
431
500
  }
432
- setTimeout(async () => {
501
+ const cleanupTimeout1 = setTimeout(async () => {
433
502
  // Closing matter
434
503
  await this.stopMatter();
435
504
  // Closing storage
436
505
  await this.stopStorage();
437
506
  // Serialize registeredDevices
438
- if (this.nodeContext) {
507
+ if (this.nodeStorage && this.nodeContext) {
439
508
  this.log.info('Saving registered devices...');
440
509
  const serializedRegisteredDevices = [];
441
510
  this.registeredDevices.forEach((registeredDevice) => {
442
511
  serializedRegisteredDevices.push(registeredDevice.device.serialize(registeredDevice.plugin));
443
512
  });
444
- //console.log('serializedRegisteredDevices:', serializedRegisteredDevices);
445
513
  await this.nodeContext.set('devices', serializedRegisteredDevices);
446
514
  this.log.info('Saved registered devices');
447
515
  // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
516
+ this.log.debug('Closing node storage context...');
517
+ this.nodeContext.close();
448
518
  this.nodeContext = undefined;
519
+ this.log.debug('Closing node storage manager...');
520
+ this.nodeStorage.close();
449
521
  this.nodeStorage = undefined;
450
522
  }
451
523
  else {
@@ -453,15 +525,22 @@ export class Matterbridge {
453
525
  }
454
526
  this.registeredPlugins = [];
455
527
  this.registeredDevices = [];
456
- setTimeout(async () => {
457
- this.log.info('Cleanup completed.');
458
- //if (restart) console.log(this);
459
- if (restart)
460
- await this.initialize();
461
- else
462
- process.exit(0);
528
+ this.log.info('Waiting for matter to deliver last messages...');
529
+ const cleanupTimeout2 = setTimeout(async () => {
530
+ if (restart) {
531
+ this.log.info('Cleanup completed. Restarting...');
532
+ Matterbridge.instance = undefined;
533
+ this.emit('restart');
534
+ }
535
+ else {
536
+ this.log.info('Cleanup completed. Shutting down...');
537
+ Matterbridge.instance = undefined;
538
+ this.emit('shutdown');
539
+ }
463
540
  }, 2 * 1000);
541
+ cleanupTimeout2.unref();
464
542
  }, 3 * 1000);
543
+ cleanupTimeout1.unref();
465
544
  }
466
545
  }
467
546
  /**
@@ -486,14 +565,14 @@ export class Matterbridge {
486
565
  */
487
566
  async addDevice(pluginName, device) {
488
567
  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`);
568
+ this.log.error(`Adding device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
490
569
  return;
491
570
  }
492
- this.log.debug(`Adding device ${dev}${device.name}${db} for plugin ${plg}${pluginName}${db}`);
571
+ this.log.debug(`Adding device ${dev}${device.name}-${device.deviceName}${db} for plugin ${plg}${pluginName}${db}`);
493
572
  // Check if the plugin is registered
494
573
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
495
574
  if (!plugin) {
496
- this.log.error(`Error adding device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
575
+ this.log.error(`Error adding device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
497
576
  return;
498
577
  }
499
578
  // Register and add the device to matterbridge aggregator in bridge mode
@@ -504,14 +583,14 @@ export class Matterbridge {
504
583
  plugin.registeredDevices++;
505
584
  if (plugin.addedDevices !== undefined)
506
585
  plugin.addedDevices++;
507
- this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
586
+ this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
508
587
  }
509
588
  // Only register the device in childbridge mode
510
589
  if (this.bridgeMode === 'childbridge') {
511
590
  this.registeredDevices.push({ plugin: pluginName, device, added: false });
512
591
  if (plugin.registeredDevices !== undefined)
513
592
  plugin.registeredDevices++;
514
- this.log.info(`Registered device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
593
+ this.log.info(`Registered device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
515
594
  }
516
595
  }
517
596
  /**
@@ -522,14 +601,14 @@ export class Matterbridge {
522
601
  */
523
602
  async addBridgedDevice(pluginName, device) {
524
603
  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`);
604
+ this.log.error(`Adding bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
526
605
  return;
527
606
  }
528
- this.log.debug(`Adding bridged device ${db}${device.name}${nf} for plugin ${plg}${pluginName}${db}`);
607
+ this.log.debug(`Adding bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
529
608
  // Check if the plugin is registered
530
609
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
531
610
  if (!plugin) {
532
- this.log.error(`Error adding bridged device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
611
+ this.log.error(`Error adding bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
533
612
  return;
534
613
  }
535
614
  // Register and add the device to matterbridge aggregator in bridge mode
@@ -540,14 +619,92 @@ export class Matterbridge {
540
619
  plugin.registeredDevices++;
541
620
  if (plugin.addedDevices !== undefined)
542
621
  plugin.addedDevices++;
543
- this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
622
+ this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
544
623
  }
545
624
  // Only register the device in childbridge mode
546
625
  if (this.bridgeMode === 'childbridge') {
547
626
  this.registeredDevices.push({ plugin: pluginName, device, added: false });
548
627
  if (plugin.registeredDevices !== undefined)
549
628
  plugin.registeredDevices++;
550
- this.log.info(`Registered bridged device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
629
+ this.log.info(`Registered bridged device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
630
+ }
631
+ }
632
+ async removeBridgedDevice(pluginName, device) {
633
+ if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
634
+ this.log.error(`Removing bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
635
+ return;
636
+ }
637
+ this.log.debug(`Removing bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
638
+ // Check if the plugin is registered
639
+ const plugin = this.findPlugin(pluginName);
640
+ if (!plugin) {
641
+ this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
642
+ return;
643
+ }
644
+ if (this.bridgeMode === 'childbridge' && !plugin.aggregator) {
645
+ this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} aggregator not found`);
646
+ return;
647
+ }
648
+ if (this.bridgeMode === 'childbridge' && !plugin.connected) {
649
+ this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not connected`);
650
+ return;
651
+ }
652
+ // Register and add the device to matterbridge aggregator in bridge mode
653
+ if (this.bridgeMode === 'bridge') {
654
+ this.matterAggregator.removeBridgedDevice(device);
655
+ this.registeredDevices.forEach((registeredDevice, index) => {
656
+ if (registeredDevice.device === device) {
657
+ this.registeredDevices.splice(index, 1);
658
+ return;
659
+ }
660
+ });
661
+ if (plugin.registeredDevices !== undefined)
662
+ plugin.registeredDevices--;
663
+ if (plugin.addedDevices !== undefined)
664
+ plugin.addedDevices--;
665
+ this.log.info(`Rmoved bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
666
+ }
667
+ // Only register the device in childbridge mode
668
+ if (this.bridgeMode === 'childbridge') {
669
+ if (plugin.type === 'AccessoryPlatform') {
670
+ this.log.warn(`Removing bridged device ${dev}${device.name}-${device.deviceName}${wr} for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
671
+ }
672
+ else if (plugin.type === 'DynamicPlatform') {
673
+ this.registeredDevices.forEach((registeredDevice, index) => {
674
+ if (registeredDevice.device === device) {
675
+ this.registeredDevices.splice(index, 1);
676
+ return;
677
+ }
678
+ });
679
+ plugin.aggregator.removeBridgedDevice(device);
680
+ }
681
+ if (plugin.registeredDevices !== undefined)
682
+ plugin.registeredDevices--;
683
+ if (plugin.addedDevices !== undefined)
684
+ plugin.addedDevices--;
685
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
686
+ }
687
+ }
688
+ /**
689
+ * Removes all bridged devices associated with a specific plugin.
690
+ *
691
+ * @param pluginName - The name of the plugin.
692
+ * @returns A promise that resolves when all devices have been removed.
693
+ */
694
+ async removeAllBridgedDevices(pluginName) {
695
+ const plugin = this.findPlugin(pluginName);
696
+ if (this.bridgeMode === 'childbridge' && plugin?.type === 'AccessoryPlatform') {
697
+ this.log.warn(`Removing devices for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
698
+ return;
699
+ }
700
+ const devicesToRemove = [];
701
+ for (const registeredDevice of this.registeredDevices) {
702
+ if (registeredDevice.plugin === pluginName) {
703
+ devicesToRemove.push(registeredDevice);
704
+ }
705
+ }
706
+ for (const registeredDevice of devicesToRemove) {
707
+ this.removeBridgedDevice(pluginName, registeredDevice.device);
551
708
  }
552
709
  }
553
710
  /**
@@ -798,13 +955,33 @@ export class Matterbridge {
798
955
  await this.cleanup('No matter server initialized');
799
956
  return;
800
957
  }
958
+ if (this.bridgeMode === 'controller') {
959
+ this.log.info('Creating matter commissioning controller');
960
+ this.commissioningController = new CommissioningController({
961
+ autoConnect: false,
962
+ });
963
+ this.log.info('Adding matter commissioning controller to matter server');
964
+ await this.matterServer.addCommissioningController(this.commissioningController);
965
+ this.log.info('Starting matter server');
966
+ await this.matterServer.start();
967
+ this.log.info('Matter server started');
968
+ if (hasParameter('discover')) {
969
+ const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
970
+ console.log(discover);
971
+ }
972
+ this.log.info(`Commissioning controller is already commisioned: ${this.commissioningController.isCommissioned()}`);
973
+ const nodes = this.commissioningController.getCommissionedNodes();
974
+ nodes.forEach(async (nodeId) => {
975
+ this.log.warn(`Connecting to commissioned node: ${nodeId}`);
976
+ });
977
+ }
801
978
  if (this.bridgeMode === 'bridge') {
802
979
  // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
803
980
  // Plugins are started and configured by callback when Matterbridge is commissioned
804
981
  this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
805
982
  this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
806
983
  if (!this.matterbridgeContext) {
807
- this.log.error(`Error creating storage context${er}`);
984
+ this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
808
985
  return;
809
986
  }
810
987
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
@@ -815,8 +992,9 @@ export class Matterbridge {
815
992
  this.commissioningServer.addDevice(this.matterAggregator);
816
993
  this.log.debug('Adding matterbridge commissioning server to matter server');
817
994
  await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
818
- this.log.debug('Starting matter server');
995
+ this.log.debug('Starting matter server...');
819
996
  await this.startMatterServer();
997
+ this.log.info('Matter server started');
820
998
  this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
821
999
  }
822
1000
  if (this.bridgeMode === 'childbridge') {
@@ -846,10 +1024,6 @@ export class Matterbridge {
846
1024
  .forEach((registeredDevice) => {
847
1025
  if (!plugin.storageContext)
848
1026
  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
- }
853
1027
  if (!plugin.commissioningServer)
854
1028
  plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
855
1029
  this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
@@ -860,10 +1034,6 @@ export class Matterbridge {
860
1034
  }
861
1035
  if (plugin.type === 'DynamicPlatform') {
862
1036
  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
- }
867
1037
  plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
868
1038
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
869
1039
  plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
@@ -890,7 +1060,7 @@ export class Matterbridge {
890
1060
  });
891
1061
  if (!allStarted)
892
1062
  return;
893
- this.log.info('Starting matter server');
1063
+ this.log.info('Starting matter server...');
894
1064
  // Setting reachability to true
895
1065
  this.registeredPlugins.forEach((plugin) => {
896
1066
  if (!plugin.enabled)
@@ -907,13 +1077,13 @@ export class Matterbridge {
907
1077
  });
908
1078
  });
909
1079
  await this.startMatterServer();
1080
+ this.log.info('Matter server started');
910
1081
  for (const plugin of this.registeredPlugins) {
911
1082
  this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.name);
912
1083
  }
913
1084
  Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
914
1085
  clearInterval(startMatterInterval);
915
1086
  }, 1000);
916
- return;
917
1087
  }
918
1088
  }
919
1089
  async startMatterServer() {
@@ -979,9 +1149,9 @@ export class Matterbridge {
979
1149
  storageContext.set('serialNumber', storageContext.get('serialNumber', random));
980
1150
  storageContext.set('uniqueId', storageContext.get('uniqueId', random));
981
1151
  storageContext.set('softwareVersion', softwareVersion ?? 1);
982
- storageContext.set('softwareVersionString', softwareVersionString ?? '1.0.0');
1152
+ storageContext.set('softwareVersionString', softwareVersionString ?? '1.0');
983
1153
  storageContext.set('hardwareVersion', hardwareVersion ?? 1);
984
- storageContext.set('hardwareVersionString', hardwareVersionString ?? '1.0.0');
1154
+ storageContext.set('hardwareVersionString', hardwareVersionString ?? '1.0');
985
1155
  return storageContext;
986
1156
  }
987
1157
  /**
@@ -1130,7 +1300,7 @@ export class Matterbridge {
1130
1300
  if (plugin && plugin.type === 'DynamicPlatform' && plugin.configured !== true) {
1131
1301
  for (const registeredDevice of this.registeredDevices) {
1132
1302
  if (registeredDevice.plugin === name) {
1133
- this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
1303
+ this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
1134
1304
  if (!plugin.aggregator) {
1135
1305
  this.log.error(`****Aggregator not found for plugin ${plg}${plugin.name}${er}`);
1136
1306
  continue;
@@ -1138,7 +1308,9 @@ export class Matterbridge {
1138
1308
  plugin.aggregator.addBridgedDevice(registeredDevice.device);
1139
1309
  if (plugin.addedDevices !== undefined)
1140
1310
  plugin.addedDevices++;
1141
- this.log.info(`Added bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${registeredDevice.device.name}${nf} for plugin ${plg}${plugin.name}${nf}`);
1311
+ this.log.info(
1312
+ // eslint-disable-next-line max-len
1313
+ `Added bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} for plugin ${plg}${plugin.name}${nf}`);
1142
1314
  registeredDevice.added = true;
1143
1315
  }
1144
1316
  }
@@ -1244,6 +1416,22 @@ export class Matterbridge {
1244
1416
  });
1245
1417
  });
1246
1418
  }
1419
+ /**
1420
+ * Retrieves the path to the global Node.js modules directory.
1421
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
1422
+ */
1423
+ async getGlobalNodeModules() {
1424
+ return new Promise((resolve, reject) => {
1425
+ exec('npm root -g', (error, stdout) => {
1426
+ if (error) {
1427
+ reject(error);
1428
+ }
1429
+ else {
1430
+ resolve(stdout.trim());
1431
+ }
1432
+ });
1433
+ });
1434
+ }
1247
1435
  /**
1248
1436
  * Logs the node and system information.
1249
1437
  */
@@ -1298,16 +1486,20 @@ export class Matterbridge {
1298
1486
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1299
1487
  // Home directory
1300
1488
  this.homeDirectory = os.homedir();
1489
+ this.matterbridgeInformation.homeDirectory = this.homeDirectory;
1301
1490
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
1302
1491
  // Package root directory
1303
1492
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
1304
1493
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
1494
+ this.matterbridgeInformation.rootDirectory = this.rootDirectory;
1305
1495
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
1306
1496
  // Global node_modules directory
1307
- this.globalModulesDir = execSync('npm root -g').toString().trim();
1308
- this.log.debug(`Global node_modules Directory: ${this.globalModulesDir}`);
1497
+ this.globalModulesDirectory = await this.getGlobalNodeModules();
1498
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
1499
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1309
1500
  // Create the data directory .matterbridge in the home directory
1310
1501
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
1502
+ this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
1311
1503
  try {
1312
1504
  await fs.access(this.matterbridgeDirectory);
1313
1505
  }
@@ -1331,6 +1523,7 @@ export class Matterbridge {
1331
1523
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
1332
1524
  // Create the data directory .matterbridge in the home directory
1333
1525
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
1526
+ this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
1334
1527
  try {
1335
1528
  await fs.access(this.matterbridgePluginDirectory);
1336
1529
  }
@@ -1355,8 +1548,10 @@ export class Matterbridge {
1355
1548
  // Matterbridge version
1356
1549
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
1357
1550
  this.matterbridgeVersion = packageJson.version;
1551
+ this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
1358
1552
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1359
1553
  this.matterbridgeLatestVersion = await this.getLatestVersion('matterbridge');
1554
+ this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeLatestVersion;
1360
1555
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1361
1556
  if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
1362
1557
  this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
@@ -1437,6 +1632,13 @@ export class Matterbridge {
1437
1632
  this.log.debug('The frontend sent /api/system-info');
1438
1633
  res.json(this.systemInformation);
1439
1634
  });
1635
+ // Endpoint to provide matterbridge information
1636
+ this.expressApp.get('/api/matterbridge-info', (req, res) => {
1637
+ this.log.debug('The frontend sent /api/matterbridge-info');
1638
+ this.matterbridgeInformation.bridgeMode = this.bridgeMode;
1639
+ this.matterbridgeInformation.debugEnabled = this.debugEnabled;
1640
+ res.json(this.matterbridgeInformation);
1641
+ });
1440
1642
  // Endpoint to provide plugins
1441
1643
  this.expressApp.get('/api/plugins', (req, res) => {
1442
1644
  this.log.debug('The frontend sent /api/plugins');
@@ -1513,7 +1715,7 @@ export class Matterbridge {
1513
1715
  res.json(data);
1514
1716
  });
1515
1717
  // Endpoint to receive commands
1516
- this.expressApp.post('/api/command/:command/:param', (req, res) => {
1718
+ this.expressApp.post('/api/command/:command/:param', async (req, res) => {
1517
1719
  const command = req.params.command;
1518
1720
  const param = req.params.param;
1519
1721
  this.log.debug(`The frontend sent /api/command/${command}/${param}`);
@@ -1522,7 +1724,7 @@ export class Matterbridge {
1522
1724
  return;
1523
1725
  }
1524
1726
  this.log.info(`***Received command: ${command}:${param}`);
1525
- // Handle the command debugLevel
1727
+ // Handle the command debugLevel from Settings
1526
1728
  if (command === 'setloglevel') {
1527
1729
  if (param === 'Debug') {
1528
1730
  this.log.setLogDebug(true);
@@ -1543,10 +1745,50 @@ export class Matterbridge {
1543
1745
  plugin.platform?.log.setLogDebug(this.debugEnabled);
1544
1746
  });
1545
1747
  }
1546
- // Handle the command debugLevel
1748
+ // Handle the command debugLevel from Header
1547
1749
  if (command === 'restart') {
1548
1750
  this.restartProcess();
1549
1751
  }
1752
+ // Handle the command update from Header
1753
+ if (command === 'update') {
1754
+ this.log.warn(`The /api/command/${command} is not yet implemented`);
1755
+ }
1756
+ // Handle the command addplugin from Header
1757
+ if (command === 'addplugin') {
1758
+ this.log.warn(`The /api/command/${command}/${param} is not yet implemented`);
1759
+ }
1760
+ // Handle the command enableplugin from Home
1761
+ if (command === 'enableplugin') {
1762
+ const plugins = await this.nodeContext?.get('plugins');
1763
+ if (!plugins)
1764
+ return;
1765
+ const plugin = plugins.find((plugin) => plugin.name === param);
1766
+ if (plugin) {
1767
+ plugin.enabled = true;
1768
+ plugin.loaded = undefined;
1769
+ plugin.started = undefined;
1770
+ plugin.configured = undefined;
1771
+ plugin.connected = undefined;
1772
+ await this.nodeContext?.set('plugins', plugins);
1773
+ this.log.info(`Enabled plugin ${plg}${param}${nf}`);
1774
+ }
1775
+ }
1776
+ // Handle the command disableplugin from Home
1777
+ if (command === 'disableplugin') {
1778
+ const plugins = await this.nodeContext?.get('plugins');
1779
+ if (!plugins)
1780
+ return;
1781
+ const plugin = plugins.find((plugin) => plugin.name === param);
1782
+ if (plugin) {
1783
+ plugin.enabled = false;
1784
+ plugin.loaded = undefined;
1785
+ plugin.started = undefined;
1786
+ plugin.configured = undefined;
1787
+ plugin.connected = undefined;
1788
+ await this.nodeContext?.set('plugins', plugins);
1789
+ this.log.info(`Disabled plugin ${plg}${param}${nf}`);
1790
+ }
1791
+ }
1550
1792
  res.json({ message: 'Command received' });
1551
1793
  });
1552
1794
  // Fallback for routing