matterbridge 1.1.11 → 1.2.0

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 (43) hide show
  1. package/CHANGELOG.md +18 -3
  2. package/README.md +59 -14
  3. package/dist/AirQualityCluster.d.ts.map +1 -1
  4. package/dist/AirQualityCluster.js +1 -10
  5. package/dist/AirQualityCluster.js.map +1 -1
  6. package/dist/index.d.ts +0 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +0 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/matterbridge.d.ts +90 -14
  11. package/dist/matterbridge.d.ts.map +1 -1
  12. package/dist/matterbridge.js +249 -90
  13. package/dist/matterbridge.js.map +1 -1
  14. package/dist/matterbridgeAccessoryPlatform.d.ts +5 -7
  15. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
  16. package/dist/matterbridgeAccessoryPlatform.js +6 -8
  17. package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
  18. package/dist/matterbridgeDevice.d.ts +66 -1
  19. package/dist/matterbridgeDevice.d.ts.map +1 -1
  20. package/dist/matterbridgeDevice.js +128 -10
  21. package/dist/matterbridgeDevice.js.map +1 -1
  22. package/dist/matterbridgeDynamicPlatform.d.ts +5 -7
  23. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
  24. package/dist/matterbridgeDynamicPlatform.js +6 -8
  25. package/dist/matterbridgeDynamicPlatform.js.map +1 -1
  26. package/dist/matterbridgePlatform.d.ts +43 -0
  27. package/dist/matterbridgePlatform.d.ts.map +1 -0
  28. package/dist/matterbridgePlatform.js +45 -0
  29. package/dist/matterbridgePlatform.js.map +1 -0
  30. package/frontend/build/asset-manifest.json +6 -6
  31. package/frontend/build/index.html +1 -1
  32. package/frontend/build/static/css/{main.70102d98.css → main.b996bc4e.css} +2 -2
  33. package/frontend/build/static/css/main.b996bc4e.css.map +1 -0
  34. package/frontend/build/static/js/{main.5d39b100.js → main.657bca15.js} +3 -3
  35. package/frontend/build/static/js/main.657bca15.js.map +1 -0
  36. package/package.json +12 -10
  37. package/dist/ColorControlServer.d.ts +0 -86
  38. package/dist/ColorControlServer.d.ts.map +0 -1
  39. package/dist/ColorControlServer.js +0 -102
  40. package/dist/ColorControlServer.js.map +0 -1
  41. package/frontend/build/static/css/main.70102d98.css.map +0 -1
  42. package/frontend/build/static/js/main.5d39b100.js.map +0 -1
  43. /package/frontend/build/static/js/{main.5d39b100.js.LICENSE.txt → main.657bca15.js.LICENSE.txt} +0 -0
@@ -4,7 +4,7 @@
4
4
  * @file matterbridge.ts
5
5
  * @author Luca Liguori
6
6
  * @date 2023-12-29
7
- * @version 1.1.1
7
+ * @version 1.2.0
8
8
  *
9
9
  * Copyright 2023, 2024 Luca Liguori.
10
10
  *
