matterbridge 1.2.3 → 1.2.5

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 (45) hide show
  1. package/CHANGELOG.md +189 -171
  2. package/README.md +379 -297
  3. package/TODO.md +7 -0
  4. package/dist/AirQualityCluster.d.ts +134 -104
  5. package/dist/AirQualityCluster.d.ts.map +1 -1
  6. package/dist/AirQualityCluster.js +43 -26
  7. package/dist/AirQualityCluster.js.map +1 -1
  8. package/dist/EveHistoryCluster.d.ts +446 -0
  9. package/dist/EveHistoryCluster.d.ts.map +1 -0
  10. package/dist/EveHistoryCluster.js +170 -0
  11. package/dist/EveHistoryCluster.js.map +1 -0
  12. package/dist/TvocCluster.d.ts +364 -148
  13. package/dist/TvocCluster.d.ts.map +1 -1
  14. package/dist/TvocCluster.js +115 -32
  15. package/dist/TvocCluster.js.map +1 -1
  16. package/dist/cli.js +6 -1
  17. package/dist/cli.js.map +1 -1
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/matterbridge.d.ts +9 -1
  23. package/dist/matterbridge.d.ts.map +1 -1
  24. package/dist/matterbridge.js +338 -150
  25. package/dist/matterbridge.js.map +1 -1
  26. package/dist/matterbridgeController.d.ts.map +1 -1
  27. package/dist/matterbridgeController.js +9 -0
  28. package/dist/matterbridgeController.js.map +1 -1
  29. package/dist/matterbridgeDevice.d.ts +1254 -9
  30. package/dist/matterbridgeDevice.d.ts.map +1 -1
  31. package/dist/matterbridgeDevice.js +372 -91
  32. package/dist/matterbridgeDevice.js.map +1 -1
  33. package/frontend/build/asset-manifest.json +3 -3
  34. package/frontend/build/index.html +1 -1
  35. package/frontend/build/matterbridge 32x32.png +0 -0
  36. package/frontend/build/matterbridge 64x64.png +0 -0
  37. package/frontend/build/static/css/main.61f6cf42.css.map +1 -1
  38. package/frontend/build/static/js/main.6b861489.js +3 -0
  39. package/frontend/build/static/js/main.6b861489.js.map +1 -0
  40. package/matterbridge.service +18 -0
  41. package/package.json +88 -87
  42. package/frontend/build/Matterbridge.jpg +0 -0
  43. package/frontend/build/static/js/main.e3553a4d.js +0 -3
  44. package/frontend/build/static/js/main.e3553a4d.js.map +0 -1
  45. /package/frontend/build/static/js/{main.e3553a4d.js.LICENSE.txt → main.6b861489.js.LICENSE.txt} +0 -0
@@ -31,7 +31,7 @@ import express from 'express';
31
31
  import os from 'os';
32
32
  import path from 'path';
33
33
  import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
34
- import { BasicInformationCluster, BridgedDeviceBasicInformationCluster, ClusterServer, GeneralCommissioning, PowerSourceCluster, ThreadNetworkDiagnosticsCluster, } from '@project-chip/matter-node.js/cluster';
34
+ import { BasicInformationCluster, BooleanStateCluster, BridgedDeviceBasicInformationCluster, ClusterServer, GeneralCommissioning, PowerSourceCluster, ThreadNetworkDiagnosticsCluster } from '@project-chip/matter-node.js/cluster';
35
35
  import { DeviceTypeId, VendorId } from '@project-chip/matter-node.js/datatype';
36
36
  import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter-node.js/device';
37
37
  import { Format, Level, Logger } from '@project-chip/matter-node.js/log';
