matterbridge 1.2.2 → 1.2.4

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.
@@ -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';
@@ -297,7 +297,7 @@ export class Matterbridge extends EventEmitter {
297
297
  // Start the storage (we need it now for frontend and later for matterbridge)
298
298
  await this.startStorage('json', path.join(this.matterbridgeDirectory, 'matterbridge.json'));
299
299
  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');
300
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
301
301
  // Initialize frontend
302
302
  await this.initializeFrontend(getIntParameter('frontend'));
303
303
  if (hasParameter('test')) {
@@ -310,7 +310,7 @@ export class Matterbridge extends EventEmitter {
310
310
  this.bridgeMode = 'controller';
311
311
  this.log.info('Creating mattercontrollerContext: mattercontrollerContext');
312
312
  this.mattercontrollerContext = this.storageManager?.createContext('mattercontrollerContext');
313
- await this.startMatterBridge();
313
+ await this.startMatterbridge();
314
314
  return;
315
315
  }
316
316
  if (hasParameter('bridge')) {
@@ -329,7 +329,7 @@ export class Matterbridge extends EventEmitter {
329
329
  plugin.manualPairingCode = undefined;
330
330
  this.loadPlugin(plugin); // No await do it asyncronously
331
331
  }
332
- await this.startMatterBridge();
332
+ await this.startMatterbridge();
333
333
  return;
334
334
  }
335
335
  if (hasParameter('childbridge')) {
@@ -348,7 +348,7 @@ export class Matterbridge extends EventEmitter {
348
348
  plugin.manualPairingCode = (await plugin.nodeContext?.get('manualPairingCode', undefined)) ?? undefined;
349
349
  this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
350
350
  }
351
- await this.startMatterBridge();
351
+ await this.startMatterbridge();
352
352
  return;
353
353
  }
354
354
  }
@@ -825,50 +825,98 @@ export class Matterbridge extends EventEmitter {
825
825
  this.mattercontrollerContext = undefined;
826
826
  }
827
827
  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
- });
828
+ // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
829
+ // Plugins are started and configured by callback when Matterbridge is commissioned
830
+ if (!this.storageManager) {
831
+ this.log.error('No storage manager initialized');
832
+ await this.cleanup('No storage manager initialized');
833
+ return;
858
834
  }
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);
835
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
836
+ this.createMatterServer(this.storageManager);
837
+ if (!this.matterServer) {
838
+ this.log.error('No matter server initialized');
839
+ await this.cleanup('No matter server initialized');
840
+ return;
870
841
  }