@@ -102,7 +102,9 @@ export class Matterbridge extends EventEmitter {
102
102
  /**
103
103
  * Loads an instance of the Matterbridge class.
104
104
  * If an instance already exists, return that instance.
105
- * @returns The loaded instance of the Matterbridge class.
105
+ *
106
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
107
+ * @returns The loaded Matterbridge instance.
106
108
  */
107
109
  static async loadInstance(initialize = false) {
108
110
  if (!Matterbridge.instance) {
@@ -129,10 +131,6 @@ export class Matterbridge extends EventEmitter {
129
131
  * @returns A Promise that resolves when the initialization is complete.
130
132
  */
131
133
  async initialize() {
132
- /*
133
- const wtf = await import('wtfnode');
134
- wtf.dump();
135
- */
136
134
  // Display the help
137
135
  if (hasParameter('help')) {
138
136
  // eslint-disable-next-line no-console
@@ -163,7 +161,7 @@ export class Matterbridge extends EventEmitter {
163
161
  await this.logNodeAndSystemInfo();
164
162
  this.log.info(
165
163
  // eslint-disable-next-line max-len
166
- `Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''} running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
164
+ `Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
167
165
  // check node version and throw error
168
166
  requireMinNodeVersion(18);
169
167
  // register SIGINT SIGTERM signal handlers
@@ -282,12 +280,14 @@ export class Matterbridge extends EventEmitter {
282
280
  this.bridgeMode = 'childbridge';
283
281
  MatterbridgeDevice.bridgeMode = 'childbridge';
284
282
  await this.testStartMatterBridge(); // No await do it asyncronously
283
+ return;
285
284
  }
286
285
  if (hasParameter('controller')) {
287
286
  this.bridgeMode = 'controller';
288
287
  this.log.info('Creating mattercontrollerContext: mattercontrollerContext');
289
288
  this.mattercontrollerContext = this.storageManager?.createContext('mattercontrollerContext');
290
289
  await this.startMatterBridge();
290
+ return;
291
291
  }
292
292
  if (hasParameter('bridge')) {
293
293
  this.bridgeMode = 'bridge';
@@ -306,6 +306,7 @@ export class Matterbridge extends EventEmitter {
306
306
  this.loadPlugin(plugin); // No await do it asyncronously
307
307
  }
308
308
  await this.startMatterBridge();
309
+ return;
309
310
  }
310
311
  if (hasParameter('childbridge')) {
311
312
  this.bridgeMode = 'childbridge';
@@ -324,8 +325,14 @@ export class Matterbridge extends EventEmitter {
324
325
  this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
325
326
  }
326
327
  await this.startMatterBridge();
328
+ return;
327
329
  }
328
330
  }
331
+ /**
332
+ * Resolves the name of a plugin by loading and parsing its package.json file.
333
+ * @param pluginPath - The path to the plugin or the path to the plugin's package.json file.
334
+ * @returns The path to the resolved package.json file, or null if the package.json file is not found or does not contain a name.
335
+ */
329
336
  async resolvePluginName(pluginPath) {
330
337
  if (!pluginPath.endsWith('package.json'))
331
338
  pluginPath = path.join(pluginPath, 'package.json');
@@ -451,14 +458,14 @@ export class Matterbridge extends EventEmitter {
451
458
  * Restarts the process by spawning a new process and exiting the current process.
452
459
  */
453
460
  async restartProcess() {
454
- //this.log.info('Restarting still not implemented');
455
- //return;
456
461
  await this.cleanup('restarting...', true);
457
462
  this.hasCleanupStarted = false;
458
463
  }
459
464
  /**
460
- * Performs cleanup operations before shutting down Matterbridge.
461
- * @param message - The reason for the cleanup.
465
+ * Cleans up the Matterbridge instance.
466
+ * @param message - The cleanup message.
467
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
468
+ * @returns A promise that resolves when the cleanup is completed.
462
469
  */
463
470
  async cleanup(message, restart = false) {
464
471
  if (!this.hasCleanupStarted) {
@@ -468,8 +475,10 @@ export class Matterbridge extends EventEmitter {
468
475
  process.removeAllListeners('SIGTERM');
469
476
  // Calling the shutdown functions with a reason
470
477
  for (const plugin of this.registeredPlugins) {
471
- if (plugin.platform)
478
+ if (plugin.platform) {
472
479
  await plugin.platform.onShutdown('Matterbridge is closing: ' + message);
480
+ await this.savePluginConfig(plugin);
481
+ }
473
482
  }
474
483
  // Set reachability to false
475
484
  /*
@@ -565,14 +574,14 @@ export class Matterbridge extends EventEmitter {
565
574
  */
566
575
  async addDevice(pluginName, device) {
567
576
  if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
568
- this.log.error(`Adding device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
577
+ this.log.error(`Adding device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
569
578
  return;
570
579
  }
571
- this.log.debug(`Adding device ${dev}${device.name}-${device.deviceName}${db} for plugin ${plg}${pluginName}${db}`);
580
+ this.log.debug(`Adding device ${dev}${device.deviceName}${db} (${dev}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
572
581
  // Check if the plugin is registered
573
582
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
574
583
  if (!plugin) {
575
- this.log.error(`Error adding device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
584
+ this.log.error(`Error adding device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
576
585
  return;
577
586
  }
578
587
  // Register and add the device to matterbridge aggregator in bridge mode
@@ -583,14 +592,14 @@ export class Matterbridge extends EventEmitter {
583
592
  plugin.registeredDevices++;
584
593
  if (plugin.addedDevices !== undefined)
585
594
  plugin.addedDevices++;
586
- this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
595
+ this.log.info(`Added and registered device (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
587
596
  }
588
597
  // Only register the device in childbridge mode
589
598
  if (this.bridgeMode === 'childbridge') {
590
599
  this.registeredDevices.push({ plugin: pluginName, device, added: false });
591
600
  if (plugin.registeredDevices !== undefined)
592
601
  plugin.registeredDevices++;
593
- this.log.info(`Registered device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
602
+ this.log.info(`Registered device ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
594
603
  }
595
604
  }
596
605
  /**
@@ -601,14 +610,14 @@ export class Matterbridge extends EventEmitter {
601
610
  */
602
611
  async addBridgedDevice(pluginName, device) {
603
612
  if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
604
- this.log.error(`Adding bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
613
+ this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
605
614
  return;
606
615
  }
607
- this.log.debug(`Adding bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
616
+ this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${dev}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
608
617
  // Check if the plugin is registered
609
618
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
610
619
  if (!plugin) {
611
- this.log.error(`Error adding bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
620
+ this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
612
621
  return;
613
622
  }
614
623
  // Register and add the device to matterbridge aggregator in bridge mode
@@ -619,34 +628,40 @@ export class Matterbridge extends EventEmitter {
619
628
  plugin.registeredDevices++;
620
629
  if (plugin.addedDevices !== undefined)
621
630
  plugin.addedDevices++;
622
- this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
631
+ 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}`);
623
632
  }
624
633
  // Only register the device in childbridge mode
625
634
  if (this.bridgeMode === 'childbridge') {
626
635
  this.registeredDevices.push({ plugin: pluginName, device, added: false });
627
636
  if (plugin.registeredDevices !== undefined)
628
637
  plugin.registeredDevices++;
629
- this.log.info(`Registered bridged device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
638
+ this.log.info(`Registered bridged device ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
630
639
  }
631
640
  }
641
+ /**
642
+ * Removes a bridged device from the Matterbridge.
643
+ * @param pluginName - The name of the plugin.
644
+ * @param device - The device to be removed.
645
+ * @returns A Promise that resolves when the device is successfully removed.
646
+ */
632
647
  async removeBridgedDevice(pluginName, device) {
633
648
  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`);
649
+ this.log.error(`Removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
635
650
  return;
636
651
  }
637
- this.log.debug(`Removing bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
652
+ this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${dev}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
638
653
  // Check if the plugin is registered
639
654
  const plugin = this.findPlugin(pluginName);
640
655
  if (!plugin) {
641
- this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
656
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
642
657
  return;
643
658
  }
644
659
  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`);
660
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} aggregator not found`);
646
661
  return;
647
662
  }
648
663
  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`);
664
+ this.log.warn(`Not removing bridged device ${dev}${device.deviceName}${wr} (${dev}${device.name}${wr}) plugin ${plg}${pluginName}${wr} not connected`);
650
665
  return;
651
666
  }
652
667
  // Register and add the device to matterbridge aggregator in bridge mode
@@ -662,12 +677,12 @@ export class Matterbridge extends EventEmitter {
662
677
  plugin.registeredDevices--;
663
678
  if (plugin.addedDevices !== undefined)
664
679
  plugin.addedDevices--;
665
- this.log.info(`Rmoved bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
680
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
666
681
  }
667
682
  // Only register the device in childbridge mode
668
683
  if (this.bridgeMode === 'childbridge') {
669
684
  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`);
685
+ this.log.warn(`Removing bridged device ${dev}${device.deviceName}${wr} (${dev}${device.name}${wr}) for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
671
686
  }
672
687
  else if (plugin.type === 'DynamicPlatform') {
673
688
  this.registeredDevices.forEach((registeredDevice, index) => {
@@ -678,11 +693,11 @@ export class Matterbridge extends EventEmitter {
678
693
  });
679
694
  plugin.aggregator.removeBridgedDevice(device);
680
695
  }
696
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
681
697
  if (plugin.registeredDevices !== undefined)
682
698
  plugin.registeredDevices--;
683
699
  if (plugin.addedDevices !== undefined)
684
700
  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
701
  }
687
702
  }
688
703
  /**
@@ -826,6 +841,92 @@ export class Matterbridge extends EventEmitter {
826
841
  }
827
842
  */
828
843
  }
844
+ /**
845
+ * Loads the configuration for a plugin.
846
+ * If the configuration file exists, it reads the file and returns the parsed JSON data.
847
+ * If the configuration file does not exist, it creates a new file with default configuration and returns it.
848
+ * If any error occurs during file access or creation, it logs an error and rejects the promise with the error.
849
+ *
850
+ * @param plugin - The plugin for which to load the configuration.
851
+ * @returns A promise that resolves to the loaded or created configuration.
852
+ */
853
+ async loadPluginConfig(plugin) {
854
+ const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
855
+ try {
856
+ await fs.access(configFile);
857
+ const data = await fs.readFile(configFile, 'utf8');
858
+ this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, JSON.parse(data));
859
+ return JSON.parse(data);
860
+ }
861
+ catch (err) {
862
+ if (err instanceof Error) {
863
+ const nodeErr = err;
864
+ if (nodeErr.code === 'ENOENT') {
865
+ try {
866
+ await this.writeFile(configFile, JSON.stringify({ name: plugin.name, type: plugin.type }, null, 2));
867
+ this.log.debug(`Created config file: ${configFile}.\nConfig:${rs}\n`, { name: plugin.name, type: plugin.type });
868
+ return { name: plugin.name, type: plugin.type };
869
+ }
870
+ catch (err) {
871
+ this.log.error(`Error creating config file ${configFile}: ${err}`);
872
+ return Promise.reject(err);
873
+ }
874
+ }
875
+ else {
876
+ this.log.error(`Error accessing config file ${configFile}: ${err}`);
877
+ return Promise.reject(err);
878
+ }
879
+ }
880
+ return Promise.reject(err);
881
+ }
882
+ }
883
+ /**
884
+ * Saves the configuration of a registered plugin.
885
+ * @param {RegisteredPlugin} plugin - The plugin whose configuration needs to be saved.
886
+ * @returns {Promise<void>} - A promise that resolves when the configuration is successfully saved.
887
+ * @throws {Error} - If the plugin's configuration is not found or if there is an error while saving the configuration.
888
+ */
889
+ async savePluginConfig(plugin) {
890
+ if (!plugin.platform?.config) {
891
+ this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`);
892
+ return Promise.reject(new Error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`));
893
+ }
894
+ const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
895
+ try {
896
+ await this.writeFile(configFile, JSON.stringify(plugin.platform.config, null, 2));
897
+ this.log.debug(`Saved config file: ${configFile}.\nConfig:${rs}\n`, plugin.platform.config);
898
+ }
899
+ catch (err) {
900
+ this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: ${err}`);
901
+ return Promise.reject(err);
902
+ }
903
+ }
904
+ /**
905
+ * Writes data to a file.
906
+ *
907
+ * @param {string} filePath - The path of the file to write to.
908
+ * @param {string} data - The data to write to the file.
909
+ * @returns {Promise<void>} - A promise that resolves when the data is successfully written to the file.
910
+ */
911
+ async writeFile(filePath, data) {
912
+ // Write the data to a file
913
+ await fs
914
+ .writeFile(`${filePath}`, data, 'utf8')
915
+ .then(() => {
916
+ this.log.debug(`Successfully wrote to ${filePath}`);
917
+ })
918
+ .catch((error) => {
919
+ this.log.error(`Error writing to ${filePath}:`, error);
920
+ });
921
+ }
922
+ /**
923
+ * Starts a plugin.
924
+ *
925
+ * @param {RegisteredPlugin} plugin - The plugin to start.
926
+ * @param {string} [message] - Optional message to pass to the plugin's onStart method.
927
+ * @param {boolean} [configure=false] - Indicates whether to configure the plugin after starting.
928
+ * @returns {Promise<void>} A promise that resolves when the plugin is started successfully, or rejects with an error if starting the plugin fails.
929
+ */
829
930
  async startPlugin(plugin, message, configure = false) {
830
931
  if (!plugin.loaded || !plugin.platform) {
831
932
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`);
@@ -856,6 +957,12 @@ export class Matterbridge extends EventEmitter {
856
957
  return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`));
857
958
  }
858
959
  }
960
+ /**
961
+ * Configures a plugin.
962
+ *
963
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
964
+ * @returns {Promise<void>} A promise that resolves when the plugin is configured successfully, or rejects with an error if configuration fails.
965
+ */
859
966
  async configurePlugin(plugin) {
860
967
  if (!plugin.loaded || !plugin.started || !plugin.platform) {
861
968
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or not started or not platform`);
@@ -884,6 +991,14 @@ export class Matterbridge extends EventEmitter {
884
991
  return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`));
885
992
  }
886
993
  }
994
+ /**
995
+ * Loads a plugin and returns the corresponding MatterbridgePlatform instance.
996
+ * @param plugin - The plugin to load.
997
+ * @param start - Optional flag indicating whether to start the plugin after loading. Default is false.
998
+ * @param message - Optional message to pass to the plugin when starting.
999
+ * @returns A Promise that resolves to the loaded MatterbridgePlatform instance.
1000
+ * @throws An error if the plugin is not enabled, already loaded, or fails to load.
1001
+ */
887
1002
  async loadPlugin(plugin, start = false, message = '') {
888
1003
  if (!plugin.enabled) {
889
1004
  this.log.error(`Plugin ${plg}${plugin.name}${er} not enabled`);
@@ -906,8 +1021,10 @@ export class Matterbridge extends EventEmitter {
906
1021
  this.log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
907
1022
  // Call the default export function of the plugin, passing this MatterBridge instance
908
1023
  if (pluginInstance.default) {
909
- const platform = pluginInstance.default(this, new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled }));
1024
+ const config = await this.loadPluginConfig(plugin);
1025
+ const platform = pluginInstance.default(this, new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled }), config);
910
1026
  platform.name = packageJson.name;
1027
+ platform.config = config;
911
1028
  plugin.name = packageJson.name;
912
1029
  plugin.description = packageJson.description;
913
1030
  plugin.version = packageJson.version;
@@ -966,8 +1083,8 @@ export class Matterbridge extends EventEmitter {
966
1083
  await this.matterServer.start();
967
1084
  this.log.info('Matter server started');
968
1085
  if (hasParameter('discover')) {
969
- const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
970
- console.log(discover);
1086
+ //const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1087
+ //console.log(discover);
971
1088
  }
972
1089
  this.log.info(`Commissioning controller is already commisioned: ${this.commissioningController.isCommissioned()}`);
973
1090
  const nodes = this.commissioningController.getCommissionedNodes();
@@ -979,11 +1096,17 @@ export class Matterbridge extends EventEmitter {
979
1096
  // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
980
1097
  // Plugins are started and configured by callback when Matterbridge is commissioned
981
1098
  this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
982
- this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1099
+ this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
983
1100
  if (!this.matterbridgeContext) {
984
1101
  this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
985
1102
  return;
986
1103
  }
1104
+ if (!this.nodeContext) {
1105
+ this.log.error(`Node storage context undefined for ${plg}Matterbridge${er}`);
1106
+ return;
1107
+ }
1108
+ this.matterbridgeContext.set('softwareVersion', 1);
1109
+ this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
987
1110
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
988
1111
  this.commissioningServer = this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
989
1112
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
@@ -995,7 +1118,7 @@ export class Matterbridge extends EventEmitter {
995
1118
  this.log.debug('Starting matter server...');
996
1119
  await this.startMatterServer();
997
1120
  this.log.info('Matter server started');
998
- this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
1121
+ await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
999
1122
  }
1000
1123
  if (this.bridgeMode === 'childbridge') {
1001
1124
  // Plugins are loaded and started by loadPlugin on startup
@@ -1033,7 +1156,9 @@ export class Matterbridge extends EventEmitter {
1033
1156
  await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1034
1157
  }
1035
1158
  if (plugin.type === 'DynamicPlatform') {
1036
- plugin.storageContext = this.createCommissioningServerContext(plugin.name, 'Matterbridge Dynamic Platform', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Dynamic Platform');
1159
+ plugin.storageContext = this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Dynamic Platform');
1160
+ plugin.storageContext.set('softwareVersion', 1);
1161
+ plugin.storageContext.set('softwareVersionString', this.matterbridgeVersion);
1037
1162
  plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
1038
1163
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
1039
1164
  plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
@@ -1079,13 +1204,31 @@ export class Matterbridge extends EventEmitter {
1079
1204
  await this.startMatterServer();
1080
1205
  this.log.info('Matter server started');
1081
1206
  for (const plugin of this.registeredPlugins) {
1082
- this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.name);
1207
+ if (!plugin.enabled)
1208
+ continue;
1209
+ if (!plugin.commissioningServer) {
1210
+ this.log.error(`Commissioning server not found for plugin ${plg}${plugin.name}${er}`);
1211
+ continue;
1212
+ }
1213
+ if (!plugin.storageContext) {
1214
+ this.log.error(`Storage context not found for plugin ${plg}${plugin.name}${er}`);
1215
+ continue;
1216
+ }
1217
+ if (!plugin.nodeContext) {
1218
+ this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1219
+ continue;
1220
+ }
1221
+ await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1083
1222
  }
1084
1223
  Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
1085
1224
  clearInterval(startMatterInterval);
1086
1225
  }, 1000);
1087
1226
  }
1088
1227
  }
1228
+ /**
1229
+ * Starts the Matter server.
1230
+ * If the Matter server is not initialized, it logs an error and performs cleanup.
1231
+ */
1089
1232
  async startMatterServer() {
1090
1233
  if (!this.matterServer) {
1091
1234
  this.log.error('No matter server initialized');
@@ -1155,17 +1298,14 @@ export class Matterbridge extends EventEmitter {
1155
1298
  return storageContext;
1156
1299
  }
1157
1300
  /**
1158
- * Shows the commissioning QR code for a given commissioning server, storage context, and name.
1159
- * If any of the parameters are missing, the method returns early.
1160
- * If the commissioning server is not commissioned, it logs the QR code and pairing code.
1161
- * If the commissioning server is already commissioned, it waits for controllers to connect.
1162
- * If the bridge mode is 'childbridge', it sets the 'paired' property of the plugin to true.
1163
- *
1164
- * @param commissioningServer - The commissioning server to show the QR code for.
1165
- * @param storageContext - The storage context to store the pairing codes.
1166
- * @param pluginName - The name of the commissioning server.
1301
+ * Shows the commissioning QR code for a given plugin.
1302
+ * @param {CommissioningServer} commissioningServer - The commissioning server instance.
1303
+ * @param {StorageContext} storageContext - The storage context instance.
1304
+ * @param {NodeStorage} nodeContext - The node storage instance.
1305
+ * @param {string} pluginName - The name of the plugin.
1306
+ * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
1167
1307
  */
1168
- async showCommissioningQRCode(commissioningServer, storageContext, pluginName) {
1308
+ async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
1169
1309
  if (!commissioningServer || !storageContext || !pluginName)
1170
1310
  return;
1171
1311
  if (!commissioningServer.isCommissioned()) {
@@ -1173,41 +1313,29 @@ export class Matterbridge extends EventEmitter {
1173
1313
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
1174
1314
  storageContext.set('qrPairingCode', qrPairingCode);
1175
1315
  storageContext.set('manualPairingCode', manualPairingCode);
1316
+ await nodeContext.set('qrPairingCode', qrPairingCode);
1317
+ await nodeContext.set('manualPairingCode', manualPairingCode);
1176
1318
  const QrCode = new QrCodeSchema();
1177
- this.log.info(`Pairing code:\n\n${QrCode.encode(qrPairingCode)}\nManual pairing code: ${manualPairingCode}\n`);
1178
- if (this.bridgeMode === 'bridge') {
1179
- await this.nodeContext?.set('qrPairingCode', qrPairingCode);
1180
- await this.nodeContext?.set('manualPairingCode', manualPairingCode);
1181
- }
1182
- if (this.bridgeMode === 'childbridge') {
1319
+ this.log.info(`Pairing code:\n\n${QrCode.encode(qrPairingCode)}\n${plg}${pluginName}${nf}\n\nqrPairingCode: ${qrPairingCode}\n\nManual pairing code: ${manualPairingCode}\n`);
1320
+ if (pluginName !== 'Matterbridge') {
1183
1321
  const plugin = this.findPlugin(pluginName);
1184
1322
  if (plugin) {
1185
- await plugin.nodeContext?.set('qrPairingCode', qrPairingCode);
1186
- await plugin.nodeContext?.set('manualPairingCode', manualPairingCode);
1187
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1323
+ plugin.qrPairingCode = qrPairingCode;
1324
+ plugin.manualPairingCode = manualPairingCode;
1188
1325
  plugin.paired = false;
1189
1326
  }
1190
1327
  }
1328
+ await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1191
1329
  }
1192
1330
  else {
1193
1331
  this.log.info(`***The commissioning server for ${plg}${pluginName}${nf} is already commissioned. Waiting for controllers to connect ...`);
1194
- if (this.bridgeMode === 'bridge') {
1195
- const qrPairingCode = storageContext.get('qrPairingCode', '');
1196
- const manualPairingCode = storageContext.get('manualPairingCode', '');
1197
- await this.nodeContext?.set('qrPairingCode', qrPairingCode);
1198
- await this.nodeContext?.set('manualPairingCode', manualPairingCode);
1199
- }
1200
- if (this.bridgeMode === 'childbridge') {
1332
+ if (pluginName !== 'Matterbridge') {
1201
1333
  const plugin = this.findPlugin(pluginName);
1202
- if (plugin && plugin.storageContext && plugin.nodeContext) {
1203
- plugin.qrPairingCode = plugin.storageContext.get('qrPairingCode', '');
1204
- plugin.manualPairingCode = plugin.storageContext.get('manualPairingCode', '');
1205
- await plugin.nodeContext.set('qrPairingCode', plugin.qrPairingCode);
1206
- await plugin.nodeContext.set('manualPairingCode', plugin.manualPairingCode);
1207
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1334
+ if (plugin) {
1208
1335
  plugin.paired = true;
1209
1336
  }
1210
1337
  }
1338
+ await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1211
1339
  }
1212
1340
  }
1213
1341
  /**
@@ -1228,11 +1356,11 @@ export class Matterbridge extends EventEmitter {
1228
1356
  * Creates a matter commissioning server.
1229
1357
  *
1230
1358
  * @param {StorageContext} context - The storage context.
1231
- * @param {string} name - The name of the commissioning server.
1359
+ * @param {string} pluginName - The name of the commissioning server.
1232
1360
  * @returns {CommissioningServer} The created commissioning server.
1233
1361
  */
1234
- createCommisioningServer(context, name) {
1235
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${name}${db}`);
1362
+ createCommisioningServer(context, pluginName) {
1363
+ this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
1236
1364
  const deviceName = context.get('deviceName');
1237
1365
  const deviceType = context.get('deviceType');
1238
1366
  const vendorId = context.get('vendorId');
@@ -1241,9 +1369,14 @@ export class Matterbridge extends EventEmitter {
1241
1369
  const productName = context.get('productName'); // Home app = Model
1242
1370
  const serialNumber = context.get('serialNumber');
1243
1371
  const uniqueId = context.get('uniqueId');
1244
- this.log.debug(
1245
- // eslint-disable-next-line max-len
1246
- `Creating matter commissioning server for plugin ${plg}${name}${db} with deviceName ${deviceName} deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')}) uniqueId ${uniqueId} serialNumber ${serialNumber}`);
1372
+ const softwareVersion = context.get('softwareVersion', 1);
1373
+ const softwareVersionString = context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1374
+ const hardwareVersion = context.get('hardwareVersion', 1);
1375
+ const hardwareVersionString = context.get('hardwareVersionString', '1.0.0');
1376
+ this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName ${deviceName} deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
1377
+ this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with uniqueId ${uniqueId} serialNumber ${serialNumber}`);
1378
+ this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
1379
+ this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
1247
1380
  const commissioningServer = new CommissioningServer({
1248
1381
  port: undefined,
1249
1382
  passcode: undefined,
@@ -1257,10 +1390,10 @@ export class Matterbridge extends EventEmitter {
1257
1390
  productName,
1258
1391
  nodeLabel: productName,
1259
1392
  productLabel: productName,
1260
- softwareVersion: context.get('softwareVersion', 1),
1261
- softwareVersionString: context.get('softwareVersionString', '1.0.0'), // Home app = Firmware Revision
1262
- hardwareVersion: context.get('hardwareVersion', 1),
1263
- hardwareVersionString: context.get('hardwareVersionString', '1.0.0'),
1393
+ softwareVersion,
1394
+ softwareVersionString, // Home app = Firmware Revision
1395
+ hardwareVersion,
1396
+ hardwareVersionString,
1264
1397
  uniqueId,
1265
1398
  serialNumber,
1266
1399
  reachable: true,
@@ -1269,15 +1402,15 @@ export class Matterbridge extends EventEmitter {
1269
1402
  const info = commissioningServer.getActiveSessionInformation(fabricIndex);
1270
1403
  let connected = false;
1271
1404
  info.forEach((session) => {
1272
- this.log.debug(`***Active session changed on fabric ${fabricIndex} ${session.fabric?.rootVendorId}/${session.fabric?.label} for ${plg}${name}${nf}`, debugStringify(session));
1405
+ this.log.debug(`***Active session changed on fabric ${fabricIndex} ${session.fabric?.rootVendorId}/${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
1273
1406
  if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
1274
- this.log.info(`***Controller ${session.fabric?.rootVendorId}/${session.fabric?.label} connected to ${plg}${name}${nf}`);
1407
+ this.log.info(`***Controller ${session.fabric?.rootVendorId}/${session.fabric?.label} connected to ${plg}${pluginName}${nf}`);
1275
1408
  connected = true;
1276
1409
  }
1277
1410
  });
1278
1411
  if (connected) {
1279
1412
  if (this.bridgeMode === 'childbridge') {
1280
- const plugin = this.findPlugin(name);
1413
+ const plugin = this.findPlugin(pluginName);
1281
1414
  if (plugin) {
1282
1415
  plugin.paired = true;
1283
1416
  plugin.connected = true;
@@ -1290,16 +1423,15 @@ export class Matterbridge extends EventEmitter {
1290
1423
  if (!plugin.enabled)
1291
1424
  continue;
1292
1425
  this.startPlugin(plugin, 'Matterbridge is commissioned and controllers are connected', true); // No await do it asyncronously with also configurePlugin
1293
- //this.configurePlugin(plugin); // No await do it asyncronously
1294
1426
  }
1295
1427
  Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
1296
1428
  }
1297
1429
  if (this.bridgeMode === 'childbridge') {
1298
1430
  //Logger.defaultLogLevel = Level.INFO;
1299
- const plugin = this.findPlugin(name);
1431
+ const plugin = this.findPlugin(pluginName);
1300
1432
  if (plugin && plugin.type === 'DynamicPlatform' && plugin.configured !== true) {
1301
1433
  for (const registeredDevice of this.registeredDevices) {
1302
- if (registeredDevice.plugin === name) {
1434
+ if (registeredDevice.plugin === pluginName) {
1303
1435
  this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
1304
1436
  if (!plugin.aggregator) {
1305
1437
  this.log.error(`****Aggregator not found for plugin ${plg}${plugin.name}${er}`);
@@ -1316,7 +1448,7 @@ export class Matterbridge extends EventEmitter {
1316
1448
  }
1317
1449
  }
1318
1450
  for (const plugin of this.registeredPlugins) {
1319
- if (plugin.name === name && plugin.platform && plugin.configured !== true) {
1451
+ if (plugin.name === pluginName && plugin.platform && plugin.configured !== true) {
1320
1452
  this.configurePlugin(plugin); // No await do it asyncronously
1321
1453
  }
1322
1454
  }
@@ -1326,12 +1458,29 @@ export class Matterbridge extends EventEmitter {
1326
1458
  }, 2000);
1327
1459
  }
1328
1460
  },
1329
- commissioningChangedCallback: (fabricIndex) => {
1461
+ commissioningChangedCallback: async (fabricIndex) => {
1330
1462
  const info = commissioningServer.getCommissionedFabricInformation(fabricIndex);
1331
- this.log.debug(`***Commissioning changed on fabric ${fabricIndex} for ${plg}${name}${nf}`, debugStringify(info));
1463
+ this.log.debug(`***Commissioning changed on fabric ${fabricIndex} for ${plg}${pluginName}${nf}`, debugStringify(info));
1332
1464
  if (info.length === 0) {
1333
- this.log.warn(`***Commissioning removed from fabric ${fabricIndex} for ${plg}${name}${nf}. Resetting the commissioning server ...`);
1334
- commissioningServer.factoryReset(); // TODO delete from storage also "matterbridge-eve-weather.EndpointStructure"
1465
+ this.log.warn(`***Commissioning removed from fabric ${fabricIndex} for ${plg}${pluginName}${nf}. Resetting the commissioning server ...`);
1466
+ await commissioningServer.factoryReset();
1467
+ if (pluginName === 'Matterbridge') {
1468
+ this.matterbridgeContext?.delete(`${pluginName}.EndpointStructure`);
1469
+ this.matterbridgeContext?.delete(`${pluginName}.EventHandler`);
1470
+ this.matterbridgeContext?.delete(`${pluginName}.SessionManager`);
1471
+ }
1472
+ else {
1473
+ for (const plugin of this.registeredPlugins) {
1474
+ if (plugin.name === pluginName) {
1475
+ await plugin.platform?.onShutdown('Commissioning removed by the controller');
1476
+ plugin.paired = false;
1477
+ plugin.connected = false;
1478
+ plugin.storageContext?.delete(`${pluginName}.EndpointStructure`);
1479
+ plugin.storageContext?.delete(`${pluginName}.EventHandler`);
1480
+ plugin.storageContext?.delete(`${pluginName}.SessionManager`);
1481
+ }
1482
+ }
1483
+ }
1335
1484
  }
1336
1485
  },
1337
1486
  });
@@ -1563,6 +1712,11 @@ export class Matterbridge extends EventEmitter {
1563
1712
  const cmdArgs = process.argv.slice(2).join(' ');
1564
1713
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
1565
1714
  }
1715
+ /**
1716
+ * Retrieves an array of base registered plugins.
1717
+ *
1718
+ * @returns {BaseRegisteredPlugin[]} An array of base registered plugins.
1719
+ */
1566
1720
  getBaseRegisteredPlugins() {
1567
1721
  const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
1568
1722
  path: plugin.path,
@@ -1583,6 +1737,10 @@ export class Matterbridge extends EventEmitter {
1583
1737
  }));
1584
1738
  return baseRegisteredPlugins;
1585
1739
  }
1740
+ /**
1741
+ * Retrieves an array of base registered devices from the registered plugins.
1742
+ * @returns {BaseRegisteredPlugin[]} An array of base registered devices.
1743
+ */
1586
1744
  getBaseRegisteredDevices() {
1587
1745
  const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
1588
1746
  path: plugin.path,
@@ -1614,6 +1772,7 @@ export class Matterbridge extends EventEmitter {
1614
1772
  this.expressApp.get('/api/qr-code', (req, res) => {
1615
1773
  this.log.debug('The frontend sent /api/qr-code');
1616
1774
  if (!this.matterbridgeContext) {
1775
+ this.log.error('/api/qr-code matterbridgeContext not found');
1617
1776
  res.json([]);
1618
1777
  return;
1619
1778
  }