matterbridge 1.2.13 → 1.2.15

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.
@@ -34,7 +34,7 @@ import path from 'path';
34
34
  import WebSocket, { WebSocketServer } from 'ws';
35
35
  import { BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster } from './BridgedDeviceBasicInformationCluster.js';
36
36
  import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
37
- import { BasicInformationCluster, BooleanStateCluster, ClusterServer, FixedLabelCluster, GeneralCommissioning, GeneralDiagnostics, GeneralDiagnosticsCluster, PowerSourceCluster, ThreadNetworkDiagnosticsCluster, getClusterNameById, } from '@project-chip/matter-node.js/cluster';
37
+ import { BasicInformationCluster, ClusterServer, FixedLabelCluster, GeneralCommissioning, GeneralDiagnostics, GeneralDiagnosticsCluster, PowerSourceCluster, ThreadNetworkDiagnosticsCluster, getClusterNameById, } from '@project-chip/matter-node.js/cluster';
38
38
  import { DeviceTypeId, VendorId } from '@project-chip/matter-node.js/datatype';
39
39
  import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter-node.js/device';
40
40
  import { Format, Level, Logger } from '@project-chip/matter-node.js/log';
@@ -42,6 +42,7 @@ import { ManualPairingCodeCodec, QrCodeSchema } from '@project-chip/matter-node.
42
42
  import { StorageBackendDisk, StorageBackendJsonFile, StorageManager } from '@project-chip/matter-node.js/storage';
43
43
  import { requireMinNodeVersion, getParameter, getIntParameter, hasParameter } from '@project-chip/matter-node.js/util';
44
44
  import { CryptoNode } from '@project-chip/matter-node.js/crypto';
45
+ import { somfytahoma_config, somfytahoma_schema, zigbee2mqtt_config, zigbee2mqtt_schema } from './defaultConfigSchema.js';
45
46
  const plg = '\u001B[38;5;33m';
46
47
  const dev = '\u001B[38;5;79m';
47
48
  const typ = '\u001B[38;5;207m';
@@ -50,6 +51,7 @@ const typ = '\u001B[38;5;207m';
50
51
  */