@@ -222,6 +222,9 @@ export class Matterbridge extends EventEmitter {
222
222
  /*
223
223
  npm > npm.cmd on windows
224
224
  */
225
+ if (process.platform === 'win32' && command === 'npm') {
226
+ command = command + '.cmd';
227
+ }
225
228
  return new Promise((resolve, reject) => {
226
229
  const childProcess = spawn(command, args, {
227
230
  stdio: 'inherit',
@@ -297,7 +300,7 @@ export class Matterbridge extends EventEmitter {
297
300
  // Start the storage (we need it now for frontend and later for matterbridge)
298
301
  await this.startStorage('json', path.join(this.matterbridgeDirectory, 'matterbridge.json'));
299
302
  this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
300
- this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
303
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
301
304
  // Initialize frontend
302
305
  await this.initializeFrontend(getIntParameter('frontend'));
303
306
  if (hasParameter('test')) {
@@ -310,7 +313,7 @@ export class Matterbridge extends EventEmitter {
310
313
  this.bridgeMode = 'controller';
311
314
  this.log.info('Creating mattercontrollerContext: mattercontrollerContext');
312
315
  this.mattercontrollerContext = this.storageManager?.createContext('mattercontrollerContext');
313
- await this.startMatterBridge();
316
+ await this.startMatterbridge();
314
317
  return;
315
318
  }
316
319
  if (hasParameter('bridge')) {
@@ -329,7 +332,7 @@ export class Matterbridge extends EventEmitter {
329
332
  plugin.manualPairingCode = undefined;
330
333
  this.loadPlugin(plugin); // No await do it asyncronously
331
334
  }
332
- await this.startMatterBridge();
335
+ await this.startMatterbridge();
333
336
  return;
334
337
  }
335
338
  if (hasParameter('childbridge')) {
@@ -348,7 +351,7 @@ export class Matterbridge extends EventEmitter {
348
351
  plugin.manualPairingCode = (await plugin.nodeContext?.get('manualPairingCode', undefined)) ?? undefined;
349
352
  this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
350
353
  }
351
- await this.startMatterBridge();
354
+ await this.startMatterbridge();
352
355
  return;
353
356
  }
354
357
  }
@@ -478,6 +481,13 @@ export class Matterbridge extends EventEmitter {
478
481
  await this.cleanup('SIGTERM received, cleaning up...');
479
482
  });
480
483
  }
484
+ /**
485
+ * Update matterbridge.
486
+ */
487
+ async updateProcess() {
488
+ await this.cleanup('updating...', false);
489
+ this.hasCleanupStarted = false;
490
+ }
481
491
  /**
482
492
  * Restarts the process by spawning a new process and exiting the current process.
483
493
  */
@@ -485,6 +495,13 @@ export class Matterbridge extends EventEmitter {
485
495
  await this.cleanup('restarting...', true);
486
496
  this.hasCleanupStarted = false;
487
497
  }
498
+ /**
499
+ * Shut down the process by exiting the current process.
500
+ */
501
+ async shutdownProcess() {
502
+ await this.cleanup('shutting down...', false);
503
+ this.hasCleanupStarted = false;
504
+ }
488
505
  /**
489
506
  * Cleans up the Matterbridge instance.
490
507
  * @param message - The cleanup message.
@@ -536,7 +553,7 @@ export class Matterbridge extends EventEmitter {
536
553
  this.expressApp.removeAllListeners();
537
554
  this.expressApp = undefined;
538
555
  }
539
- const cleanupTimeout1 = setTimeout(async () => {
556
+ /*const cleanupTimeout1 =*/ setTimeout(async () => {
540
557
  // Closing matter
541
558
  await this.stopMatter();
542
559
  // Closing storage
@@ -564,21 +581,28 @@ export class Matterbridge extends EventEmitter {
564
581
  this.registeredPlugins = [];
565
582
  this.registeredDevices = [];
566
583
  this.log.info('Waiting for matter to deliver last messages...');
567
- const cleanupTimeout2 = setTimeout(async () => {
584
+ /*const cleanupTimeout2 =*/ setTimeout(async () => {
568
585
  if (restart) {
569
- this.log.info('Cleanup completed. Restarting...');
570
- Matterbridge.instance = undefined;
571
- this.emit('restart');
586
+ if (message === 'updating...') {
587
+ this.log.info('Cleanup completed. Updating...');
588
+ Matterbridge.instance = undefined;
589
+ this.emit('update');
590
+ }
591
+ else if (message === 'restarting...') {
592
+ this.log.info('Cleanup completed. Restarting...');
593
+ Matterbridge.instance = undefined;
594
+ this.emit('restart');
595
+ }
572
596
  }
573
597
  else {
574
598
  this.log.info('Cleanup completed. Shutting down...');
575
599
  Matterbridge.instance = undefined;
576
600
  this.emit('shutdown');
577
601
  }
578
- }, 2 * 1000);
579
- cleanupTimeout2.unref();
602
+ }, 1 * 1000);
603
+ //cleanupTimeout2.unref();
580
604
  }, 3 * 1000);
581
- cleanupTimeout1.unref();
605
+ //cleanupTimeout1.unref();
582
606
  }
583
607
  }
584
608
  /**
@@ -825,50 +849,98 @@ export class Matterbridge extends EventEmitter {
825
849
  this.mattercontrollerContext = undefined;
826
850
  }
827
851
  async testStartMatterBridge() {
828
- /*
829
- this.log.error('****Start forEach registeredPlugin');
830
- this.registeredPlugins
831
- .filter((plugin) => plugin.enabled === true)
832
- .forEach(async (plugin) => {
833
- plugin.loaded = false;
834
- plugin.started = false;
835
- plugin.configured = false;
836
- plugin.connected = undefined;
837
- this.log.error(`****Starting registeredPlugin ${plugin.name}`);
838
- this.loadPlugin(plugin, true, 'Matterbridge is starting');
839
- });
840
- this.log.error('****Stop forEach registeredPlugin');
841
- */
842
- /*
843
- for (const plugin of this.registeredPlugins) {
844
- if (!plugin.enabled) continue;
845
- // No await do it asyncronously
846
- this.loadPlugin(plugin)
847
- .then(() => {
848
- // No await do it asyncronously
849
- this.startPlugin(plugin)
850
- .then(() => {})
851
- .catch((err) => {
852
- this.log.error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`);
853
- });
854
- })
855
- .catch((err) => {
856
- this.log.error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`);
857
- });
852
+ // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
853
+ // Plugins are started and configured by callback when Matterbridge is commissioned
854
+ if (!this.storageManager) {
855
+ this.log.error('No storage manager initialized');
856
+ await this.cleanup('No storage manager initialized');
857
+ return;
858
858
  }
859
- for (const plugin of this.registeredPlugins) {
860
- if (!plugin.enabled) continue;
861
- // Start the interval to check if the plugin is loaded and started
862
- let times = 0;
863
- const interval = setInterval(() => {
864
- times++;
865
- this.log.info(`Waiting ${times} secs for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) and send devices ...`);
866
- if (!plugin.loaded || !plugin.started) return;
867
- this.log.info(`Plugin ${plg}${plugin.name}${db} sent ${plugin.registeredDevices} devices`);
868
- clearInterval(interval);
869
- }, 1000);
859
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
860
+ this.createMatterServer(this.storageManager);
861
+ if (!this.matterServer) {
862
+ this.log.error('No matter server initialized');
863
+ await this.cleanup('No matter server initialized');
864
+ return;
870
865
  }
871
- */
866
+ this.log.debug('***Starting startMatterbridge interval for Matterbridge');
867
+ let failCount = 0;
868
+ const startInterval = setInterval(async () => {
869
+ for (const plugin of this.registeredPlugins) {
870
+ if (!plugin.enabled)
871
+ continue;
872
+ if (!plugin.loaded) {
873
+ this.log.info(`***Waiting (failSafeCount=${failCount}/30) in startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded}...`);
874
+ failCount++;
875
+ if (failCount > 30) {
876
+ this.log.error(`***Failed to load plugin ${plg}${plugin.name}${er}`);
877
+ plugin.error = true;
878
+ }
879
+ else {
880
+ return;
881
+ }
882
+ }
883
+ }
884
+ clearInterval(startInterval);
885
+ this.log.debug('***Cleared startMatterbridge interval for Matterbridge');
886
+ this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
887
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
888
+ if (!this.matterbridgeContext) {
889
+ this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
890
+ return;
891
+ }
892
+ if (!this.nodeContext) {
893
+ this.log.error(`Node storage context undefined for ${plg}Matterbridge${er}`);
894
+ return;
895
+ }
896
+ await this.matterbridgeContext.set('softwareVersion', 1);
897
+ await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
898
+ this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
899
+ this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
900
+ this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
901
+ this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext);
902
+ this.log.debug('Adding matterbridge aggregator to commissioning server');
903
+ this.commissioningServer.addDevice(this.matterAggregator);
904
+ const device = new MatterbridgeDevice(DeviceTypes.CONTACT_SENSOR);
905
+ device.createDefaultIdentifyClusterServer();
906
+ //device.createDefaultBasicInformationClusterServer('Boolean test', '0x89930475', 0x8000, 'Matterbridge', 77, 'Boolean');
907
+ device.createDefaultBridgedDeviceBasicInformationClusterServer('Boolean test', '0x89930475', 0x8000, 'Matterbridge', 'Boolean');
908
+ device.createDefaultBooleanStateClusterServer(true);
909
+ device.createDefaultPowerSourceReplaceableBatteryClusterServer(75);
910
+ device.createDefaultPowerSourceConfigurationClusterServer(1);
911
+ //this.commissioningServer.addDevice(device);
912
+ //this.matterAggregator.addBridgedDevice(device);
913
+ this.log.debug('Adding matterbridge commissioning server to matter server');
914
+ await this.matterServer?.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
915
+ this.log.debug(`Setting reachability to true for ${plg}Matterbridge${db}`);
916
+ this.commissioningServer.setReachability(true);
917
+ this.log.debug('Starting matter server...');
918
+ await this.startMatterServer();
919
+ this.log.info('Matter server started');
920
+ await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
921
+ //2024-03-31 21:54:12.065 DEBUG InteractionServer Received subscribe request from udp://fe80::14b6:bb16:5d3a:1293%9:51251 on session secure/48170 (keepSubscriptions=true, isFabricFiltered=false)
922
+ //2024-03-31 21:54:12.066 DEBUG InteractionServer Subscribe to attributes:*/*/*, events:!*/*/*
923
+ /*
924
+ 2024-03-31 21:52:22.669 DEBUG SubscriptionHandler Sending subscription update message for ID 2529153003 with 1 attributes and 1 events
925
+ 2024-03-31 21:52:22.669 DEBUG MessageExchange New exchange protocol: 1 id: 30278 session: secure/24489 peerSessionId: 2131 active threshold ms: 4000 active interval ms: 300 idle interval ms: 300 retries: 5
926
+ 2024-03-31 21:52:22.669 DEBUG SubscriptionHandler Sending subscription changes for ID 2529153003: MA-contactsensor(0x3b)/BooleanState(0x45)/stateValue(0x0)=true (1805472651)
927
+ 2024-03-31 21:52:22.670 DEBUG InteractionMessenger Sending DataReport chunk with 1 attributes and 1 events: 85 bytes
928
+ */
929
+ setTimeout(() => {
930
+ this.matterAggregator?.addBridgedDevice(device);
931
+ this.log.info('Added device to aggregator');
932
+ }, 30 * 1000);
933
+ setInterval(() => {
934
+ const cluster = device.getClusterServer(BooleanStateCluster);
935
+ if (!cluster)
936
+ return;
937
+ const contact = cluster.getStateValueAttribute();
938
+ cluster.setStateValueAttribute(!contact);
939
+ if (cluster.isEventSupportedByName('stateChange'))
940
+ cluster.triggerStateChangeEvent({ stateValue: !contact });
941
+ this.log.info('Set attribute and event for BooleanStateCluster to', !contact);
942
+ }, 60 * 1000);
943
+ }, 1000);
872
944
  }
873
945
  /**
874
946
  * Loads the configuration for a plugin.
@@ -884,8 +956,12 @@ export class Matterbridge extends EventEmitter {
884
956
  try {
885
957
  await fs.access(configFile);
886
958
  const data = await fs.readFile(configFile, 'utf8');
887
- this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, JSON.parse(data));
888
- return JSON.parse(data);
959
+ const config = JSON.parse(data);
960
+ this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, config);
961
+ /* The first time a plugin is added to the system, the config file is created with the plugin name and type "".*/
962
+ config.name = plugin.name;
963
+ config.type = plugin.type;
964
+ return config;
889
965
  }