871
- */
842
+ this.log.debug('***Starting startMatterbridge interval for Matterbridge');
843
+ let failCount = 0;
844
+ const startInterval = setInterval(async () => {
845
+ for (const plugin of this.registeredPlugins) {
846
+ if (!plugin.enabled)
847
+ continue;
848
+ if (!plugin.loaded) {
849
+ this.log.info(`***Waiting (failSafeCount=${failCount}/30) in startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded}...`);
850
+ failCount++;
851
+ if (failCount > 30) {
852
+ this.log.error(`***Failed to load plugin ${plg}${plugin.name}${er}`);
853
+ plugin.error = true;
854
+ }
855
+ else {
856
+ return;
857
+ }
858
+ }
859
+ }
860
+ clearInterval(startInterval);
861
+ this.log.debug('***Cleared startMatterbridge interval for Matterbridge');
862
+ this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
863
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
864
+ if (!this.matterbridgeContext) {
865
+ this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
866
+ return;
867
+ }
868
+ if (!this.nodeContext) {
869
+ this.log.error(`Node storage context undefined for ${plg}Matterbridge${er}`);
870
+ return;
871
+ }
872
+ await this.matterbridgeContext.set('softwareVersion', 1);
873
+ await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
874
+ this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
875
+ this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
876
+ this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
877
+ this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext);
878
+ this.log.debug('Adding matterbridge aggregator to commissioning server');
879
+ this.commissioningServer.addDevice(this.matterAggregator);
880
+ const device = new MatterbridgeDevice(DeviceTypes.CONTACT_SENSOR);
881
+ device.createDefaultIdentifyClusterServer();
882
+ //device.createDefaultBasicInformationClusterServer('Boolean test', '0x89930475', 0x8000, 'Matterbridge', 77, 'Boolean');
883
+ device.createDefaultBridgedDeviceBasicInformationClusterServer('Boolean test', '0x89930475', 0x8000, 'Matterbridge', 'Boolean');
884
+ device.createDefaultBooleanStateClusterServer(true);
885
+ device.createDefaultPowerSourceReplaceableBatteryClusterServer(75);
886
+ device.createDefaultPowerSourceConfigurationClusterServer(1);
887
+ //this.commissioningServer.addDevice(device);
888
+ //this.matterAggregator.addBridgedDevice(device);
889
+ this.log.debug('Adding matterbridge commissioning server to matter server');
890
+ await this.matterServer?.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
891
+ this.log.debug(`Setting reachability to true for ${plg}Matterbridge${db}`);
892
+ this.commissioningServer.setReachability(true);
893
+ this.log.debug('Starting matter server...');
894
+ await this.startMatterServer();
895
+ this.log.info('Matter server started');
896
+ await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
897
+ //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)
898
+ //2024-03-31 21:54:12.066 DEBUG InteractionServer Subscribe to attributes:*/*/*, events:!*/*/*
899
+ /*
900
+ 2024-03-31 21:52:22.669 DEBUG SubscriptionHandler Sending subscription update message for ID 2529153003 with 1 attributes and 1 events
901
+ 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
902
+ 2024-03-31 21:52:22.669 DEBUG SubscriptionHandler Sending subscription changes for ID 2529153003: MA-contactsensor(0x3b)/BooleanState(0x45)/stateValue(0x0)=true (1805472651)
903
+ 2024-03-31 21:52:22.670 DEBUG InteractionMessenger Sending DataReport chunk with 1 attributes and 1 events: 85 bytes
904
+ */
905
+ setTimeout(() => {
906
+ this.matterAggregator?.addBridgedDevice(device);
907
+ this.log.info('Added device to aggregator');
908
+ }, 30 * 1000);
909
+ setInterval(() => {
910
+ const cluster = device.getClusterServer(BooleanStateCluster);
911
+ if (!cluster)
912
+ return;
913
+ const contact = cluster.getStateValueAttribute();
914
+ cluster.setStateValueAttribute(!contact);
915
+ if (cluster.isEventSupportedByName('stateChange'))
916
+ cluster.triggerStateChangeEvent({ stateValue: !contact });
917
+ this.log.info('Set attribute and event for BooleanStateCluster to', !contact);
918
+ }, 60 * 1000);
919
+ }, 1000);
872
920
  }
873
921
  /**
874
922
  * Loads the configuration for a plugin.
@@ -884,8 +932,12 @@ export class Matterbridge extends EventEmitter {
884
932
  try {
885
933
  await fs.access(configFile);
886
934
  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);
935
+ const config = JSON.parse(data);
936
+ this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, config);
937
+ /* The first time a plugin is added to the system, the config file is created with the plugin name and type "".*/
938
+ config.name = plugin.name;
939
+ config.type = plugin.type;
940
+ return config;
889
941
  }
890
942
  catch (err) {
891
943
  if (err instanceof Error) {
@@ -962,7 +1014,7 @@ export class Matterbridge extends EventEmitter {
962
1014
  return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`));
963
1015
  }
964
1016
  if (plugin.started) {
965
- this.log.error(`Plugin ${plg}${plugin.name}${er} already started`);
1017
+ this.log.warn(`Plugin ${plg}${plugin.name}${wr} already started`);
966
1018
  return Promise.resolve();
967
1019
  }
968
1020
  this.log.info(`Starting plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
@@ -994,7 +1046,7 @@ export class Matterbridge extends EventEmitter {
994
1046
  */
995
1047
  async configurePlugin(plugin) {
996
1048
  if (!plugin.loaded || !plugin.started || !plugin.platform) {
997
- this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or not started or not platform`);
1049
+ this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded (${plugin.loaded}) or not started (${plugin.started}) or not platform (${plugin.platform?.name})`);
998
1050
  return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not loaded or not started or not platform`));
999
1051
  }
1000
1052
  if (plugin.configured) {
@@ -1071,12 +1123,14 @@ export class Matterbridge extends EventEmitter {
1071
1123
  }
1072
1124
  else {
1073
1125
  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`));
1126
+ return;
1127
+ //return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} does not provide a default export`));
1075
1128
  }
1076
1129
  }
1077
1130
  catch (err) {
1078
1131
  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}`));
1132
+ return;
1133
+ //return Promise.reject(new Error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`));
1080
1134
  }
1081
1135
  }