51
52
  export class Matterbridge extends EventEmitter {
52
53
  systemInformation = {
54
+ macAddress: '',
53
55
  ipv4Address: '',
54
56
  ipv6Address: '',
55
57
  nodeVersion: '',
@@ -157,7 +159,7 @@ export class Matterbridge extends EventEmitter {
157
159
  addedDevices: 0,
158
160
  };
159
161
  this.registeredPlugins.push(plugin);
160
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
162
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
161
163
  // Log system info and create .matterbridge directory
162
164
  await this.logNodeAndSystemInfo();
163
165
  this.matterbridgeDirectory = dataPath;
@@ -179,7 +181,7 @@ export class Matterbridge extends EventEmitter {
179
181
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
180
182
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
181
183
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
182
- this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext);
184
+ this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
183
185
  this.log.debug('Adding matterbridge aggregator to commissioning server');
184
186
  this.commissioningServer.addDevice(this.matterAggregator);
185
187
  this.log.debug('Adding matterbridge commissioning server to matter server');
@@ -499,7 +501,7 @@ export class Matterbridge extends EventEmitter {
499
501
  const plugin = { path: packageJsonPath, type: '', name: packageJson.name, version: packageJson.version, description: packageJson.description, author: packageJson.author, enabled: true };
500
502
  if (await this.loadPlugin(plugin)) {
501
503
  this.registeredPlugins.push(plugin);
502
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
504
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
503
505
  this.log.info(`Plugin ${plg}${packageJsonPath}${nf} type ${plugin.type} added to matterbridge`);
504
506
  }
505
507
  else {
@@ -513,7 +515,7 @@ export class Matterbridge extends EventEmitter {
513
515
  else if (mode === 'remove') {
514
516
  if (this.registeredPlugins.find((registeredPlugin) => registeredPlugin.name === packageJson.name)) {
515
517
  this.registeredPlugins.splice(this.registeredPlugins.findIndex((registeredPlugin) => registeredPlugin.name === packageJson.name), 1);
516
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
518
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
517
519
  this.log.info(`Plugin ${plg}${packageJsonPath}${nf} removed from matterbridge`);
518
520
  }
519
521
  else {
@@ -528,7 +530,7 @@ export class Matterbridge extends EventEmitter {
528
530
  plugin.started = undefined;
529
531
  plugin.configured = undefined;
530
532
  plugin.connected = undefined;
531
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
533
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
532
534
  this.log.info(`Plugin ${plg}${packageJsonPath}${nf} enabled`);
533
535
  }
534
536
  else {
@@ -543,7 +545,7 @@ export class Matterbridge extends EventEmitter {
543
545
  plugin.started = undefined;
544
546
  plugin.configured = undefined;
545
547
  plugin.connected = undefined;
546
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
548
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
547
549
  this.log.info(`Plugin ${plg}${packageJsonPath}${nf} disabled`);
548
550
  }
549
551
  else {
@@ -565,7 +567,7 @@ export class Matterbridge extends EventEmitter {
565
567
  if (!context)
566
568
  this.log.error(`Plugin ${plg}${plugin.name}${er} context not found`);
567
569
  await context?.clearAll();
568
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
570
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
569
571
  this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
570
572
  }
571
573
  else {
@@ -992,7 +994,8 @@ export class Matterbridge extends EventEmitter {
992
994
  }
993
995
  }
994
996
  catch (error) {
995
- this.log.error('Storage initialize() error!');
997
+ this.log.error('Storage initialize() error! The file .matterbridge/matterbridge.json may be corrupted.');
998
+ this.log.error('Please delete it and rename matterbridge.backup.json to matterbridge.json and try to restart Matterbridge.');
996
999
  await this.cleanup('Storage initialize() error!');
997
1000
  }
998
1001
  }
@@ -1035,93 +1038,176 @@ export class Matterbridge extends EventEmitter {
1035
1038
  this.mattercontrollerContext = undefined;
1036
1039
  }
1037
1040
  async testStartMatterBridge() {
1038
- // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
1039
- // Plugins are started and configured by callback when Matterbridge is commissioned
1041
+ /*
1040
1042
  if (!this.storageManager) {
1041
- this.log.error('No storage manager initialized');
1042
- await this.cleanup('No storage manager initialized');
1043
- return;
1043
+ this.log.error('No storage manager initialized');
1044
+ await this.cleanup('No storage manager initialized');
1045
+ return;
1044
1046
  }
1045
1047
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1046
1048
  this.matterServer = this.createMatterServer(this.storageManager);
1049
+
1047
1050
  this.log.debug('***Starting startMatterbridge interval for Matterbridge');
1048
1051
  let failCount = 0;
1049
1052
  const startInterval = setInterval(async () => {
1050
- for (const plugin of this.registeredPlugins) {
1051
- if (!plugin.enabled)
1052
- continue;
1053
- if (!plugin.loaded) {
1054
- this.log.info(`***Waiting (failSafeCount=${failCount}/30) in startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded}...`);
1055
- failCount++;
1056
- if (failCount > 30) {
1057
- this.log.error(`***Failed to load plugin ${plg}${plugin.name}${er}`);
1058
- plugin.error = true;
1053
+ for (const plugin of this.registeredPlugins) {
1054
+ if (!plugin.enabled) continue;
1055
+ if (!plugin.loaded) {
1056
+ this.log.info(`***Waiting (failSafeCount=${failCount}/30) in startMatterbridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded}...`);
1057
+ failCount++;
1058
+ if (failCount > 30) {
1059
+ this.log.error(`***Failed to load plugin ${plg}${plugin.name}${er}`);
1060
+ plugin.error = true;
1061
+ } else {
1062
+ return;
1063
+ }
1064
+ }
1065
+ }
1066
+ clearInterval(startInterval);
1067
+ this.log.debug('***Cleared startMatterbridge interval for Matterbridge');
1068
+
1069
+ this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1070
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
1071
+ if (!this.matterbridgeContext) {
1072
+ this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
1073
+ return;
1074
+ }
1075
+ if (!this.nodeContext) {
1076
+ this.log.error(`Node storage context undefined for ${plg}Matterbridge${er}`);
1077
+ return;
1078
+ }
1079
+ await this.matterbridgeContext.set('softwareVersion', 1);
1080
+ await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
1081
+ this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1082
+ this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1083
+
1084
+ this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1085
+ this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
1086
+ this.log.debug('Adding matterbridge aggregator to commissioning server');
1087
+ this.commissioningServer.addDevice(this.matterAggregator);
1088
+
1089
+ const device = new MatterbridgeDevice(DeviceTypes.CONTACT_SENSOR);
1090
+ device.createDefaultIdentifyClusterServer();
1091
+ device.createDefaultBridgedDeviceBasicInformationClusterServer('Boolean test', '0x89930475', 0x8000, 'Matterbridge', 'Boolean');
1092
+ device.createDefaultBooleanStateClusterServer(true);
1093
+ device.createDefaultPowerSourceReplaceableBatteryClusterServer(75);
1094
+ device.createDefaultPowerSourceConfigurationClusterServer(1);
1095
+
1096
+ this.log.debug('Adding matterbridge commissioning server to matter server');
1097
+ await this.matterServer?.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1098
+ this.log.debug(`Setting reachability to true for ${plg}Matterbridge${db}`);
1099
+ this.commissioningServer.setReachability(true);
1100
+ this.log.debug('Starting matter server...');
1101
+ await this.startMatterServer();
1102
+ this.log.info('Matter server started');
1103
+ await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1104
+
1105
+ setTimeout(() => {
1106
+ this.matterAggregator?.addBridgedDevice(device);
1107
+ this.log.info('Added device to aggregator');
1108
+ }, 30 * 1000);
1109
+
1110
+ setInterval(() => {
1111
+ const cluster = device.getClusterServer(BooleanStateCluster);
1112
+ if (!cluster) return;
1113
+ const contact = cluster.getStateValueAttribute();
1114
+ cluster.setStateValueAttribute(!contact);
1115
+ if (cluster.isEventSupportedByName('stateChange')) cluster.triggerStateChangeEvent!({ stateValue: !contact });
1116
+ this.log.info('Set attribute and event for BooleanStateCluster to', !contact);
1117
+ }, 60 * 1000);
1118
+ }, 1000);
1119
+ */
1120
+ }
1121
+ /**
1122
+ * Loads the schema for a plugin.
1123
+ * If the schema file exists, it reads the file and returns the parsed JSON data.
1124
+ * If the schema file does not exist, it creates a new file with default configuration and returns it.
1125
+ * If any error occurs during file access or creation, it logs an error and return an empty schema.
1126
+ *
1127
+ * @param plugin - The plugin for which to load the schema.
1128
+ * @returns A promise that resolves to the loaded or created schema.
1129
+ */
1130
+ async loadPluginSchema(plugin) {
1131
+ const schemaFile = path.join(this.matterbridgeDirectory, `${plugin.name}.schema.json`);
1132
+ try {
1133
+ await fs.access(schemaFile);
1134
+ const data = await fs.readFile(schemaFile, 'utf8');
1135
+ const schema = JSON.parse(data);
1136
+ schema.title = plugin.description;
1137
+ schema.description = plugin.name + ' v. ' + plugin.version + ' by ' + plugin.author;
1138
+ this.log.debug(`Schema file found: ${schemaFile}.\nSchema:${rs}\n`, schema);
1139
+ return schema;
1140
+ }
1141
+ catch (err) {
1142
+ if (err instanceof Error) {
1143
+ const nodeErr = err;
1144
+ if (nodeErr.code === 'ENOENT') {
1145
+ let schema;
1146
+ if (plugin.name === 'matterbridge-zigbee2mqtt')
1147
+ schema = zigbee2mqtt_schema;
1148
+ else if (plugin.name === 'matterbridge-somfy-tahoma')
1149
+ schema = somfytahoma_schema;
1150
+ else
1151
+ schema = {
1152
+ title: plugin.description,
1153
+ description: plugin.name + ' v. ' + plugin.version + ' by ' + plugin.author,
1154
+ type: 'object',
1155
+ properties: {
1156
+ name: {
1157
+ description: 'Plugin name',
1158
+ type: 'string',
1159
+ readOnly: true,
1160
+ },
1161
+ type: {
1162
+ description: 'Plugin type',
1163
+ type: 'string',
1164
+ readOnly: true,
1165
+ },
1166
+ unregisterOnShutdown: {
1167
+ description: 'Unregister all devices on shutdown (development only)',
1168
+ type: 'boolean',
1169
+ },
1170
+ },
1171
+ };
1172
+ try {
1173
+ await this.writeFile(schemaFile, JSON.stringify(schema, null, 2));
1174
+ this.log.debug(`Created schema file: ${schemaFile}.\nSchema:${rs}\n`, schema);
1175
+ return schema;
1059
1176
  }
1060
- else {
1061
- return;
1177
+ catch (err) {
1178
+ this.log.error(`Error creating schema file ${schemaFile}: ${err}`);
1179
+ return schema;
1062
1180
  }
1063
1181
  }
1182
+ else {
1183
+ this.log.error(`Error accessing schema file ${schemaFile}: ${err}`);
1184
+ return {};
1185
+ }
1064
1186
  }
1065
- clearInterval(startInterval);
1066
- this.log.debug('***Cleared startMatterbridge interval for Matterbridge');
1067
- this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1068
- this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
1069
- if (!this.matterbridgeContext) {
1070
- this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
1071
- return;
1072
- }
1073
- if (!this.nodeContext) {
1074
- this.log.error(`Node storage context undefined for ${plg}Matterbridge${er}`);
1075
- return;
1076
- }
1077
- await this.matterbridgeContext.set('softwareVersion', 1);
1078
- await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
1079
- this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1080
- this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1081
- this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1082
- this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext);
1083
- this.log.debug('Adding matterbridge aggregator to commissioning server');
1084
- this.commissioningServer.addDevice(this.matterAggregator);
1085
- const device = new MatterbridgeDevice(DeviceTypes.CONTACT_SENSOR);
1086
- device.createDefaultIdentifyClusterServer();
1087
- //device.createDefaultBasicInformationClusterServer('Boolean test', '0x89930475', 0x8000, 'Matterbridge', 77, 'Boolean');
1088
- device.createDefaultBridgedDeviceBasicInformationClusterServer('Boolean test', '0x89930475', 0x8000, 'Matterbridge', 'Boolean');
1089
- device.createDefaultBooleanStateClusterServer(true);
1090
- device.createDefaultPowerSourceReplaceableBatteryClusterServer(75);
1091
- device.createDefaultPowerSourceConfigurationClusterServer(1);
1092
- //this.commissioningServer.addDevice(device);
1093
- //this.matterAggregator.addBridgedDevice(device);
1094
- this.log.debug('Adding matterbridge commissioning server to matter server');
1095
- await this.matterServer?.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1096
- this.log.debug(`Setting reachability to true for ${plg}Matterbridge${db}`);
1097
- this.commissioningServer.setReachability(true);
1098
- this.log.debug('Starting matter server...');
1099
- await this.startMatterServer();
1100
- this.log.info('Matter server started');
1101
- await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1102
- //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)
1103
- //2024-03-31 21:54:12.066 DEBUG InteractionServer Subscribe to attributes:*/*/*, events:!*/*/*
1104
- /*
1105
- 2024-03-31 21:52:22.669 DEBUG SubscriptionHandler Sending subscription update message for ID 2529153003 with 1 attributes and 1 events
1106
- 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
1107
- 2024-03-31 21:52:22.669 DEBUG SubscriptionHandler Sending subscription changes for ID 2529153003: MA-contactsensor(0x3b)/BooleanState(0x45)/stateValue(0x0)=true (1805472651)
1108
- 2024-03-31 21:52:22.670 DEBUG InteractionMessenger Sending DataReport chunk with 1 attributes and 1 events: 85 bytes
1109
- */
1110
- setTimeout(() => {
1111
- this.matterAggregator?.addBridgedDevice(device);
1112
- this.log.info('Added device to aggregator');
1113
- }, 30 * 1000);
1114
- setInterval(() => {
1115
- const cluster = device.getClusterServer(BooleanStateCluster);
1116
- if (!cluster)
1117
- return;
1118
- const contact = cluster.getStateValueAttribute();
1119
- cluster.setStateValueAttribute(!contact);
1120
- if (cluster.isEventSupportedByName('stateChange'))
1121
- cluster.triggerStateChangeEvent({ stateValue: !contact });
1122
- this.log.info('Set attribute and event for BooleanStateCluster to', !contact);
1123
- }, 60 * 1000);
1124
- }, 1000);
1187
+ this.log.error(`Error loading schema file ${schemaFile}: ${err}`);
1188
+ return {};
1189
+ }
1190
+ }
1191
+ /**
1192
+ * Saves the plugin configuration to a JSON file.
1193
+ * @param plugin - The registered plugin.
1194
+ * @param config - The platform configuration.
1195
+ * @returns A promise that resolves when the configuration is saved successfully, or rejects with an error.
1196
+ */
1197
+ async savePluginConfigFromJson(plugin, config) {
1198
+ if (!config.name || !config.type || config.name !== plugin.name) {
1199
+ this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config`);
1200
+ return;
1201
+ }
1202
+ const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
1203
+ try {
1204
+ await this.writeFile(configFile, JSON.stringify(config, null, 2));
1205
+ this.log.debug(`Saved config file: ${configFile}.\nConfig:${rs}\n`, config);
1206
+ }
1207
+ catch (err) {
1208
+ this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: ${err}`);
1209
+ return;
1210
+ }
1125
1211
  }
1126
1212
  /**
1127
1213
  * Loads the configuration for a plugin.
@@ -1148,22 +1234,30 @@ export class Matterbridge extends EventEmitter {
1148
1234
  if (err instanceof Error) {
1149
1235
  const nodeErr = err;
1150
1236
  if (nodeErr.code === 'ENOENT') {
1237
+ let config;
1238
+ if (plugin.name === 'matterbridge-zigbee2mqtt')
1239
+ config = zigbee2mqtt_config;
1240
+ else if (plugin.name === 'matterbridge-somfy-tahoma')
1241
+ config = somfytahoma_config;
1242
+ else
1243
+ config = { name: plugin.name, type: plugin.type, unregisterOnShutdown: false };
1151
1244
  try {
1152
- await this.writeFile(configFile, JSON.stringify({ name: plugin.name, type: plugin.type }, null, 2));
1153
- this.log.debug(`Created config file: ${configFile}.\nConfig:${rs}\n`, { name: plugin.name, type: plugin.type });
1154
- return { name: plugin.name, type: plugin.type };
1245
+ await this.writeFile(configFile, JSON.stringify(config, null, 2));
1246
+ this.log.debug(`Created config file: ${configFile}.\nConfig:${rs}\n`, config);
1247
+ return config;
1155
1248
  }
1156
1249
  catch (err) {
1157
1250
  this.log.error(`Error creating config file ${configFile}: ${err}`);
1158
- return Promise.reject(err);
1251
+ return config;
1159
1252
  }
1160
1253
  }
1161
1254
  else {
1162
1255
  this.log.error(`Error accessing config file ${configFile}: ${err}`);
1163
- return Promise.reject(err);
1256
+ return {};
1164
1257
  }
1165
1258
  }
1166
- return Promise.reject(err);
1259
+ this.log.error(`Error loading config file ${configFile}: ${err}`);
1260
+ return {};
1167
1261
  }
1168
1262
  }
1169
1263
  /**
@@ -1328,11 +1422,11 @@ export class Matterbridge extends EventEmitter {
1328
1422
  plugin.loaded = true;
1329
1423
  plugin.registeredDevices = 0;
1330
1424
  plugin.addedDevices = 0;
1331
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1425
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
1332
1426
  this.getLatestVersion(plugin.name)
1333
1427
  .then(async (latestVersion) => {
1334
1428
  plugin.latestVersion = latestVersion;
1335
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1429
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
1336
1430
  if (plugin.version !== latestVersion)
1337
1431
  this.log.warn(`The plugin ${plg}${plugin.name}${wr} is out of date. Current version: ${plugin.version}, Latest version: ${latestVersion}`);
1338
1432
  else
@@ -1570,12 +1664,10 @@ export class Matterbridge extends EventEmitter {
1570
1664
  this.log.error(`Node storage context undefined for ${plg}Matterbridge${er}`);
1571
1665
  return;
1572
1666
  }
1573
- await this.matterbridgeContext.set('softwareVersion', 1);
1574
- await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
1575
1667
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1576
1668
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1577
1669
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1578
- this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext);
1670
+ this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
1579
1671
  this.log.debug('Adding matterbridge aggregator to commissioning server');
1580
1672
  this.commissioningServer.addDevice(this.matterAggregator);
1581
1673
  this.log.debug('Adding matterbridge commissioning server to matter server');
@@ -1622,8 +1714,7 @@ export class Matterbridge extends EventEmitter {
1622
1714
  continue;
1623
1715
  if (!plugin.storageContext)
1624
1716
  plugin.storageContext = await this.importCommissioningServerContext(plugin.name, registeredDevice.device);
1625
- await plugin.storageContext.set('softwareVersion', 1);
1626
- await plugin.storageContext.set('softwareVersionString', this.matterbridgeVersion);
1717
+ this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
1627
1718
  if (!plugin.commissioningServer)
1628
1719
  plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1629
1720
  this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
@@ -1635,13 +1726,12 @@ export class Matterbridge extends EventEmitter {
1635
1726
  await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1636
1727
  }
1637
1728
  if (plugin.type === 'DynamicPlatform') {
1638
- // eslint-disable-next-line max-len
1729
+ this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
1639
1730
  plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Dynamic Platform');
1640
- await plugin.storageContext.set('softwareVersion', 1);
1641
- await plugin.storageContext.set('softwareVersionString', this.matterbridgeVersion);
1731
+ this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
1642
1732
  plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1643
1733
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
1644
- plugin.aggregator = await this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
1734
+ plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name); // Generate serialNumber and uniqueId
1645
1735
  this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
1646
1736
  plugin.commissioningServer.addDevice(plugin.aggregator);
1647
1737
  this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
@@ -1727,10 +1817,35 @@ export class Matterbridge extends EventEmitter {
1727
1817
  this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
1728
1818
  const basic = device.getClusterServer(BasicInformationCluster);
1729
1819
  if (!basic) {
1730
- throw new Error('importCommissioningServerContext error: cannot find the BasicInformationCluster');
1820
+ this.log.error('importCommissioningServerContext error: cannot find the BasicInformationCluster');
1821
+ process.exit(1);
1822
+ }
1823
+ if (!this.storageManager) {
1824
+ this.log.error('importCommissioningServerContext error: no storage manager initialized');
1825
+ process.exit(1);
1731
1826
  }
1732
- //const random = 'CS' + CryptoNode.getRandomData(8).toHex();
1733
- 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());
1827
+ this.log.debug(`Importing commissioning server storage context for ${plg}${pluginName}${db}`);
1828
+ const storageContext = this.storageManager.createContext(pluginName);
1829
+ await storageContext.set('deviceName', basic.getNodeLabelAttribute());
1830
+ await storageContext.set('deviceType', DeviceTypeId(device.deviceType));
1831
+ await storageContext.set('vendorId', basic.getVendorIdAttribute());
1832
+ await storageContext.set('vendorName', basic.getVendorNameAttribute());
1833
+ await storageContext.set('productId', basic.getProductIdAttribute());
1834
+ await storageContext.set('productName', basic.getProductNameAttribute());
1835
+ await storageContext.set('nodeLabel', basic.getNodeLabelAttribute());
1836
+ await storageContext.set('productLabel', basic.getNodeLabelAttribute());
1837
+ await storageContext.set('serialNumber', basic.attributes.serialNumber?.getLocal());
1838
+ await storageContext.set('uniqueId', basic.attributes.uniqueId?.getLocal());
1839
+ await storageContext.set('softwareVersion', basic.getSoftwareVersionAttribute());
1840
+ await storageContext.set('softwareVersionString', basic.getSoftwareVersionStringAttribute());
1841
+ await storageContext.set('hardwareVersion', basic.getHardwareVersionAttribute());
1842
+ await storageContext.set('hardwareVersionString', basic.getHardwareVersionStringAttribute());
1843
+ this.log.debug(`Imported commissioning server storage context for ${plg}${pluginName}${db}`);
1844
+ this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
1845
+ this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
1846
+ this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1847
+ this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1848
+ return storageContext;
1734
1849
  }
1735
1850
  /**
1736
1851
  * Creates a commissioning server storage context.
@@ -1750,7 +1865,7 @@ export class Matterbridge extends EventEmitter {
1750
1865
  * @param hardwareVersionString - The hardware version string of the device (optional).
1751
1866
  * @returns The storage context for the commissioning server.
1752
1867
  */
1753
- async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) {
1868
+ async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
1754
1869
  if (!this.storageManager) {
1755
1870
  this.log.error('No storage manager initialized');
1756
1871
  process.exit(1);
@@ -1768,11 +1883,13 @@ export class Matterbridge extends EventEmitter {
1768
1883
  await storageContext.set('productLabel', productName.slice(0, 32));
1769
1884
  await storageContext.set('serialNumber', await storageContext.get('serialNumber', random));
1770
1885
  await storageContext.set('uniqueId', await storageContext.get('uniqueId', random));
1771
- await storageContext.set('softwareVersion', softwareVersion ?? 1);
1772
- await storageContext.set('softwareVersionString', softwareVersionString ?? '1.0.0');
1773
- await storageContext.set('hardwareVersion', hardwareVersion ?? 1);
1774
- await storageContext.set('hardwareVersionString', hardwareVersionString ?? '1.0.0');
1886
+ await storageContext.set('softwareVersion', this.matterbridgeVersion && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
1887
+ await storageContext.set('softwareVersionString', this.matterbridgeVersion ?? '1.0.0');
1888
+ await storageContext.set('hardwareVersion', this.systemInformation.osRelease && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
1889
+ await storageContext.set('hardwareVersionString', this.systemInformation.osRelease ?? '1.0.0');
1775
1890
  this.log.debug(`Created commissioning server storage context for ${plg}${pluginName}${db}`);
1891
+ this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
1892
+ this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
1776
1893
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1777
1894
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1778
1895
  return storageContext;
@@ -1805,7 +1922,7 @@ export class Matterbridge extends EventEmitter {
1805
1922
  plugin.paired = false;
1806
1923
  }
1807
1924
  }
1808
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1925
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
1809
1926
  }
1810
1927
  else {
1811
1928
  this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is already commissioned . Waiting for controllers to connect ...`);
@@ -1815,7 +1932,7 @@ export class Matterbridge extends EventEmitter {
1815
1932
  plugin.paired = true;
1816
1933
  }
1817
1934
  }
1818
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1935
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
1819
1936
  }
1820
1937
  }
1821
1938
  /**
@@ -2044,18 +2161,43 @@ export class Matterbridge extends EventEmitter {
2044
2161
  });
2045
2162
  const gdcCluster = commissioningServer.getRootClusterServer(GeneralDiagnosticsCluster);
2046
2163
  if (gdcCluster) {
2047
- gdcCluster.setNetworkInterfacesAttribute([
2048
- {
2049
- name: 'eth0',
2050
- isOperational: true,
2051
- offPremiseServicesReachableIPv4: null,
2052
- offPremiseServicesReachableIPv6: null,
2053
- hardwareAddress: Uint8Array.fromString('00000000'),
2054
- iPv4Addresses: [Uint8Array.fromString('0000')],
2055
- iPv6Addresses: [Uint8Array.fromString('0000000000000000')],
2056
- type: GeneralDiagnostics.InterfaceType.Ethernet,
2057
- },
2058
- ]);
2164
+ // We have like "30:f6:ef:69:2b:c5" in this.systemInformation.macAddress
2165
+ const macArray = this.systemInformation.macAddress.split(':').map((hex) => parseInt(hex, 16));
2166
+ let hardwareAddress = new Uint8Array(macArray);
2167
+ if (hardwareAddress.length === 6)
2168
+ hardwareAddress = Uint8Array.from([0, 0, ...hardwareAddress]);
2169
+ // We have like "192.168.1.189" in this.systemInformation.ipv4Address
2170
+ const ipv4Array = this.systemInformation.ipv4Address.split('.').map((num) => parseInt(num));
2171
+ const iPv4Address = new Uint8Array(ipv4Array);
2172
+ // We have like "fd78:cbf8:4939:746:d555:85a9:74f6:9c6" in this.systemInformation.ipv6Address
2173
+ const ipv6Groups = this.systemInformation.ipv6Address.split(':');
2174
+ const ipv6Array = [];
2175
+ for (const group of ipv6Groups) {
2176
+ const decimal = parseInt(group, 16);
2177
+ ipv6Array.push(decimal >> 8); // High byte
2178
+ ipv6Array.push(decimal & 0xff); // Low byte
2179
+ }
2180
+ const iPv6Address = new Uint8Array(ipv6Array);
2181
+ this.log.debug(`GeneralDiagnosticsCluster for ${plg}${pluginName}${db} hardwareAddress ${this.systemInformation.macAddress} => ${debugStringify(hardwareAddress)}`);
2182
+ this.log.debug(`GeneralDiagnosticsCluster for ${plg}${pluginName}${db} iPv4Address ${this.systemInformation.ipv4Address} => ${debugStringify(iPv4Address)}`);
2183
+ this.log.debug(`GeneralDiagnosticsCluster for ${plg}${pluginName}${db} iPv6Address ${this.systemInformation.ipv6Address} => ${debugStringify(iPv6Address)}`);
2184
+ try {
2185
+ gdcCluster.setNetworkInterfacesAttribute([
2186
+ {
2187
+ name: 'eth0',
2188
+ isOperational: true,
2189
+ offPremiseServicesReachableIPv4: null,
2190
+ offPremiseServicesReachableIPv6: null,
2191
+ hardwareAddress,
2192
+ iPv4Addresses: [iPv4Address],
2193
+ iPv6Addresses: [iPv6Address],
2194
+ type: GeneralDiagnostics.InterfaceType.Ethernet,
2195
+ },
2196
+ ]);
2197
+ }
2198
+ catch (error) {
2199
+ this.log.error(`GeneralDiagnosticsCluster.setNetworkInterfacesAttribute for ${plg}${pluginName}${er} error:`, error);
2200
+ }
2059
2201
  }
2060
2202
  else
2061
2203
  this.log.warn(`*GeneralDiagnosticsCluster not found for ${plg}${pluginName}${wr}`);
@@ -2078,10 +2220,13 @@ export class Matterbridge extends EventEmitter {
2078
2220
  * @param {StorageContext} context - The storage context.
2079
2221
  * @returns {Aggregator} - The created Matter Aggregator.
2080
2222
  */
2081
- async createMatterAggregator(context) {
2223
+ async createMatterAggregator(context, pluginName) {
2082
2224
  const random = 'AG' + CryptoNode.getRandomData(8).toHex();
2083
2225
  await context.set('aggregatorSerialNumber', await context.get('aggregatorSerialNumber', random));
2084
2226
  await context.set('aggregatorUniqueId', await context.get('aggregatorUniqueId', random));
2227
+ this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with uniqueId ${await context.get('aggregatorUniqueId')} serialNumber ${await context.get('aggregatorSerialNumber')}`);
2228
+ this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with softwareVersion ${await context.get('softwareVersion', 1)} softwareVersionString ${await context.get('softwareVersionString', '1.0.0')}`);
2229
+ this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with hardwareVersion ${await context.get('hardwareVersion', 1)} hardwareVersionString ${await context.get('hardwareVersionString', '1.0.0')}`);
2085
2230
  const matterAggregator = new Aggregator();
2086
2231
  matterAggregator.addClusterServer(ClusterServer(BasicInformationCluster, {
2087
2232
  dataModelRevision: 1,
@@ -2094,10 +2239,10 @@ export class Matterbridge extends EventEmitter {
2094
2239
  nodeLabel: 'Matterbridge aggregator',
2095
2240
  serialNumber: await context.get('aggregatorSerialNumber'),
2096
2241
  uniqueId: await context.get('aggregatorUniqueId'),
2097
- softwareVersion: 1,
2098
- softwareVersionString: 'v.1.0',
2099
- hardwareVersion: 1,
2100
- hardwareVersionString: 'v.1.0',
2242
+ softwareVersion: await context.get('softwareVersion', 1),
2243
+ softwareVersionString: await context.get('softwareVersionString', '1.0.0'),
2244
+ hardwareVersion: await context.get('hardwareVersion', 1),
2245
+ hardwareVersionString: await context.get('hardwareVersionString', '1.0.0'),
2101
2246
  reachable: true,
2102
2247
  capabilityMinima: { caseSessionsPerFabric: 3, subscriptionsPerFabric: 3 },
2103
2248
  }, {}, {
@@ -2172,9 +2317,11 @@ export class Matterbridge extends EventEmitter {
2172
2317
  for (const detail of interfaceDetails) {
2173
2318
  if (detail.family === 'IPv4' && !detail.internal && this.systemInformation.ipv4Address === 'Not found') {
2174
2319
  this.systemInformation.ipv4Address = detail.address;
2320
+ this.systemInformation.macAddress = detail.mac;
2175
2321
  }
2176
2322
  else if (detail.family === 'IPv6' && !detail.internal && this.systemInformation.ipv6Address === 'Not found') {
2177
2323
  this.systemInformation.ipv6Address = detail.address;
2324
+ this.systemInformation.macAddress = detail.mac;
2178
2325
  }
2179
2326
  }
2180
2327
  // Break if both addresses are found to improve efficiency
@@ -2201,6 +2348,7 @@ export class Matterbridge extends EventEmitter {
2201
2348
  this.log.debug('Host System Information:');
2202
2349
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
2203
2350
  this.log.debug(`- User: ${this.systemInformation.user}`);
2351
+ this.log.debug(`- MAC Address: ${this.systemInformation.macAddress}`);
2204
2352
  this.log.debug(`- IPv4 Address: ${this.systemInformation.ipv4Address}`);
2205
2353
  this.log.debug(`- IPv6 Address: ${this.systemInformation.ipv6Address}`);
2206
2354
  this.log.debug(`- Node.js: ${versionMajor}.${versionMinor}.${versionPatch}`);
@@ -2316,25 +2464,30 @@ export class Matterbridge extends EventEmitter {
2316
2464
  *
2317
2465
  * @returns {BaseRegisteredPlugin[]} An array of base registered plugins.
2318
2466
  */
2319
- getBaseRegisteredPlugins() {
2320
- const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
2321
- path: plugin.path,
2322
- type: plugin.type,
2323
- name: plugin.name,
2324
- version: plugin.version,
2325
- latestVersion: plugin.latestVersion,
2326
- description: plugin.description,
2327
- author: plugin.author,
2328
- enabled: plugin.enabled,
2329
- loaded: plugin.loaded,
2330
- started: plugin.started,
2331
- configured: plugin.configured,
2332
- paired: plugin.paired,
2333
- connected: plugin.connected,
2334
- registeredDevices: plugin.registeredDevices,
2335
- qrPairingCode: plugin.qrPairingCode,
2336
- manualPairingCode: plugin.manualPairingCode,
2337
- }));
2467
+ async getBaseRegisteredPlugins(includeConfigSchema = false) {
2468
+ const baseRegisteredPlugins = [];
2469
+ for (const plugin of this.registeredPlugins) {
2470
+ baseRegisteredPlugins.push({
2471
+ path: plugin.path,
2472
+ type: plugin.type,
2473
+ name: plugin.name,
2474
+ version: plugin.version,
2475
+ latestVersion: plugin.latestVersion,
2476
+ description: plugin.description,
2477
+ author: plugin.author,
2478
+ enabled: plugin.enabled,
2479
+ loaded: plugin.loaded,
2480
+ started: plugin.started,
2481
+ configured: plugin.configured,
2482
+ paired: plugin.paired,
2483
+ connected: plugin.connected,
2484
+ registeredDevices: plugin.registeredDevices,
2485
+ qrPairingCode: plugin.qrPairingCode,
2486
+ manualPairingCode: plugin.manualPairingCode,
2487
+ configJson: includeConfigSchema ? await this.loadPluginConfig(plugin) : {},
2488
+ schemaJson: includeConfigSchema ? await this.loadPluginSchema(plugin) : {},
2489
+ });
2490
+ }
2338
2491
  return baseRegisteredPlugins;
2339
2492
  }
2340
2493
  /**
@@ -2555,9 +2708,9 @@ export class Matterbridge extends EventEmitter {
2555
2708
  res.json(response);
2556
2709
  });
2557
2710
  // Endpoint to provide plugins
2558
- this.expressApp.get('/api/plugins', (req, res) => {
2711
+ this.expressApp.get('/api/plugins', async (req, res) => {
2559
2712
  this.log.debug('The frontend sent /api/plugins');
2560
- res.json(this.getBaseRegisteredPlugins());
2713
+ res.json(await this.getBaseRegisteredPlugins(true));
2561
2714
  });
2562
2715
  // Endpoint to provide devices
2563
2716
  this.expressApp.get('/api/devices', (req, res) => {
@@ -2662,7 +2815,7 @@ export class Matterbridge extends EventEmitter {
2662
2815
  res.json(data);
2663
2816
  });
2664
2817
  // Endpoint to receive commands
2665
- this.expressApp.post('/api/command/:command/:param', async (req, res) => {
2818
+ this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2666
2819
  const command = req.params.command;
2667
2820
  let param = req.params.param;
2668
2821
  this.log.debug(`The frontend sent /api/command/${command}/${param}`);
@@ -2679,6 +2832,7 @@ export class Matterbridge extends EventEmitter {
2679
2832
  }
2680
2833
  // Handle the command debugLevel from Settings
2681
2834
  if (command === 'setloglevel') {
2835
+ this.log.debug('setloglevel:', param);
2682
2836
  if (param === 'Debug') {
2683
2837
  this.log.setLogDebug(true);
2684
2838
  this.debugEnabled = true;
@@ -2737,10 +2891,23 @@ export class Matterbridge extends EventEmitter {
2737
2891
  }
2738
2892
  this.updateProcess();
2739
2893
  }
2894
+ // Handle the command saveschema from Home
2895
+ if (command === 'saveconfig') {
2896
+ param = param.replace(/\*/g, '\\');
2897
+ this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
2898
+ //console.log('Req.body:', JSON.stringify(req.body, null, 2));
2899
+ const plugins = await this.nodeContext?.get('plugins');
2900
+ if (!plugins)
2901
+ return;
2902
+ const plugin = plugins.find((plugin) => plugin.name === param);
2903
+ if (!plugin)
2904
+ return;
2905
+ this.savePluginConfigFromJson(plugin, req.body);
2906
+ }
2740
2907
  // Handle the command installplugin from Home
2741
2908
  if (command === 'installplugin') {
2742
2909
  param = param.replace(/\*/g, '\\');
2743
- this.log.info(`Installing plugin ${plg}${param}${db}...`);
2910
+ this.log.info(`Installing plugin ${plg}${param}${nf}...`);
2744
2911
  try {
2745
2912
  await this.spawnCommand('npm', ['install', '-g', param, '--loglevel=verbose']);
2746
2913
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
@@ -2772,7 +2939,7 @@ export class Matterbridge extends EventEmitter {
2772
2939
  const plugin = { path: packageJsonPath, type: '', name: packageJson.name, version: packageJson.version, description: packageJson.description, author: packageJson.author, enabled: true };
2773
2940
  if (await this.loadPlugin(plugin)) {
2774
2941
  this.registeredPlugins.push(plugin);
2775
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
2942
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
2776
2943
  this.log.info(`Plugin ${plg}${packageJsonPath}${nf} type ${plugin.type} added to matterbridge. Restart required.`);
2777
2944
  }
2778
2945
  else {
@@ -2794,7 +2961,7 @@ export class Matterbridge extends EventEmitter {
2794
2961
  // await this.savePluginConfig(this.registeredPlugins[index]);
2795
2962
  }
2796
2963
  this.registeredPlugins.splice(index, 1);
2797
- await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
2964
+ await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
2798
2965
  this.log.info(`Plugin ${plg}${param}${nf} removed from matterbridge`);
2799
2966
  }
2800
2967
  else {