890
966
  catch (err) {
891
967
  if (err instanceof Error) {
@@ -1071,12 +1147,14 @@ export class Matterbridge extends EventEmitter {
1071
1147
  }
1072
1148
  else {
1073
1149
  this.log.error(`Plugin ${plg}${plugin.name}${er} does not provide a default export`);
1074
- return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} does not provide a default export`));
1150
+ return;
1151
+ //return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} does not provide a default export`));
1075
1152
  }
1076
1153
  }
1077
1154
  catch (err) {
1078
1155
  this.log.error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`);
1079
- return Promise.reject(new Error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`));
1156
+ return;
1157
+ //return Promise.reject(new Error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`));
1080
1158
  }
1081
1159
  }
1082
1160
  /**
@@ -1088,7 +1166,7 @@ export class Matterbridge extends EventEmitter {
1088
1166
  * @private
1089
1167
  * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1090
1168
  */
1091
- async startMatterBridge() {
1169
+ async startMatterbridge() {
1092
1170
  if (!this.storageManager) {
1093
1171
  this.log.error('No storage manager initialized');
1094
1172
  await this.cleanup('No storage manager initialized');
@@ -1129,7 +1207,7 @@ export class Matterbridge extends EventEmitter {
1129
1207
  this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1130
1208
  }
1131
1209
  else {
1132
- longDiscriminator = this.mattercontrollerContext.get('longDiscriminator', 3840);
1210
+ longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1133
1211
  if (longDiscriminator > 4095)
1134
1212
  throw new Error('Discriminator value must be less than 4096');
1135
1213
  setupPin = this.mattercontrollerContext.get('pin', 20202021);
@@ -1238,30 +1316,62 @@ export class Matterbridge extends EventEmitter {
1238
1316
  if (this.bridgeMode === 'bridge') {
1239
1317
  // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
1240
1318
  // Plugins are started and configured by callback when Matterbridge is commissioned
1241
- this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1242
- this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
1243
- if (!this.matterbridgeContext) {
1244
- this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
1245
- return;
1246
- }
1247
- if (!this.nodeContext) {
1248
- this.log.error(`Node storage context undefined for ${plg}Matterbridge${er}`);
1249
- return;
1250
- }
1251
- this.matterbridgeContext.set('softwareVersion', 1);
1252
- this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
1253
- this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1254
- this.commissioningServer = this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1255
- this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1256
- this.matterAggregator = this.createMatterAggregator(this.matterbridgeContext);
1257
- this.log.debug('Adding matterbridge aggregator to commissioning server');
1258
- this.commissioningServer.addDevice(this.matterAggregator);
1259
- this.log.debug('Adding matterbridge commissioning server to matter server');
1260
- await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1261
- this.log.debug('Starting matter server...');
1262
- await this.startMatterServer();
1263
- this.log.info('Matter server started');
1264
- await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1319
+ this.log.debug('***Starting startMatterbridge interval for Matterbridge');
1320
+ let failCount = 0;
1321
+ const startInterval = setInterval(async () => {
1322
+ for (const plugin of this.registeredPlugins) {
1323
+ if (!plugin.enabled)
1324
+ continue;
1325
+ if (!plugin.loaded) {
1326
+ this.log.info(`***Waiting (failSafeCount=${failCount}/30) in startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded}...`);
1327
+ failCount++;
1328
+ if (failCount > 30) {
1329
+ this.log.error(`***Failed to load plugin ${plg}${plugin.name}${er}`);
1330
+ plugin.error = true;
1331
+ }
1332
+ else {
1333
+ return;
1334
+ }
1335
+ }
1336
+ }
1337
+ clearInterval(startInterval);
1338
+ this.log.debug('***Cleared startMatterbridge interval for Matterbridge');
1339
+ this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1340
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
1341
+ if (!this.matterbridgeContext) {
1342
+ this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
1343
+ return;
1344
+ }
1345
+ if (!this.nodeContext) {
1346
+ this.log.error(`Node storage context undefined for ${plg}Matterbridge${er}`);
1347
+ return;
1348
+ }
1349
+ await this.matterbridgeContext.set('softwareVersion', 1);
1350
+ await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
1351
+ this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1352
+ this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1353
+ this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1354
+ this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext);
1355
+ this.log.debug('Adding matterbridge aggregator to commissioning server');
1356
+ this.commissioningServer.addDevice(this.matterAggregator);
1357
+ this.log.debug('Adding matterbridge commissioning server to matter server');
1358
+ await this.matterServer?.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1359
+ this.log.debug(`Setting reachability to true for ${plg}Matterbridge${db}`);
1360
+ this.commissioningServer.setReachability(true);
1361
+ this.log.debug('Starting matter server...');
1362
+ await this.startMatterServer();
1363
+ this.log.info('Matter server started');
1364
+ await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1365
+ /*
1366
+ setInterval(() => {
1367
+ this.matterAggregator?.getBridgedDevices().forEach((device) => {
1368
+ this.log.info(`Bridged device: ${dev}${device.name}${nf}`);
1369
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(true);
1370
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: true });
1371
+ });
1372
+ }, 30 * 1000);
1373
+ */
1374
+ }, 1000);
1265
1375
  }
1266
1376
  if (this.bridgeMode === 'childbridge') {
1267
1377
  // Plugins are loaded and started by loadPlugin on startup
@@ -1272,45 +1382,50 @@ export class Matterbridge extends EventEmitter {
1272
1382
  if (!plugin.enabled)
1273
1383
  return;
1274
1384
  // Start the interval to check if the plugins is started
1275
- // TODO set a counter or a timeout
1276
- this.log.debug(`*Starting startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1385
+ this.log.debug(`*Starting startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1386
+ let failCount = 0;
1277
1387
  const startInterval = setInterval(async () => {
1278
- if (!this.matterServer) {
1279
- this.log.error('No matter server initialized');
1280
- await this.cleanup('No matter server initialized');
1281
- return;
1282
- }
1283
- if (!plugin.loaded || !plugin.started) {
1284
- this.log.info(`**Waiting in startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1388
+ if (!plugin.loaded || !plugin.started /* || !plugin.configured*/) {
1389
+ this.log.info(`***Waiting (failSafeCount=${failCount}/30) in startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1390
+ failCount++;
1391
+ if (failCount > 30) {
1392
+ this.log.error(`***Failed to load plugin ${plg}${plugin.name}${er}`);
1393
+ plugin.error = true;
1394
+ clearInterval(startInterval);
1395
+ }
1285
1396
  return;
1286
1397
  }
1287
1398
  if (plugin.type === 'AccessoryPlatform') {
1288
- this.registeredDevices
1289
- .filter((registeredDevice) => registeredDevice.plugin === plugin.name)
1290
- .forEach((registeredDevice) => {
1399
+ for (const registeredDevice of this.registeredDevices) {
1400
+ if (registeredDevice.plugin !== plugin.name)
1401
+ continue;
1291
1402
  if (!plugin.storageContext)
1292
- plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device);
1403
+ plugin.storageContext = await this.importCommissioningServerContext(plugin.name, registeredDevice.device);
1404
+ await plugin.storageContext.set('softwareVersion', 1);
1405
+ await plugin.storageContext.set('softwareVersionString', this.matterbridgeVersion);
1293
1406
  if (!plugin.commissioningServer)
1294
- plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
1407
+ plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1295
1408
  this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
1296
1409
  plugin.commissioningServer.addDevice(registeredDevice.device);
1297
- });
1410
+ }
1298
1411
  this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
1299
- await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1412
+ await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1300
1413
  }
1301
1414
  if (plugin.type === 'DynamicPlatform') {
1302
- plugin.storageContext = this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Dynamic Platform');
1303
- plugin.storageContext.set('softwareVersion', 1);
1304
- plugin.storageContext.set('softwareVersionString', this.matterbridgeVersion);
1305
- plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
1415
+ // eslint-disable-next-line max-len
1416
+ plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Dynamic Platform');
1417
+ await plugin.storageContext.set('softwareVersion', 1);
1418
+ await plugin.storageContext.set('softwareVersionString', this.matterbridgeVersion);
1419
+ plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1306
1420
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
1307
- plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
1421
+ plugin.aggregator = await this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
1308
1422
  this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
1309
1423
  plugin.commissioningServer.addDevice(plugin.aggregator);
1310
1424
  this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
1311
- await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1425
+ await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1312
1426
  }
1313
1427
  clearInterval(startInterval);
1428
+ this.log.debug(`*Cleared startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1314
1429
  }, 1000);
1315
1430
  });
1316
1431
  // Start the interval to check if all plugins are loaded and started and so start the matter server
@@ -1321,10 +1436,12 @@ export class Matterbridge extends EventEmitter {
1321
1436
  this.registeredPlugins.forEach((plugin) => {
1322
1437
  if (!plugin.enabled)
1323
1438
  return;
1439
+ if (plugin.error)
1440
+ return;
1324
1441
  if (plugin.enabled && (!plugin.loaded || !plugin.started))
1325
1442
  allStarted = false;
1326
1443
  if (!allStarted)
1327
- this.log.info(`**Waiting in start matter server interval for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
1444
+ this.log.info(`***Waiting in start matter server interval for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
1328
1445
  });
1329
1446
  if (!allStarted)
1330
1447
  return;
@@ -1389,14 +1506,14 @@ export class Matterbridge extends EventEmitter {
1389
1506
  * @returns The commissioning server context.
1390
1507
  * @throws Error if the BasicInformationCluster is not found.
1391
1508
  */
1392
- importCommissioningServerContext(pluginName, device) {
1509
+ async importCommissioningServerContext(pluginName, device) {
1393
1510
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
1394
1511
  const basic = device.getClusterServer(BasicInformationCluster);
1395
1512
  if (!basic) {
1396
1513
  throw new Error('importCommissioningServerContext error: cannot find the BasicInformationCluster');
1397
1514
  }
1398
1515
  //const random = 'CS' + CryptoNode.getRandomData(8).toHex();
1399
- return this.createCommissioningServerContext(pluginName, basic.getNodeLabelAttribute(), DeviceTypeId(device.deviceType), basic.getVendorIdAttribute(), basic.getVendorNameAttribute(), basic.getProductIdAttribute(), basic.getProductNameAttribute(), basic.attributes.serialNumber?.getLocal(), basic.attributes.uniqueId?.getLocal(), basic.attributes.softwareVersion?.getLocal(), basic.attributes.softwareVersionString?.getLocal(), basic.attributes.hardwareVersion?.getLocal(), basic.attributes.hardwareVersionString?.getLocal());
1516
+ return await this.createCommissioningServerContext(pluginName, basic.getNodeLabelAttribute(), DeviceTypeId(device.deviceType), basic.getVendorIdAttribute(), basic.getVendorNameAttribute(), basic.getProductIdAttribute(), basic.getProductNameAttribute(), basic.attributes.serialNumber?.getLocal(), basic.attributes.uniqueId?.getLocal(), basic.getSoftwareVersionAttribute(), basic.getSoftwareVersionStringAttribute(), basic.getHardwareVersionAttribute(), basic.getHardwareVersionStringAttribute());
1400
1517
  }
1401
1518
  /**
1402
1519
  * Creates a commissioning server storage context.
@@ -1416,7 +1533,7 @@ export class Matterbridge extends EventEmitter {
1416
1533
  * @param hardwareVersionString - The hardware version string of the device (optional).
1417
1534
  * @returns The storage context for the commissioning server.
1418
1535
  */
1419
- createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) {
1536
+ async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) {
1420
1537
  if (!this.storageManager) {
1421
1538
  this.log.error('No storage manager initialized');
1422
1539
  process.exit(1);
@@ -1424,20 +1541,23 @@ export class Matterbridge extends EventEmitter {
1424
1541
  this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
1425
1542
  const random = 'CS' + CryptoNode.getRandomData(8).toHex();
1426
1543
  const storageContext = this.storageManager.createContext(pluginName);
1427
- storageContext.set('deviceName', deviceName);
1428
- storageContext.set('deviceType', deviceType);
1429
- storageContext.set('vendorId', vendorId);
1430
- storageContext.set('vendorName', vendorName.slice(0, 32));
1431
- storageContext.set('productId', productId);
1432
- storageContext.set('productName', productName.slice(0, 32));
1433
- storageContext.set('nodeLabel', productName.slice(0, 32));
1434
- storageContext.set('productLabel', productName.slice(0, 32));
1435
- storageContext.set('serialNumber', storageContext.get('serialNumber', random));
1436
- storageContext.set('uniqueId', storageContext.get('uniqueId', random));
1437
- storageContext.set('softwareVersion', softwareVersion ?? 1);
1438
- storageContext.set('softwareVersionString', softwareVersionString ?? '1.0');
1439
- storageContext.set('hardwareVersion', hardwareVersion ?? 1);
1440
- storageContext.set('hardwareVersionString', hardwareVersionString ?? '1.0');
1544
+ await storageContext.set('deviceName', deviceName);
1545
+ await storageContext.set('deviceType', deviceType);
1546
+ await storageContext.set('vendorId', vendorId);
1547
+ await storageContext.set('vendorName', vendorName.slice(0, 32));
1548
+ await storageContext.set('productId', productId);
1549
+ await storageContext.set('productName', productName.slice(0, 32));
1550
+ await storageContext.set('nodeLabel', productName.slice(0, 32));
1551
+ await storageContext.set('productLabel', productName.slice(0, 32));
1552
+ await storageContext.set('serialNumber', await storageContext.get('serialNumber', random));
1553
+ await storageContext.set('uniqueId', await storageContext.get('uniqueId', random));
1554
+ await storageContext.set('softwareVersion', softwareVersion ?? 1);
1555
+ await storageContext.set('softwareVersionString', softwareVersionString ?? '1.0.0');
1556
+ await storageContext.set('hardwareVersion', hardwareVersion ?? 1);
1557
+ await storageContext.set('hardwareVersionString', hardwareVersionString ?? '1.0.0');
1558
+ this.log.debug(`**Created commissioning server storage context for ${plg}${pluginName}${db}`);
1559
+ this.log.debug(`**- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1560
+ this.log.debug(`**- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1441
1561
  return storageContext;
1442
1562
  }
1443
1563
  /**
@@ -1502,20 +1622,20 @@ export class Matterbridge extends EventEmitter {
1502
1622
  * @param {string} pluginName - The name of the commissioning server.
1503
1623
  * @returns {CommissioningServer} The created commissioning server.
1504
1624
  */
1505
- createCommisioningServer(context, pluginName) {
1625
+ async createCommisioningServer(context, pluginName) {
1506
1626
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
1507
- const deviceName = context.get('deviceName');
1508
- const deviceType = context.get('deviceType');
1509
- const vendorId = context.get('vendorId');
1510
- const vendorName = context.get('vendorName'); // Home app = Manufacturer
1511
- const productId = context.get('productId');
1512
- const productName = context.get('productName'); // Home app = Model
1513
- const serialNumber = context.get('serialNumber');
1514
- const uniqueId = context.get('uniqueId');
1515
- const softwareVersion = context.get('softwareVersion', 1);
1516
- const softwareVersionString = context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1517
- const hardwareVersion = context.get('hardwareVersion', 1);
1518
- const hardwareVersionString = context.get('hardwareVersionString', '1.0.0');
1627
+ const deviceName = await context.get('deviceName');
1628
+ const deviceType = await context.get('deviceType');
1629
+ const vendorId = await context.get('vendorId');
1630
+ const vendorName = await context.get('vendorName'); // Home app = Manufacturer
1631
+ const productId = await context.get('productId');
1632
+ const productName = await context.get('productName'); // Home app = Model
1633
+ const serialNumber = await context.get('serialNumber');
1634
+ const uniqueId = await context.get('uniqueId');
1635
+ const softwareVersion = await context.get('softwareVersion', 1);
1636
+ const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1637
+ const hardwareVersion = await context.get('hardwareVersion', 1);
1638
+ const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
1519
1639
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName ${deviceName} deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
1520
1640
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with uniqueId ${uniqueId} serialNumber ${serialNumber}`);
1521
1641
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
@@ -1645,10 +1765,10 @@ export class Matterbridge extends EventEmitter {
1645
1765
  * @param {StorageContext} context - The storage context.
1646
1766
  * @returns {Aggregator} - The created Matter Aggregator.
1647
1767
  */
1648
- createMatterAggregator(context) {
1768
+ async createMatterAggregator(context) {
1649
1769
  const random = 'AG' + CryptoNode.getRandomData(8).toHex();
1650
- context.set('aggregatorSerialNumber', context.get('aggregatorSerialNumber', random));
1651
- context.set('aggregatorUniqueId', context.get('aggregatorUniqueId', random));
1770
+ await context.set('aggregatorSerialNumber', await context.get('aggregatorSerialNumber', random));
1771
+ await context.set('aggregatorUniqueId', await context.get('aggregatorUniqueId', random));
1652
1772
  const matterAggregator = new Aggregator();
1653
1773
  matterAggregator.addClusterServer(ClusterServer(BasicInformationCluster, {
1654
1774
  dataModelRevision: 1,
@@ -1659,8 +1779,8 @@ export class Matterbridge extends EventEmitter {
1659
1779
  productName: 'Matterbridge aggregator',
1660
1780
  productLabel: 'Matterbridge aggregator',
1661
1781
  nodeLabel: 'Matterbridge aggregator',
1662
- serialNumber: context.get('aggregatorSerialNumber'),
1663
- uniqueId: context.get('aggregatorUniqueId'),
1782
+ serialNumber: await context.get('aggregatorSerialNumber'),
1783
+ uniqueId: await context.get('aggregatorUniqueId'),
1664
1784
  softwareVersion: 1,
1665
1785
  softwareVersionString: 'v.1.0',
1666
1786
  hardwareVersion: 1,
@@ -1942,6 +2062,26 @@ export class Matterbridge extends EventEmitter {
1942
2062
  this.expressApp = express();
1943
2063
  // Serve React build directory
1944
2064
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2065
+ // Endpoint to provide login code
2066
+ this.expressApp.post('/api/login', express.json(), async (req, res) => {
2067
+ const { password } = req.body;
2068
+ this.log.debug('The frontend sent /api/login', password);
2069
+ if (!this.nodeContext) {
2070
+ this.log.error('/api/login nodeContext not found');
2071
+ res.json({ valid: false });
2072
+ return;
2073
+ }
2074
+ try {
2075
+ const storedPassword = await this.nodeContext.get('password', '');
2076
+ if (storedPassword === '' || password === storedPassword)
2077
+ res.json({ valid: true });
2078
+ else
2079
+ res.json({ valid: false });
2080
+ }
2081
+ catch (error) {
2082
+ res.json({ valid: false });
2083
+ }
2084
+ });
1945
2085
  // Endpoint to provide QR pairing code
1946
2086
  this.expressApp.get('/api/qr-code', (req, res) => {
1947
2087
  this.log.debug('The frontend sent /api/qr-code');
@@ -1995,7 +2135,7 @@ export class Matterbridge extends EventEmitter {
1995
2135
  data.push({
1996
2136
  pluginName: registeredDevice.plugin,
1997
2137
  type: registeredDevice.device.name + ' (0x' + registeredDevice.device.deviceType.toString(16).padStart(4, '0') + ')',
1998
- endpoint: registeredDevice.device.id,
2138
+ endpoint: registeredDevice.device.number,
1999
2139
  name,
2000
2140
  serial,
2001
2141
  uniqueId,
@@ -2015,7 +2155,7 @@ export class Matterbridge extends EventEmitter {
2015
2155
  }
2016
2156
  const data = [];
2017
2157
  this.registeredDevices.forEach((registeredDevice) => {
2018
- if (registeredDevice.plugin === selectedPluginName && registeredDevice.device.id === selectedDeviceEndpoint) {
2158
+ if (registeredDevice.plugin === selectedPluginName && registeredDevice.device.number === selectedDeviceEndpoint) {
2019
2159
  const clusterServers = registeredDevice.device.getAllClusterServers();
2020
2160
  clusterServers.forEach((clusterServer) => {
2021
2161
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
@@ -2056,7 +2196,13 @@ export class Matterbridge extends EventEmitter {
2056
2196
  res.status(400).json({ error: 'No command provided' });
2057
2197
  return;
2058
2198
  }
2059
- this.log.info(`***Received command: ${command}:${param}`);
2199
+ this.log.debug(`*Received frontend command: ${command}:${param}`);
2200
+ // Handle the command setpassword from Settings
2201
+ if (command === 'setpassword') {
2202
+ const password = param.slice(1, -1); // Remove the first and last characters
2203
+ this.log.info('setpassword', param, password);
2204
+ await this.nodeContext?.set('password', password);
2205
+ }
2060
2206
  // Handle the command debugLevel from Settings
2061
2207
  if (command === 'setloglevel') {
2062
2208
  if (param === 'Debug') {
@@ -2078,15 +2224,47 @@ export class Matterbridge extends EventEmitter {
2078
2224
  plugin.platform?.log.setLogDebug(this.debugEnabled);
2079
2225
  });
2080
2226
  }
2081
- // Handle the command debugLevel from Header
2227
+ // Handle the command shutdown from Header
2228
+ if (command === 'shutdown') {
2229
+ this.shutdownProcess();
2230
+ }
2231
+ // Handle the command restart from Header
2082
2232
  if (command === 'restart') {
2083
2233
  this.restartProcess();
2084
2234
  }
2085
2235
  // Handle the command update from Header
2236
+ if (command === 'update') {
2237
+ this.log.warn(`***Updating matterbridge ${plg}${param}${db}`);
2238
+ try {
2239
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2240
+ this.log.info('Matterbridge has been updated. Full restart required.');
2241
+ }
2242
+ catch (error) {
2243
+ this.log.error('Error updating matterbridge');
2244
+ res.json({ message: 'Command received' });
2245
+ return;
2246
+ }
2247
+ this.updateProcess();
2248
+ }
2249
+ // Handle the command update from Header
2086
2250
  if (command === 'update') {
2087
2251
  this.log.warn(`The /api/command/${command} is not yet implemented`);
2088
2252
  }
2089
- // Handle the command addplugin from Home C:\Users\lligu\GitHub\matterbridge-example-accessory-platform
2253
+ // Handle the command installplugin from Home
2254
+ if (command === 'installplugin') {
2255
+ param = param.replace(/\*/g, '\\');
2256
+ this.log.warn(`***Installing plugin ${plg}${param}${db}`);
2257
+ try {
2258
+ await this.spawnCommand('npm', ['install', '-g', param]);
2259
+ this.log.info(`Plugin ${plg}${param}${nf} installed. Restart required.`);
2260
+ }
2261
+ catch (error) {
2262
+ this.log.error(`Error installing plugin ${plg}${param}${er}`);
2263
+ res.json({ message: 'Command received' });
2264
+ return;
2265
+ }
2266
+ }
2267
+ // Handle the command addplugin from Home
2090
2268
  if (command === 'addplugin') {
2091
2269
  param = param.replace(/\*/g, '\\');
2092
2270
  if (this.registeredPlugins.find((plugin) => plugin.name === param)) {
@@ -2156,7 +2334,15 @@ export class Matterbridge extends EventEmitter {
2156
2334
  if (pluginToEnable) {
2157
2335
  pluginToEnable.enabled = true;
2158
2336
  pluginToEnable.platform = await this.loadPlugin(pluginToEnable);
2159
- await this.startPlugin(pluginToEnable, 'The plugin has been enabled', true);
2337
+ if (pluginToEnable.platform) {
2338
+ await this.startPlugin(pluginToEnable, 'The plugin has been enabled', true);
2339
+ pluginToEnable.enabled = false;
2340
+ }
2341
+ else {
2342
+ pluginToEnable.enabled = false;
2343
+ pluginToEnable.error = true;
2344
+ this.log.error(`Error enabling plugin ${plg}${param}${er}`);
2345
+ }
2160
2346
  }
2161
2347
  }
2162
2348
  }
@@ -2193,7 +2379,7 @@ export class Matterbridge extends EventEmitter {
2193
2379
  });
2194
2380
  // Fallback for routing
2195
2381
  this.expressApp.get('*', (req, res) => {
2196
- this.log.warn('The frontend sent *', req.url);
2382
+ this.log.debug('The frontend sent *', req.url);
2197
2383
  res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
2198
2384
  });
2199
2385
  this.expressServer = this.expressApp.listen(port, () => {
@@ -2220,6 +2406,8 @@ export class Matterbridge extends EventEmitter {
2220
2406
  attributes += `Cover position: ${clusterServer.attributes.currentPositionLiftPercent100ths.getLocal() / 100}% `;
2221
2407
  if (clusterServer.name === 'DoorLock')
2222
2408
  attributes += `State: ${clusterServer.attributes.lockState.getLocal() === 1 ? 'Locked' : 'Not locked'} `;
2409
+ if (clusterServer.name === 'Thermostat')
2410
+ attributes += `Temperature: ${clusterServer.attributes.localTemperature.getLocal() / 100}°C `;
2223
2411
  if (clusterServer.name === 'LevelControl')
2224
2412
  attributes += `Level: ${clusterServer.getCurrentLevelAttribute()}% `;
2225
2413
  if (clusterServer.name === 'ColorControl')