1082
1136
  /**
@@ -1088,7 +1142,7 @@ export class Matterbridge extends EventEmitter {
1088
1142
  * @private
1089
1143
  * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1090
1144
  */
1091
- async startMatterBridge() {
1145
+ async startMatterbridge() {
1092
1146
  if (!this.storageManager) {
1093
1147
  this.log.error('No storage manager initialized');
1094
1148
  await this.cleanup('No storage manager initialized');
@@ -1129,7 +1183,7 @@ export class Matterbridge extends EventEmitter {
1129
1183
  this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1130
1184
  }
1131
1185
  else {
1132
- longDiscriminator = this.mattercontrollerContext.get('longDiscriminator', 3840);
1186
+ longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1133
1187
  if (longDiscriminator > 4095)
1134
1188
  throw new Error('Discriminator value must be less than 4096');
1135
1189
  setupPin = this.mattercontrollerContext.get('pin', 20202021);
@@ -1238,30 +1292,62 @@ export class Matterbridge extends EventEmitter {
1238
1292
  if (this.bridgeMode === 'bridge') {
1239
1293
  // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
1240
1294
  // 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');
1295
+ this.log.debug('***Starting startMatterbridge interval for Matterbridge');
1296
+ let failCount = 0;
1297
+ const startInterval = setInterval(async () => {
1298
+ for (const plugin of this.registeredPlugins) {
1299
+ if (!plugin.enabled)
1300
+ continue;
1301
+ if (!plugin.loaded) {
1302
+ this.log.info(`***Waiting (failSafeCount=${failCount}/30) in startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded}...`);
1303
+ failCount++;
1304
+ if (failCount > 30) {
1305
+ this.log.error(`***Failed to load plugin ${plg}${plugin.name}${er}`);
1306
+ plugin.error = true;
1307
+ }
1308
+ else {
1309
+ return;
1310
+ }
1311
+ }
1312
+ }
1313
+ clearInterval(startInterval);
1314
+ this.log.debug('***Cleared startMatterbridge interval for Matterbridge');
1315
+ this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1316
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
1317
+ if (!this.matterbridgeContext) {
1318
+ this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
1319
+ return;
1320
+ }
1321
+ if (!this.nodeContext) {
1322
+ this.log.error(`Node storage context undefined for ${plg}Matterbridge${er}`);
1323
+ return;
1324
+ }
1325
+ await this.matterbridgeContext.set('softwareVersion', 1);
1326
+ await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
1327
+ this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1328
+ this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1329
+ this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1330
+ this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext);
1331
+ this.log.debug('Adding matterbridge aggregator to commissioning server');
1332
+ this.commissioningServer.addDevice(this.matterAggregator);
1333
+ this.log.debug('Adding matterbridge commissioning server to matter server');
1334
+ await this.matterServer?.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1335
+ this.log.debug(`Setting reachability to true for ${plg}Matterbridge${db}`);
1336
+ this.commissioningServer.setReachability(true);
1337
+ this.log.debug('Starting matter server...');
1338
+ await this.startMatterServer();
1339
+ this.log.info('Matter server started');
1340
+ await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1341
+ /*
1342
+ setInterval(() => {
1343
+ this.matterAggregator?.getBridgedDevices().forEach((device) => {
1344
+ this.log.info(`Bridged device: ${dev}${device.name}${nf}`);
1345
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(true);
1346
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: true });
1347
+ });
1348
+ }, 30 * 1000);
1349
+ */
1350
+ }, 1000);
1265
1351
  }
1266
1352
  if (this.bridgeMode === 'childbridge') {
1267
1353
  // Plugins are loaded and started by loadPlugin on startup
@@ -1272,45 +1358,50 @@ export class Matterbridge extends EventEmitter {
1272
1358
  if (!plugin.enabled)
1273
1359
  return;
1274
1360
  // 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}...`);
1361
+ this.log.debug(`*Starting startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1362
+ let failCount = 0;
1277
1363
  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}...`);
1364
+ if (!plugin.loaded || !plugin.started /* || !plugin.configured*/) {
1365
+ this.log.info(`***Waiting (failSafeCount=${failCount}/30) in startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1366
+ failCount++;
1367
+ if (failCount > 30) {
1368
+ this.log.error(`***Failed to load plugin ${plg}${plugin.name}${er}`);
1369
+ plugin.error = true;
1370
+ clearInterval(startInterval);
1371
+ }
1285
1372
  return;
1286
1373
  }
1287
1374
  if (plugin.type === 'AccessoryPlatform') {
1288
- this.registeredDevices
1289
- .filter((registeredDevice) => registeredDevice.plugin === plugin.name)
1290
- .forEach((registeredDevice) => {
1375
+ for (const registeredDevice of this.registeredDevices) {
1376
+ if (registeredDevice.plugin !== plugin.name)
1377
+ continue;
1291
1378
  if (!plugin.storageContext)
1292
- plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device);
1379
+ plugin.storageContext = await this.importCommissioningServerContext(plugin.name, registeredDevice.device);
1380
+ await plugin.storageContext.set('softwareVersion', 1);
1381
+ await plugin.storageContext.set('softwareVersionString', this.matterbridgeVersion);
1293
1382
  if (!plugin.commissioningServer)
1294
- plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
1383
+ plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1295
1384
  this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
1296
1385
  plugin.commissioningServer.addDevice(registeredDevice.device);
1297
- });
1386
+ }
1298
1387
  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 });
1388
+ await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1300
1389
  }
1301
1390
  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);
1391
+ // eslint-disable-next-line max-len
1392
+ plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Dynamic Platform');
1393
+ await plugin.storageContext.set('softwareVersion', 1);
1394
+ await plugin.storageContext.set('softwareVersionString', this.matterbridgeVersion);
1395
+ plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1306
1396
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
1307
- plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
1397
+ plugin.aggregator = await this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
1308
1398
  this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
1309
1399
  plugin.commissioningServer.addDevice(plugin.aggregator);
1310
1400
  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 });
1401
+ await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1312
1402
  }
1313
1403
  clearInterval(startInterval);
1404
+ this.log.debug(`*Cleared startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
1314
1405
  }, 1000);
1315
1406
  });
1316
1407
  // Start the interval to check if all plugins are loaded and started and so start the matter server
@@ -1321,10 +1412,12 @@ export class Matterbridge extends EventEmitter {
1321
1412
  this.registeredPlugins.forEach((plugin) => {
1322
1413
  if (!plugin.enabled)
1323
1414
  return;
1415
+ if (plugin.error)
1416
+ return;
1324
1417
  if (plugin.enabled && (!plugin.loaded || !plugin.started))
1325
1418
  allStarted = false;
1326
1419
  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}) ...`);
1420
+ this.log.info(`***Waiting in start matter server interval for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
1328
1421
  });
1329
1422
  if (!allStarted)
1330
1423
  return;
@@ -1389,14 +1482,14 @@ export class Matterbridge extends EventEmitter {
1389
1482
  * @returns The commissioning server context.
1390
1483
  * @throws Error if the BasicInformationCluster is not found.
1391
1484
  */
1392
- importCommissioningServerContext(pluginName, device) {
1485
+ async importCommissioningServerContext(pluginName, device) {
1393
1486
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
1394
1487
  const basic = device.getClusterServer(BasicInformationCluster);
1395
1488
  if (!basic) {
1396
1489
  throw new Error('importCommissioningServerContext error: cannot find the BasicInformationCluster');
1397
1490
  }
1398
1491
  //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());
1492
+ 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
1493
  }
1401
1494
  /**
1402
1495
  * Creates a commissioning server storage context.
@@ -1416,7 +1509,7 @@ export class Matterbridge extends EventEmitter {
1416
1509
  * @param hardwareVersionString - The hardware version string of the device (optional).
1417
1510
  * @returns The storage context for the commissioning server.
1418
1511
  */
1419
- createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) {
1512
+ async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) {
1420
1513
  if (!this.storageManager) {
1421
1514
  this.log.error('No storage manager initialized');
1422
1515
  process.exit(1);
@@ -1424,20 +1517,23 @@ export class Matterbridge extends EventEmitter {
1424
1517
  this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
1425
1518
  const random = 'CS' + CryptoNode.getRandomData(8).toHex();
1426
1519
  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');
1520
+ await storageContext.set('deviceName', deviceName);
1521
+ await storageContext.set('deviceType', deviceType);
1522
+ await storageContext.set('vendorId', vendorId);
1523
+ await storageContext.set('vendorName', vendorName.slice(0, 32));
1524
+ await storageContext.set('productId', productId);
1525
+ await storageContext.set('productName', productName.slice(0, 32));
1526
+ await storageContext.set('nodeLabel', productName.slice(0, 32));
1527
+ await storageContext.set('productLabel', productName.slice(0, 32));
1528
+ await storageContext.set('serialNumber', await storageContext.get('serialNumber', random));
1529
+ await storageContext.set('uniqueId', await storageContext.get('uniqueId', random));
1530
+ await storageContext.set('softwareVersion', softwareVersion ?? 1);
1531
+ await storageContext.set('softwareVersionString', softwareVersionString ?? '1.0.0');
1532
+ await storageContext.set('hardwareVersion', hardwareVersion ?? 1);
1533
+ await storageContext.set('hardwareVersionString', hardwareVersionString ?? '1.0.0');
1534
+ this.log.debug(`**Created commissioning server storage context for ${plg}${pluginName}${db}`);
1535
+ this.log.debug(`**- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1536
+ this.log.debug(`**- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1441
1537
  return storageContext;
1442
1538
  }
1443
1539
  /**
@@ -1502,20 +1598,20 @@ export class Matterbridge extends EventEmitter {
1502
1598
  * @param {string} pluginName - The name of the commissioning server.
1503
1599
  * @returns {CommissioningServer} The created commissioning server.
1504
1600
  */
1505
- createCommisioningServer(context, pluginName) {
1601
+ async createCommisioningServer(context, pluginName) {
1506
1602
  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');
1603
+ const deviceName = await context.get('deviceName');
1604
+ const deviceType = await context.get('deviceType');
1605
+ const vendorId = await context.get('vendorId');
1606
+ const vendorName = await context.get('vendorName'); // Home app = Manufacturer
1607
+ const productId = await context.get('productId');
1608
+ const productName = await context.get('productName'); // Home app = Model
1609
+ const serialNumber = await context.get('serialNumber');
1610
+ const uniqueId = await context.get('uniqueId');
1611
+ const softwareVersion = await context.get('softwareVersion', 1);
1612
+ const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
1613
+ const hardwareVersion = await context.get('hardwareVersion', 1);
1614
+ const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
1519
1615
  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
1616
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with uniqueId ${uniqueId} serialNumber ${serialNumber}`);
1521
1617
  this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
@@ -1645,10 +1741,10 @@ export class Matterbridge extends EventEmitter {
1645
1741
  * @param {StorageContext} context - The storage context.
1646
1742
  * @returns {Aggregator} - The created Matter Aggregator.
1647
1743
  */
1648
- createMatterAggregator(context) {
1744
+ async createMatterAggregator(context) {
1649
1745
  const random = 'AG' + CryptoNode.getRandomData(8).toHex();
1650
- context.set('aggregatorSerialNumber', context.get('aggregatorSerialNumber', random));
1651
- context.set('aggregatorUniqueId', context.get('aggregatorUniqueId', random));
1746
+ await context.set('aggregatorSerialNumber', await context.get('aggregatorSerialNumber', random));
1747
+ await context.set('aggregatorUniqueId', await context.get('aggregatorUniqueId', random));
1652
1748
  const matterAggregator = new Aggregator();
1653
1749
  matterAggregator.addClusterServer(ClusterServer(BasicInformationCluster, {
1654
1750
  dataModelRevision: 1,
@@ -1659,8 +1755,8 @@ export class Matterbridge extends EventEmitter {
1659
1755
  productName: 'Matterbridge aggregator',
1660
1756
  productLabel: 'Matterbridge aggregator',
1661
1757
  nodeLabel: 'Matterbridge aggregator',
1662
- serialNumber: context.get('aggregatorSerialNumber'),
1663
- uniqueId: context.get('aggregatorUniqueId'),
1758
+ serialNumber: await context.get('aggregatorSerialNumber'),
1759
+ uniqueId: await context.get('aggregatorUniqueId'),
1664
1760
  softwareVersion: 1,
1665
1761
  softwareVersionString: 'v.1.0',
1666
1762
  hardwareVersion: 1,
@@ -1995,7 +2091,7 @@ export class Matterbridge extends EventEmitter {
1995
2091
  data.push({
1996
2092
  pluginName: registeredDevice.plugin,
1997
2093
  type: registeredDevice.device.name + ' (0x' + registeredDevice.device.deviceType.toString(16).padStart(4, '0') + ')',
1998
- endpoint: registeredDevice.device.id,
2094
+ endpoint: registeredDevice.device.number,
1999
2095
  name,
2000
2096
  serial,
2001
2097
  uniqueId,
@@ -2015,7 +2111,7 @@ export class Matterbridge extends EventEmitter {
2015
2111
  }
2016
2112
  const data = [];
2017
2113
  this.registeredDevices.forEach((registeredDevice) => {
2018
- if (registeredDevice.plugin === selectedPluginName && registeredDevice.device.id === selectedDeviceEndpoint) {
2114
+ if (registeredDevice.plugin === selectedPluginName && registeredDevice.device.number === selectedDeviceEndpoint) {
2019
2115
  const clusterServers = registeredDevice.device.getAllClusterServers();
2020
2116
  clusterServers.forEach((clusterServer) => {
2021
2117
  Object.entries(clusterServer.attributes).forEach(([key, value]) => {
@@ -2151,21 +2247,50 @@ export class Matterbridge extends EventEmitter {
2151
2247
  await this.nodeContext?.set('plugins', plugins);
2152
2248
  this.log.info(`Enabled plugin ${plg}${param}${nf}`);
2153
2249
  }
2250
+ if (this.bridgeMode === 'bridge') {
2251
+ const pluginToEnable = this.findPlugin(param);
2252
+ if (pluginToEnable) {
2253
+ pluginToEnable.enabled = true;
2254
+ pluginToEnable.platform = await this.loadPlugin(pluginToEnable);
2255
+ if (pluginToEnable.platform) {
2256
+ await this.startPlugin(pluginToEnable, 'The plugin has been enabled', true);
2257
+ pluginToEnable.enabled = false;
2258
+ }
2259
+ else {
2260
+ pluginToEnable.enabled = false;
2261
+ pluginToEnable.error = true;
2262
+ this.log.error(`Error enabling plugin ${plg}${param}${er}`);
2263
+ }
2264
+ }
2265
+ }
2154
2266
  }
2155
2267
  // Handle the command disableplugin from Home
2156
2268
  if (command === 'disableplugin') {
2157
- const plugins = await this.nodeContext?.get('plugins');
2158
- if (!plugins)
2159
- return;
2160
- const plugin = plugins.find((plugin) => plugin.name === param);
2161
- if (plugin) {
2162
- plugin.enabled = false;
2163
- plugin.loaded = undefined;
2164
- plugin.started = undefined;
2165
- plugin.configured = undefined;
2166
- plugin.connected = undefined;
2167
- await this.nodeContext?.set('plugins', plugins);
2168
- this.log.info(`Disabled plugin ${plg}${param}${nf}`);
2269
+ const pluginToDisable = this.findPlugin(param);
2270
+ if (pluginToDisable) {
2271
+ if (pluginToDisable.platform) {
2272
+ await pluginToDisable.platform.onShutdown('The plugin has been removed.');
2273
+ await this.savePluginConfig(pluginToDisable);
2274
+ }
2275
+ pluginToDisable.enabled = false;
2276
+ pluginToDisable.loaded = undefined;
2277
+ pluginToDisable.started = undefined;
2278
+ pluginToDisable.configured = undefined;
2279
+ pluginToDisable.connected = undefined;
2280
+ pluginToDisable.platform = undefined;
2281
+ const plugins = await this.nodeContext?.get('plugins');
2282
+ if (!plugins)
2283
+ return;
2284
+ const plugin = plugins.find((plugin) => plugin.name === param);
2285
+ if (plugin) {
2286
+ plugin.enabled = false;
2287
+ plugin.loaded = undefined;
2288
+ plugin.started = undefined;
2289
+ plugin.configured = undefined;
2290
+ plugin.connected = undefined;
2291
+ await this.nodeContext?.set('plugins', plugins);
2292
+ this.log.info(`Disabled plugin ${plg}${param}${nf}`);
2293
+ }
2169
2294
  }
2170
2295
  }
2171
2296
  res.json({ message: 'Command received' });
@@ -2199,6 +2324,8 @@ export class Matterbridge extends EventEmitter {
2199
2324
  attributes += `Cover position: ${clusterServer.attributes.currentPositionLiftPercent100ths.getLocal() / 100}% `;
2200
2325
  if (clusterServer.name === 'DoorLock')
2201
2326
  attributes += `State: ${clusterServer.attributes.lockState.getLocal() === 1 ? 'Locked' : 'Not locked'} `;
2327
+ if (clusterServer.name === 'Thermostat')
2328
+ attributes += `Temperature: ${clusterServer.attributes.localTemperature.getLocal() / 100}°C `;
2202
2329
  if (clusterServer.name === 'LevelControl')
2203
2330
  attributes += `Level: ${clusterServer.getCurrentLevelAttribute()}% `;
2204
2331
  if (clusterServer.name === 'ColorControl')