matterbridge 1.4.0 → 1.4.2

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 (53) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +6 -4
  3. package/dist/cluster/AirQualityCluster.d.ts.map +1 -1
  4. package/dist/cluster/AirQualityCluster.js +1 -0
  5. package/dist/cluster/AirQualityCluster.js.map +1 -1
  6. package/dist/cluster/BridgedDeviceBasicInformationCluster.d.ts.map +1 -1
  7. package/dist/cluster/BridgedDeviceBasicInformationCluster.js +1 -0
  8. package/dist/cluster/BridgedDeviceBasicInformationCluster.js.map +1 -1
  9. package/dist/cluster/PowerTopologyCluster.d.ts.map +1 -1
  10. package/dist/cluster/PowerTopologyCluster.js +1 -1
  11. package/dist/cluster/PowerTopologyCluster.js.map +1 -1
  12. package/dist/cluster/TvocCluster.d.ts.map +1 -1
  13. package/dist/cluster/TvocCluster.js +1 -0
  14. package/dist/cluster/TvocCluster.js.map +1 -1
  15. package/dist/deviceManager.d.ts +46 -0
  16. package/dist/deviceManager.d.ts.map +1 -0
  17. package/dist/deviceManager.js +92 -0
  18. package/dist/deviceManager.js.map +1 -0
  19. package/dist/matterbridge.d.ts +21 -76
  20. package/dist/matterbridge.d.ts.map +1 -1
  21. package/dist/matterbridge.js +345 -459
  22. package/dist/matterbridge.js.map +1 -1
  23. package/dist/matterbridgeDevice.d.ts +69 -148
  24. package/dist/matterbridgeDevice.d.ts.map +1 -1
  25. package/dist/matterbridgeDevice.js +232 -30
  26. package/dist/matterbridgeDevice.js.map +1 -1
  27. package/dist/matterbridgePlatform.d.ts +6 -1
  28. package/dist/matterbridgePlatform.d.ts.map +1 -1
  29. package/dist/matterbridgePlatform.js +8 -0
  30. package/dist/matterbridgePlatform.js.map +1 -1
  31. package/dist/matterbridgeTypes.d.ts +28 -1
  32. package/dist/matterbridgeTypes.d.ts.map +1 -1
  33. package/dist/matterbridgeTypes.js +26 -1
  34. package/dist/matterbridgeTypes.js.map +1 -1
  35. package/dist/{plugins.d.ts → pluginManager.d.ts} +6 -3
  36. package/dist/pluginManager.d.ts.map +1 -0
  37. package/dist/{plugins.js → pluginManager.js} +26 -16
  38. package/dist/pluginManager.js.map +1 -0
  39. package/dist/utils/utils.d.ts +57 -0
  40. package/dist/utils/utils.d.ts.map +1 -1
  41. package/dist/utils/utils.js +103 -0
  42. package/dist/utils/utils.js.map +1 -1
  43. package/frontend/build/asset-manifest.json +6 -6
  44. package/frontend/build/index.html +1 -1
  45. package/frontend/build/static/css/{main.df840158.css → main.5174e68c.css} +2 -2
  46. package/frontend/build/static/css/{main.df840158.css.map → main.5174e68c.css.map} +1 -1
  47. package/frontend/build/static/js/{main.2a46688a.js → main.dec34964.js} +3 -3
  48. package/frontend/build/static/js/main.dec34964.js.map +1 -0
  49. package/package.json +26 -25
  50. package/dist/plugins.d.ts.map +0 -1
  51. package/dist/plugins.js.map +0 -1
  52. package/frontend/build/static/js/main.2a46688a.js.map +0 -1
  53. /package/frontend/build/static/js/{main.2a46688a.js.LICENSE.txt → main.dec34964.js.LICENSE.txt} +0 -0
@@ -4,7 +4,7 @@
4
4
  * @file matterbridge.ts
5
5
  * @author Luca Liguori
6
6
  * @date 2023-12-29
7
- * @version 1.3.2
7
+ * @version 1.4.0
8
8
  *
9
9
  * Copyright 2023, 2024 Luca Liguori.
10
10
  *
@@ -21,8 +21,8 @@
21
21
  * limitations under the License. *
22
22
  */
23
23
  import { NodeStorageManager } from 'node-persist-manager';
24
- import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, er, nf, rs, wr, RED, GREEN, zb, hk, or, idn, BLUE, CYAN } from 'node-ansi-logger';
25
- import { fileURLToPath, pathToFileURL } from 'url';
24
+ import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, hk, or, idn, BLUE, CYAN, nt } from 'node-ansi-logger';
25
+ import { fileURLToPath } from 'url';
26
26
  import { promises as fs } from 'fs';
27
27
  import { exec, spawn } from 'child_process';
28
28
  import { createServer } from 'http';
@@ -34,7 +34,6 @@ import path from 'path';
34
34
  import WebSocket, { WebSocketServer } from 'ws';
35
35
  // Matterbridge
36
36
  import { MatterbridgeDevice } from './matterbridgeDevice.js';
37
- import { shelly_config, somfytahoma_config, zigbee2mqtt_config } from './defaultConfigSchema.js';
38
37
  import { BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster } from './cluster/BridgedDeviceBasicInformationCluster.js';
39
38
  import { logInterfaces, wait, waiter } from './utils/utils.js';
40
39
  // @project-chip/matter-node.js
@@ -42,12 +41,13 @@ import { CommissioningController, CommissioningServer, MatterServer } from '@pro
42
41
  import { BasicInformationCluster, ClusterServer, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, SwitchCluster, ThreadNetworkDiagnosticsCluster, getClusterNameById } from '@project-chip/matter-node.js/cluster';
43
42
  import { DeviceTypeId, VendorId } from '@project-chip/matter-node.js/datatype';
44
43
  import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter-node.js/device';
45
- import { Format, Level, Logger } from '@project-chip/matter-node.js/log';
44
+ import { createFileLogger, Format, Level, Logger } from '@project-chip/matter-node.js/log';
46
45
  import { ManualPairingCodeCodec, QrCodeSchema } from '@project-chip/matter-node.js/schema';
47
46
  import { StorageBackendDisk, StorageBackendJsonFile, StorageManager } from '@project-chip/matter-node.js/storage';
48
47
  import { requireMinNodeVersion, getParameter, getIntParameter, hasParameter } from '@project-chip/matter-node.js/util';
49
48
  import { CryptoNode } from '@project-chip/matter-node.js/crypto';
50
- import { Plugins } from './plugins.js';
49
+ import { PluginManager } from './pluginManager.js';
50
+ import { DeviceManager } from './deviceManager.js';
51
51
  // Default colors
52
52
  const plg = '\u001B[38;5;33m';
53
53
  const dev = '\u001B[38;5;79m';
@@ -86,7 +86,7 @@ export class Matterbridge extends EventEmitter {
86
86
  matterbridgeConnected: false,
87
87
  bridgeMode: '',
88
88
  restartMode: '',
89
- debugEnabled: false,
89
+ loggerLevel: "info" /* LogLevel.INFO */,
90
90
  matterLoggerLevel: Level.INFO,
91
91
  };
92
92
  homeDirectory = '';
@@ -102,10 +102,14 @@ export class Matterbridge extends EventEmitter {
102
102
  matterbridgeConnected = false;
103
103
  bridgeMode = '';
104
104
  restartMode = '';
105
- debugEnabled = false;
105
+ // public debugEnabled = false;
106
+ // public loggerLevel: LogLevel = LogLevel.INFO;
106
107
  profile = getParameter('profile');
107
108
  log;
109
+ matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
110
+ matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
108
111
  plugins;
112
+ devices;
109
113
  registeredDevices = [];
110
114
  nodeStorage;
111
115
  nodeContext;
@@ -201,12 +205,46 @@ export class Matterbridge extends EventEmitter {
201
205
  this.restartMode = 'service';
202
206
  if (hasParameter('docker'))
203
207
  this.restartMode = 'docker';
204
- // Set Matterbridge logger
205
- if (hasParameter('debug'))
206
- this.debugEnabled = true;
207
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled });
208
+ // Set the matterbridge directory
209
+ this.homeDirectory = os.homedir();
210
+ this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
211
+ // Create the file logger for matterbridge
212
+ if (hasParameter('filelogger'))
213
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
214
+ // Create matterbridge logger
215
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
216
+ // Set matterbridge logger level
217
+ if (hasParameter('logger')) {
218
+ const level = getParameter('logger');
219
+ if (level === 'debug') {
220
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
221
+ }
222
+ else if (level === 'info') {
223
+ this.log.logLevel = "info" /* LogLevel.INFO */;
224
+ }
225
+ else if (level === 'notice') {
226
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
227
+ }
228
+ else if (level === 'warn') {
229
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
230
+ }
231
+ else if (level === 'error') {
232
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
233
+ }
234
+ else if (level === 'fatal') {
235
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
236
+ }
237
+ else {
238
+ this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
239
+ this.log.logLevel = "info" /* LogLevel.INFO */;
240
+ }
241
+ }
242
+ else {
243
+ this.log.logLevel = "info" /* LogLevel.INFO */;
244
+ }
245
+ MatterbridgeDevice.logLevel = this.log.logLevel;
208
246
  this.log.debug('Matterbridge is starting...');
209
- // Set matter.js logger level and format
247
+ // Set matter.js logger level, format and logger
210
248
  if (hasParameter('matterlogger')) {
211
249
  const level = getParameter('matterlogger');
212
250
  if (level === 'debug') {
@@ -228,7 +266,7 @@ export class Matterbridge extends EventEmitter {
228
266
  Logger.defaultLogLevel = Level.FATAL;
229
267
  }
230
268
  else {
231
- this.log.warn(`Invalid matterlogger level: ${level}. Using default level ${this.debugEnabled ? 'debug' : 'info'}.`);
269
+ this.log.warn(`Invalid matter.js logger level: ${level}. Using default level "info".`);
232
270
  Logger.defaultLogLevel = Level.INFO;
233
271
  }
234
272
  }
@@ -236,16 +274,30 @@ export class Matterbridge extends EventEmitter {
236
274
  Logger.defaultLogLevel = Level.INFO;
237
275
  }
238
276
  Logger.format = Format.ANSI;
277
+ Logger.setLogger('default', this.createMatterLogger());
278
+ // Create the file logger for matter.js
279
+ if (hasParameter('matterfilelogger')) {
280
+ try {
281
+ await fs.unlink(path.join(this.matterbridgeDirectory, this.matterLoggerFile));
282
+ }
283
+ catch (error) {
284
+ this.log.debug(`Error unlinking the log file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
285
+ }
286
+ Logger.addLogger('filelogger', await createFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile)), {
287
+ defaultLogLevel: Level.DEBUG,
288
+ logFormat: Format.PLAIN,
289
+ });
290
+ }
239
291
  // Initialize nodeStorage and nodeContext
240
- this.homeDirectory = os.homedir();
241
- this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
242
292
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
243
293
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
244
294
  this.log.debug('Creating node storage context for matterbridge');
245
295
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
246
- // Initialize Plugins
247
- this.plugins = new Plugins(this);
296
+ // Initialize PluginManager
297
+ this.plugins = new PluginManager(this);
248
298
  await this.plugins.loadFromStorage();
299
+ // Initialize DeviceManager
300
+ this.devices = new DeviceManager(this, this.nodeContext);
249
301
  // Get the plugins from node storage and create the plugins node storage contexts
250
302
  for (const plugin of this.plugins) {
251
303
  const packageJson = await this.plugins.parse(plugin);
@@ -256,6 +308,7 @@ export class Matterbridge extends EventEmitter {
256
308
  await this.spawnCommand('npm', ['install', '-g', plugin.name]);
257
309
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
258
310
  plugin.error = false;
311
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
259
312
  }
260
313
  catch (error) {
261
314
  plugin.error = true;
@@ -274,7 +327,7 @@ export class Matterbridge extends EventEmitter {
274
327
  }
275
328
  // Log system info and create .matterbridge directory
276
329
  await this.logNodeAndSystemInfo();
277
- this.log.info(`Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} ` +
330
+ this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ${hasParameter('bridge') ? 'mode bridge' : ''}${hasParameter('childbridge') ? 'mode childbridge' : ''}${hasParameter('controller') ? 'mode controller' : ''} ` +
278
331
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch} `);
279
332
  // Check node version and throw error
280
333
  requireMinNodeVersion(18);
@@ -299,7 +352,7 @@ export class Matterbridge extends EventEmitter {
299
352
  - port [port]: start the commissioning server on the given port (default 5540)
300
353
  - mdnsinterface [name]: set the interface to use for the matter server mdnsInterface (default all interfaces)
301
354
  - frontend [port]: start the frontend on the given port (default 8283)
302
- - debug: enable the Matterbridge debug mode (default false)
355
+ - logger: set the matterbridge logger level: debug | info | notice | warn | error | fatal (default info)
303
356
  - matterlogger: set the matter.js logger level: debug | info | notice | warn | error | fatal (default info)
304
357
  - reset: remove the commissioning for Matterbridge (bridge mode). Shutdown Matterbridge before using it!
305
358
  - factoryreset: remove all commissioning information and reset all internal storages. Shutdown Matterbridge before using it!
@@ -417,13 +470,13 @@ export class Matterbridge extends EventEmitter {
417
470
  return;
418
471
  }
419
472
  // Start the matter storage and create the matterbridge context
420
- await this.startStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
473
+ await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
421
474
  this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
422
475
  this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
423
476
  if (hasParameter('reset') && getParameter('reset') === undefined) {
424
477
  this.log.info('Resetting Matterbridge commissioning information...');
425
478
  await this.matterbridgeContext?.clearAll();
426
- await this.stopStorage();
479
+ await this.stopMatterStorage();
427
480
  this.log.info('Reset done! Remove the device from the controller.');
428
481
  this.emit('shutdown');
429
482
  return;
@@ -443,7 +496,7 @@ export class Matterbridge extends EventEmitter {
443
496
  else {
444
497
  this.log.warn(`Plugin ${plg}${getParameter('reset')}${wr} not registerd in matterbridge`);
445
498
  }
446
- await this.stopStorage();
499
+ await this.stopMatterStorage();
447
500
  this.emit('shutdown');
448
501
  return;
449
502
  }
@@ -484,8 +537,8 @@ export class Matterbridge extends EventEmitter {
484
537
  this.log.debug('Adding matterbridge commissioning server to matter server');
485
538
  await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
486
539
  for (const plugin of this.plugins) {
487
- plugin.configJson = await this.loadPluginConfig(plugin);
488
- plugin.schemaJson = await this.loadPluginSchema(plugin);
540
+ plugin.configJson = await this.plugins.loadConfig(plugin);
541
+ plugin.schemaJson = await this.plugins.loadSchema(plugin);
489
542
  // Check if the plugin is available
490
543
  if (!(await this.plugins.resolve(plugin.path))) {
491
544
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found. Disabling it.`);
@@ -509,7 +562,7 @@ export class Matterbridge extends EventEmitter {
509
562
  plugin.addedDevices = undefined;
510
563
  plugin.qrPairingCode = undefined;
511
564
  plugin.manualPairingCode = undefined;
512
- this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
565
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
513
566
  }
514
567
  await this.startBridge();
515
568
  return;
@@ -522,8 +575,8 @@ export class Matterbridge extends EventEmitter {
522
575
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
523
576
  this.matterServer = this.createMatterServer(this.storageManager);
524
577
  for (const plugin of this.plugins) {
525
- plugin.configJson = await this.loadPluginConfig(plugin);
526
- plugin.schemaJson = await this.loadPluginSchema(plugin);
578
+ plugin.configJson = await this.plugins.loadConfig(plugin);
579
+ plugin.schemaJson = await this.plugins.loadSchema(plugin);
527
580
  // Check if the plugin is available
528
581
  if (!(await this.plugins.resolve(plugin.path))) {
529
582
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found. Disabling it.`);
@@ -547,7 +600,7 @@ export class Matterbridge extends EventEmitter {
547
600
  plugin.addedDevices = undefined;
548
601
  plugin.qrPairingCode = (await plugin.nodeContext?.get('qrPairingCode', undefined)) ?? undefined;
549
602
  plugin.manualPairingCode = (await plugin.nodeContext?.get('manualPairingCode', undefined)) ?? undefined;
550
- this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
603
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
551
604
  }
552
605
  await this.startChildbridge();
553
606
  return;
@@ -573,9 +626,11 @@ export class Matterbridge extends EventEmitter {
573
626
  */
574
627
  deregisterSignalHandlers() {
575
628
  this.log.debug(`Deregistering SIGINT and SIGTERM signal handlers...`);
576
- this.sigintHandler && process.off('SIGINT', this.sigintHandler);
629
+ if (this.sigintHandler)
630
+ process.off('SIGINT', this.sigintHandler);
577
631
  this.sigintHandler = undefined;
578
- this.sigtermHandler && process.off('SIGTERM', this.sigtermHandler);
632
+ if (this.sigtermHandler)
633
+ process.off('SIGTERM', this.sigtermHandler);
579
634
  this.sigtermHandler = undefined;
580
635
  }
581
636
  /**
@@ -824,6 +879,37 @@ export class Matterbridge extends EventEmitter {
824
879
  // error.stack && this.log.debug(error.stack);
825
880
  });
826
881
  }
882
+ createMatterLogger() {
883
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
884
+ return (_level, formattedLog) => {
885
+ const logger = formattedLog.slice(44, 44 + 20).trim();
886
+ const message = formattedLog.slice(65);
887
+ matterLogger.logName = logger;
888
+ switch (_level) {
889
+ case Level.DEBUG:
890
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
891
+ break;
892
+ case Level.INFO:
893
+ matterLogger.log("info" /* LogLevel.INFO */, message);
894
+ break;
895
+ case Level.NOTICE:
896
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
897
+ break;
898
+ case Level.WARN:
899
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
900
+ break;
901
+ case Level.ERROR:
902
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
903
+ break;
904
+ case Level.FATAL:
905
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
906
+ break;
907
+ default:
908
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
909
+ break;
910
+ }
911
+ };
912
+ }
827
913
  /**
828
914
  * Update matterbridge and cleanup.
829
915
  */
@@ -945,8 +1031,8 @@ export class Matterbridge extends EventEmitter {
945
1031
  // this.cleanupTimeout1 = setTimeout(async () => {
946
1032
  // Closing matter
947
1033
  await this.stopMatterServer();
948
- // Closing storage
949
- await this.stopStorage();
1034
+ // Closing matter storage
1035
+ await this.stopMatterStorage();
950
1036
  // Serialize registeredDevices
951
1037
  if (this.nodeStorage && this.nodeContext) {
952
1038
  this.log.info('Saving registered devices...');
@@ -1010,7 +1096,7 @@ export class Matterbridge extends EventEmitter {
1010
1096
  await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1011
1097
  this.log.info('Factory reset done! Remove all paired devices from the controllers.');
1012
1098
  }
1013
- this.log.info('Cleanup completed. Shutting down...');
1099
+ this.log.notice('Cleanup completed. Shutting down...');
1014
1100
  Matterbridge.instance = undefined;
1015
1101
  this.emit('shutdown');
1016
1102
  }
@@ -1027,10 +1113,6 @@ export class Matterbridge extends EventEmitter {
1027
1113
  * @returns {Promise<void>} - A promise that resolves when the device is added.
1028
1114
  */
1029
1115
  async addBridgedDevice(pluginName, device) {
1030
- if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
1031
- this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
1032
- return;
1033
- }
1034
1116
  this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${dev}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1035
1117
  // Check if the plugin is registered
1036
1118
  const plugin = this.plugins.get(pluginName);
@@ -1040,12 +1122,17 @@ export class Matterbridge extends EventEmitter {
1040
1122
  }
1041
1123
  // Register and add the device to matterbridge aggregator in bridge mode
1042
1124
  if (this.bridgeMode === 'bridge') {
1043
- this.matterAggregator?.addBridgedDevice(device);
1125
+ if (!this.matterAggregator) {
1126
+ this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
1127
+ return;
1128
+ }
1129
+ this.matterAggregator.addBridgedDevice(device);
1044
1130
  }
1045
1131
  // The first time create the commissioning server and the aggregator for DynamicPlatform
1046
1132
  // Register and add the device in childbridge mode
1047
1133
  if (this.bridgeMode === 'childbridge') {
1048
1134
  if (plugin.type === 'AccessoryPlatform') {
1135
+ // Check if the plugin is locked with the commissioning server
1049
1136
  if (!plugin.locked) {
1050
1137
  plugin.locked = true;
1051
1138
  plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
@@ -1059,10 +1146,10 @@ export class Matterbridge extends EventEmitter {
1059
1146
  }
1060
1147
  }
1061
1148
  if (plugin.type === 'DynamicPlatform') {
1149
+ // Check if the plugin is locked with the commissioning server and the aggregator
1062
1150
  if (!plugin.locked) {
1063
1151
  plugin.locked = true;
1064
1152
  this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
1065
- // plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Dynamic Platform');
1066
1153
  plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, plugin.description);
1067
1154
  this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
1068
1155
  plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
@@ -1081,6 +1168,8 @@ export class Matterbridge extends EventEmitter {
1081
1168
  plugin.registeredDevices++;
1082
1169
  if (plugin.addedDevices !== undefined)
1083
1170
  plugin.addedDevices++;
1171
+ // Add the device to the DeviceManager
1172
+ this.devices.set(device);
1084
1173
  this.log.info(`Added and registered bridged device (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1085
1174
  }
1086
1175
  /**
@@ -1090,10 +1179,6 @@ export class Matterbridge extends EventEmitter {
1090
1179
  * @returns A Promise that resolves when the device is successfully removed.
1091
1180
  */
1092
1181
  async removeBridgedDevice(pluginName, device) {
1093
- if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
1094
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
1095
- return;
1096
- }
1097
1182
  this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${dev}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1098
1183
  // Check if the plugin is registered
1099
1184
  const plugin = this.plugins.get(pluginName);
@@ -1101,18 +1186,16 @@ export class Matterbridge extends EventEmitter {
1101
1186
  this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1102
1187
  return;
1103
1188
  }
1104
- if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatfoem' && !plugin.aggregator) {
1105
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
1106
- return;
1107
- }
1108
- if (this.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatfoem' && !plugin.commissioningServer) {
1109
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
1110
- return;
1111
- }
1112
1189
  // Remove the device from matterbridge aggregator in bridge mode
1113
1190
  if (this.bridgeMode === 'bridge') {
1191
+ if (!this.matterAggregator) {
1192
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
1193
+ return;
1194
+ }
1114
1195
  device.setBridgedDeviceReachability(false);
1115
1196
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1197
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1198
+ // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1116
1199
  this.matterAggregator?.removeBridgedDevice(device);
1117
1200
  this.registeredDevices.forEach((registeredDevice, index) => {
1118
1201
  if (registeredDevice.device === device) {
@@ -1129,6 +1212,10 @@ export class Matterbridge extends EventEmitter {
1129
1212
  // Remove the device in childbridge mode
1130
1213
  if (this.bridgeMode === 'childbridge') {
1131
1214
  if (plugin.type === 'AccessoryPlatform') {
1215
+ if (!plugin.commissioningServer) {
1216
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
1217
+ return;
1218
+ }
1132
1219
  this.registeredDevices.forEach((registeredDevice, index) => {
1133
1220
  if (registeredDevice.device === device) {
1134
1221
  this.registeredDevices.splice(index, 1);
@@ -1137,6 +1224,10 @@ export class Matterbridge extends EventEmitter {
1137
1224
  });
1138
1225
  }
1139
1226
  else if (plugin.type === 'DynamicPlatform') {
1227
+ if (!plugin.aggregator) {
1228
+ this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
1229
+ return;
1230
+ }
1140
1231
  this.registeredDevices.forEach((registeredDevice, index) => {
1141
1232
  if (registeredDevice.device === device) {
1142
1233
  this.registeredDevices.splice(index, 1);
@@ -1145,19 +1236,22 @@ export class Matterbridge extends EventEmitter {
1145
1236
  });
1146
1237
  device.setBridgedDeviceReachability(false);
1147
1238
  device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1148
- plugin.aggregator?.removeBridgedDevice(device);
1239
+ plugin.aggregator.removeBridgedDevice(device);
1149
1240
  }
1150
1241
  this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1151
1242
  if (plugin.registeredDevices !== undefined)
1152
1243
  plugin.registeredDevices--;
1153
1244
  if (plugin.addedDevices !== undefined)
1154
1245
  plugin.addedDevices--;
1246
+ // Remove the commissioning server
1155
1247
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1156
1248
  this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1157
1249
  plugin.commissioningServer = undefined;
1158
1250
  this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1159
1251
  }
1160
1252
  }
1253
+ // Remove the device from the DeviceManager
1254
+ this.devices.remove(device);
1161
1255
  }
1162
1256
  /**
1163
1257
  * Removes all bridged devices associated with a specific plugin.
@@ -1177,304 +1271,6 @@ export class Matterbridge extends EventEmitter {
1177
1271
  this.removeBridgedDevice(pluginName, registeredDevice.device);
1178
1272
  }
1179
1273
  }
1180
- /**
1181
- * Loads the schema for a plugin.
1182
- * If the schema file exists in the plugin directory, it reads the file and returns the parsed JSON data and delete the schema form .matterbridge.
1183
- * If the schema file does not exist, it creates a new schema with the default configuration and returns it.
1184
- *
1185
- * @param plugin - The plugin for which to load the schema.
1186
- * @returns A promise that resolves to the loaded or created schema.
1187
- */
1188
- async loadPluginSchema(plugin) {
1189
- let schemaFile = plugin.path.replace('package.json', `${plugin.name}.schema.json`);
1190
- try {
1191
- await fs.access(schemaFile);
1192
- const data = await fs.readFile(schemaFile, 'utf8');
1193
- const schema = JSON.parse(data);
1194
- schema.title = plugin.description;
1195
- schema.description = plugin.name + ' v. ' + plugin.version + ' by ' + plugin.author;
1196
- this.log.debug(`Schema file found: ${schemaFile}.`);
1197
- // this.log.debug(`Schema file found: ${schemaFile}.\nSchema:${rs}\n`, schema);
1198
- schemaFile = path.join(this.matterbridgeDirectory, `${plugin.name}.schema.json`);
1199
- try {
1200
- await fs.unlink(schemaFile);
1201
- this.log.debug(`Schema file ${schemaFile} deleted.`);
1202
- }
1203
- catch (err) {
1204
- this.log.debug(`Schema file ${schemaFile} to delete not found.`);
1205
- }
1206
- return schema;
1207
- }
1208
- catch (err) {
1209
- this.log.debug(`Schema file ${schemaFile} not found. Loading default schema.`);
1210
- const schema = {
1211
- title: plugin.description,
1212
- description: plugin.name + ' v. ' + plugin.version + ' by ' + plugin.author,
1213
- type: 'object',
1214
- properties: {
1215
- name: {
1216
- description: 'Plugin name',
1217
- type: 'string',
1218
- readOnly: true,
1219
- },
1220
- type: {
1221
- description: 'Plugin type',
1222
- type: 'string',
1223
- readOnly: true,
1224
- },
1225
- debug: {
1226
- description: 'Enable the debug for the plugin (development only)',
1227
- type: 'boolean',
1228
- default: false,
1229
- },
1230
- unregisterOnShutdown: {
1231
- description: 'Unregister all devices on shutdown (development only)',
1232
- type: 'boolean',
1233
- default: false,
1234
- },
1235
- },
1236
- };
1237
- return schema;
1238
- }
1239
- }
1240
- /**
1241
- * Loads the configuration for a plugin.
1242
- * If the configuration file exists, it reads the file and returns the parsed JSON data.
1243
- * If the configuration file does not exist, it creates a new file with default configuration and returns it.
1244
- * If any error occurs during file access or creation, it logs an error and return un empty config.
1245
- *
1246
- * @param plugin - The plugin for which to load the configuration.
1247
- * @returns A promise that resolves to the loaded or created configuration.
1248
- */
1249
- async loadPluginConfig(plugin) {
1250
- const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
1251
- try {
1252
- await fs.access(configFile);
1253
- const data = await fs.readFile(configFile, 'utf8');
1254
- const config = JSON.parse(data);
1255
- this.log.debug(`Config file found: ${configFile}.`);
1256
- // this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, config);
1257
- /* The first time a plugin is added to the system, the config file is created with the plugin name and type "".*/
1258
- config.name = plugin.name;
1259
- config.type = plugin.type;
1260
- if (config.debug === undefined)
1261
- config.debug = false;
1262
- if (config.unregisterOnShutdown === undefined)
1263
- config.unregisterOnShutdown = false;
1264
- return config;
1265
- }
1266
- catch (err) {
1267
- if (err instanceof Error) {
1268
- const nodeErr = err;
1269
- if (nodeErr.code === 'ENOENT') {
1270
- let config;
1271
- if (plugin.name === 'matterbridge-zigbee2mqtt')
1272
- config = zigbee2mqtt_config;
1273
- else if (plugin.name === 'matterbridge-somfy-tahoma')
1274
- config = somfytahoma_config;
1275
- else if (plugin.name === 'matterbridge-shelly')
1276
- config = shelly_config;
1277
- else
1278
- config = { name: plugin.name, type: plugin.type, debug: false, unregisterOnShutdown: false };
1279
- try {
1280
- await this.writeFile(configFile, JSON.stringify(config, null, 2));
1281
- this.log.debug(`Created config file: ${configFile}.`);
1282
- // this.log.debug(`Created config file: ${configFile}.\nConfig:${rs}\n`, config);
1283
- return config;
1284
- }
1285
- catch (err) {
1286
- this.log.error(`Error creating config file ${configFile}: ${err}`);
1287
- return config;
1288
- }
1289
- }
1290
- else {
1291
- this.log.error(`Error accessing config file ${configFile}: ${err}`);
1292
- return { name: plugin.name, type: plugin.type, debug: false, unregisterOnShutdown: false };
1293
- }
1294
- }
1295
- this.log.error(`Error loading config file ${configFile}: ${err}`);
1296
- return { name: plugin.name, type: plugin.type, debug: false, unregisterOnShutdown: false };
1297
- }
1298
- }
1299
- /**
1300
- * Saves the configuration of a registered plugin.
1301
- * @param {RegisteredPlugin} plugin - The plugin whose configuration needs to be saved.
1302
- * @returns {Promise<void>} - A promise that resolves when the configuration is successfully saved.
1303
- * @throws {Error} - If the plugin's configuration is not found or if there is an error while saving the configuration.
1304
- */
1305
- async savePluginConfig(plugin) {
1306
- if (!plugin.platform?.config) {
1307
- this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`);
1308
- return Promise.reject(new Error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`));
1309
- }
1310
- const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
1311
- try {
1312
- await this.writeFile(configFile, JSON.stringify(plugin.platform.config, null, 2));
1313
- this.log.debug(`Saved config file: ${configFile}.`);
1314
- // this.log.debug(`Saved config file: ${configFile}.\nConfig:${rs}\n`, plugin.platform.config);
1315
- }
1316
- catch (err) {
1317
- this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: ${err}`);
1318
- return Promise.reject(err);
1319
- }
1320
- }
1321
- /**
1322
- * Writes data to a file.
1323
- *
1324
- * @param {string} filePath - The path of the file to write to.
1325
- * @param {string} data - The data to write to the file.
1326
- * @returns {Promise<void>} - A promise that resolves when the data is successfully written to the file.
1327
- */
1328
- async writeFile(filePath, data) {
1329
- try {
1330
- await fs.writeFile(`${filePath}`, data, 'utf8');
1331
- this.log.debug(`Successfully wrote to ${filePath}`);
1332
- }
1333
- catch (error) {
1334
- this.log.error(`Error writing to ${filePath}:`, error);
1335
- }
1336
- }
1337
- /**
1338
- * Loads a plugin and returns the corresponding MatterbridgePlatform instance.
1339
- * @param plugin - The plugin to load.
1340
- * @param start - Optional flag indicating whether to start the plugin after loading. Default is false.
1341
- * @param message - Optional message to pass to the plugin when starting.
1342
- * @returns A Promise that resolves to the loaded MatterbridgePlatform instance.
1343
- * @throws An error if the plugin is not enabled, already loaded, or fails to load.
1344
- */
1345
- async loadPlugin(plugin, start = false, message = '') {
1346
- if (!plugin.enabled) {
1347
- this.log.error(`Plugin ${plg}${plugin.name}${er} not enabled`);
1348
- return Promise.resolve(undefined);
1349
- }
1350
- if (plugin.platform) {
1351
- this.log.error(`Plugin ${plg}${plugin.name}${er} already loaded`);
1352
- return Promise.resolve(plugin.platform);
1353
- }
1354
- this.log.info(`Loading plugin ${plg}${plugin.name}${nf} type ${typ}${plugin.type}${nf}`);
1355
- try {
1356
- // Load the package.json of the plugin
1357
- const packageJson = JSON.parse(await fs.readFile(plugin.path, 'utf8'));
1358
- // Resolve the main module path relative to package.json
1359
- const pluginEntry = path.resolve(path.dirname(plugin.path), packageJson.main);
1360
- // Dynamically import the plugin
1361
- const pluginUrl = pathToFileURL(pluginEntry);
1362
- this.log.debug(`Importing plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
1363
- const pluginInstance = await import(pluginUrl.href);
1364
- this.log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
1365
- // Call the default export function of the plugin, passing this MatterBridge instance, the log and the config
1366
- if (pluginInstance.default) {
1367
- const config = await this.loadPluginConfig(plugin);
1368
- const log = new AnsiLogger({ logName: plugin.description ?? 'No description', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: config.debug ?? false });
1369
- const platform = pluginInstance.default(this, log, config);
1370
- platform.name = packageJson.name;
1371
- platform.config = config;
1372
- platform.version = packageJson.version;
1373
- plugin.name = packageJson.name;
1374
- plugin.description = packageJson.description ?? 'No description';
1375
- plugin.version = packageJson.version;
1376
- plugin.author = packageJson.author ?? 'Unknown';
1377
- plugin.type = platform.type;
1378
- plugin.platform = platform;
1379
- plugin.loaded = true;
1380
- plugin.registeredDevices = 0;
1381
- plugin.addedDevices = 0;
1382
- plugin.configJson = config;
1383
- plugin.schemaJson = await this.loadPluginSchema(plugin);
1384
- this.log.info(`Loaded plugin ${plg}${plugin.name}${nf} type ${typ}${platform.type} ${db}(entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`);
1385
- if (start)
1386
- this.startPlugin(plugin, message); // No await do it asyncronously
1387
- return Promise.resolve(platform);
1388
- }
1389
- else {
1390
- this.log.error(`Plugin ${plg}${plugin.name}${er} does not provide a default export`);
1391
- plugin.error = true;
1392
- return Promise.resolve(undefined);
1393
- }
1394
- }
1395
- catch (err) {
1396
- this.log.error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`);
1397
- plugin.error = true;
1398
- return Promise.resolve(undefined);
1399
- }
1400
- }
1401
- /**
1402
- * Starts a plugin.
1403
- *
1404
- * @param {RegisteredPlugin} plugin - The plugin to start.
1405
- * @param {string} [message] - Optional message to pass to the plugin's onStart method.
1406
- * @param {boolean} [configure] - Indicates whether to configure the plugin after starting (default false).
1407
- * @returns {Promise<void>} A promise that resolves when the plugin is started successfully, or rejects with an error if starting the plugin fails.
1408
- */
1409
- async startPlugin(plugin, message, configure = false) {
1410
- if (!plugin.loaded || !plugin.platform) {
1411
- this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`);
1412
- return Promise.resolve();
1413
- }
1414
- if (plugin.started) {
1415
- this.log.debug(`Plugin ${plg}${plugin.name}${db} already started`);
1416
- return Promise.resolve();
1417
- }
1418
- this.log.info(`Starting plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
1419
- try {
1420
- plugin.platform
1421
- .onStart(message)
1422
- .then(() => {
1423
- plugin.started = true;
1424
- this.log.info(`Started plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
1425
- if (configure)
1426
- this.configurePlugin(plugin); // No await do it asyncronously
1427
- return Promise.resolve();
1428
- })
1429
- .catch((err) => {
1430
- plugin.error = true;
1431
- this.log.error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`);
1432
- return Promise.resolve();
1433
- });
1434
- }
1435
- catch (err) {
1436
- plugin.error = true;
1437
- this.log.error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`);
1438
- return Promise.resolve();
1439
- }
1440
- }
1441
- /**
1442
- * Configures a plugin.
1443
- *
1444
- * @param {RegisteredPlugin} plugin - The plugin to configure.
1445
- * @returns {Promise<void>} A promise that resolves when the plugin is configured successfully, or rejects with an error if configuration fails.
1446
- */
1447
- async configurePlugin(plugin) {
1448
- if (!plugin.loaded || !plugin.started || !plugin.platform) {
1449
- this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded (${plugin.loaded}) or not started (${plugin.started}) or not platform (${plugin.platform?.name})`);
1450
- return Promise.resolve();
1451
- }
1452
- if (plugin.configured) {
1453
- this.log.info(`Plugin ${plg}${plugin.name}${nf} already configured`);
1454
- return Promise.resolve();
1455
- }
1456
- this.log.info(`Configuring plugin ${plg}${plugin.name}${nf} type ${typ}${plugin.type}${nf}`);
1457
- try {
1458
- plugin.platform
1459
- .onConfigure()
1460
- .then(() => {
1461
- plugin.configured = true;
1462
- this.log.info(`Configured plugin ${plg}${plugin.name}${nf} type ${typ}${plugin.type}${nf}`);
1463
- this.savePluginConfig(plugin);
1464
- return Promise.resolve();
1465
- })
1466
- .catch((err) => {
1467
- plugin.error = true;
1468
- this.log.error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`);
1469
- return Promise.resolve();
1470
- });
1471
- }
1472
- catch (err) {
1473
- plugin.error = true;
1474
- this.log.error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`);
1475
- return Promise.resolve();
1476
- }
1477
- }
1478
1274
  async startTest() {
1479
1275
  // Start the Matterbridge
1480
1276
  }
@@ -1514,14 +1310,14 @@ export class Matterbridge extends EventEmitter {
1514
1310
  clearInterval(startMatterInterval);
1515
1311
  this.log.debug('***Cleared startMatterInterval interval for Matterbridge');
1516
1312
  await this.startMatterServer();
1517
- this.log.info('Matter server started');
1313
+ this.log.notice('Matter server started');
1518
1314
  // Configure the plugins
1519
1315
  this.configureTimeout = setTimeout(async () => {
1520
1316
  for (const plugin of this.plugins) {
1521
1317
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1522
1318
  continue;
1523
1319
  try {
1524
- await this.plugins.configure(plugin); // No await do it asyncronously
1320
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1525
1321
  }
1526
1322
  catch (error) {
1527
1323
  plugin.error = true;
@@ -1582,14 +1378,14 @@ export class Matterbridge extends EventEmitter {
1582
1378
  clearInterval(startMatterInterval);
1583
1379
  this.log.debug('***Cleared startMatterInterval interval in childbridge mode');
1584
1380
  await this.startMatterServer();
1585
- this.log.info('Matter server started');
1381
+ this.log.notice('Matter server started');
1586
1382
  // Configure the plugins
1587
1383
  this.configureTimeout = setTimeout(async () => {
1588
1384
  for (const plugin of this.plugins) {
1589
1385
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1590
1386
  continue;
1591
1387
  try {
1592
- await this.plugins.configure(plugin); // No await do it asyncronously
1388
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1593
1389
  }
1594
1390
  catch (error) {
1595
1391
  plugin.error = true;
@@ -1815,7 +1611,7 @@ export class Matterbridge extends EventEmitter {
1815
1611
  * @param {string} storageName - The name of the storage file.
1816
1612
  * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1817
1613
  */
1818
- async startStorage(storageType, storageName) {
1614
+ async startMatterStorage(storageType, storageName) {
1819
1615
  this.log.debug(`Starting ${storageType} storage ${CYAN}${storageName}${db}`);
1820
1616
  if (storageType === 'disk') {
1821
1617
  const storageDisk = new StorageBackendDisk(storageName);
@@ -1836,8 +1632,9 @@ export class Matterbridge extends EventEmitter {
1836
1632
  await this.storageManager.initialize();
1837
1633
  this.log.debug('Storage initialized');
1838
1634
  if (storageType === 'json') {
1839
- await this.backupJsonStorage(storageName, storageName.replace('.json', '') + '.backup.json');
1635
+ await this.backupJsonMatterStorage(storageName, storageName.replace('.json', '') + '.backup.json');
1840
1636
  }
1637
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1841
1638
  }
1842
1639
  catch (error) {
1843
1640
  this.log.error(`Storage initialize() error! The file .matterbridge/${storageName} may be corrupted.`);
@@ -1851,7 +1648,7 @@ export class Matterbridge extends EventEmitter {
1851
1648
  * @param storageName - The name of the JSON storage file to be backed up.
1852
1649
  * @param backupName - The name of the backup file to be created.
1853
1650
  */
1854
- async backupJsonStorage(storageName, backupName) {
1651
+ async backupJsonMatterStorage(storageName, backupName) {
1855
1652
  try {
1856
1653
  this.log.debug(`Making backup copy of ${storageName}`);
1857
1654
  await fs.copyFile(storageName, backupName);
@@ -1875,7 +1672,7 @@ export class Matterbridge extends EventEmitter {
1875
1672
  * Stops the matter storage.
1876
1673
  * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1877
1674
  */
1878
- async stopStorage() {
1675
+ async stopMatterStorage() {
1879
1676
  this.log.debug('Stopping storage');
1880
1677
  await this.storageManager?.close();
1881
1678
  this.log.debug('Storage closed');
@@ -2028,9 +1825,9 @@ export class Matterbridge extends EventEmitter {
2028
1825
  const sessionInformations = commissioningServer.getActiveSessionInformation(fabricIndex);
2029
1826
  let connected = false;
2030
1827
  sessionInformations.forEach((session) => {
2031
- this.log.info(`*Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
1828
+ this.log.info(`Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
2032
1829
  if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
2033
- this.log.info(`*Controller ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} connected to ${plg}${pluginName}${nf} on session ${session.name}`);
1830
+ this.log.notice(`Controller ${zb}${session.fabric?.rootVendorId}${nt} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} connected to ${plg}${pluginName}${nt} on session ${session.name}`);
2034
1831
  connected = true;
2035
1832
  }
2036
1833
  });
@@ -2081,9 +1878,9 @@ export class Matterbridge extends EventEmitter {
2081
1878
  },
2082
1879
  commissioningChangedCallback: async (fabricIndex) => {
2083
1880
  const fabricInfo = commissioningServer.getCommissionedFabricInformation(fabricIndex);
2084
- this.log.debug(`*Commissioning changed on fabric ${zb}${fabricIndex}${nf} for ${plg}${pluginName}${nf}`, debugStringify(fabricInfo));
1881
+ this.log.debug(`Commissioning changed on fabric ${zb}${fabricIndex}${db} for ${plg}${pluginName}${db}`, debugStringify(fabricInfo));
2085
1882
  if (commissioningServer.getCommissionedFabricInformation().length === 0) {
2086
- this.log.warn(`*Commissioning removed from fabric ${zb}${fabricIndex}${nf} for ${plg}${pluginName}${wr}. Resetting the commissioning server ...`);
1883
+ this.log.warn(`Commissioning removed from fabric ${zb}${fabricIndex}${wr} for ${plg}${pluginName}${wr}. Resetting the commissioning server ...`);
2087
1884
  await commissioningServer.factoryReset();
2088
1885
  if (pluginName === 'Matterbridge') {
2089
1886
  await this.matterbridgeContext?.clearAll();
@@ -2104,7 +1901,7 @@ export class Matterbridge extends EventEmitter {
2104
1901
  }
2105
1902
  }
2106
1903
  }
2107
- this.log.warn(`*Restart to activate the pairing for ${plg}${pluginName}${wr}.`);
1904
+ this.log.warn(`Restart to activate the pairing for ${plg}${pluginName}${wr}.`);
2108
1905
  }
2109
1906
  else {
2110
1907
  const fabricInfo = commissioningServer.getCommissionedFabricInformation();
@@ -2282,7 +2079,7 @@ export class Matterbridge extends EventEmitter {
2282
2079
  }
2283
2080
  }
2284
2081
  /**
2285
- * Sanitizes the fabric information by converting bigint properties to string cause res..
2082
+ * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
2286
2083
  *
2287
2084
  * @param fabricInfo - The array of exposed fabric information objects.
2288
2085
  * @returns An array of sanitized exposed fabric information objects.
@@ -2414,10 +2211,9 @@ export class Matterbridge extends EventEmitter {
2414
2211
  };
2415
2212
  /**
2416
2213
  * Retrieves the base registered plugins sanitized for res.json().
2417
- * @param {boolean} includeAll - Whether to include all information for each plugin.
2418
2214
  * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
2419
2215
  */
2420
- async getBaseRegisteredPlugins(includeAll = false) {
2216
+ async getBaseRegisteredPlugins() {
2421
2217
  const baseRegisteredPlugins = [];
2422
2218
  for (const plugin of this.plugins) {
2423
2219
  baseRegisteredPlugins.push({
@@ -2436,23 +2232,23 @@ export class Matterbridge extends EventEmitter {
2436
2232
  configured: plugin.configured,
2437
2233
  paired: plugin.paired,
2438
2234
  connected: plugin.connected,
2439
- fabricInformations: includeAll ? plugin.fabricInformations : undefined,
2440
- sessionInformations: includeAll ? plugin.sessionInformations : undefined,
2235
+ fabricInformations: plugin.fabricInformations,
2236
+ sessionInformations: plugin.sessionInformations,
2441
2237
  registeredDevices: plugin.registeredDevices,
2442
2238
  addedDevices: plugin.addedDevices,
2443
2239
  qrPairingCode: plugin.qrPairingCode,
2444
2240
  manualPairingCode: plugin.manualPairingCode,
2445
- configJson: includeAll ? plugin.configJson : undefined,
2446
- schemaJson: includeAll ? plugin.schemaJson : undefined,
2241
+ configJson: plugin.configJson,
2242
+ schemaJson: plugin.schemaJson,
2447
2243
  });
2448
2244
  }
2449
2245
  return baseRegisteredPlugins;
2450
2246
  }
2451
2247
  /**
2452
2248
  * Spawns a child process with the given command and arguments.
2453
- * @param command - The command to execute.
2454
- * @param args - The arguments to pass to the command (default: []).
2455
- * @returns A promise that resolves when the child process exits successfully, or rejects if there is an error.
2249
+ * @param {string} command - The command to execute.
2250
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2251
+ * @returns {Promise<void>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2456
2252
  */
2457
2253
  async spawnCommand(command, args = []) {
2458
2254
  /*
@@ -2462,7 +2258,13 @@ export class Matterbridge extends EventEmitter {
2462
2258
  process.on('unhandledRejection', (reason, promise) => {
2463
2259
  this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2464
2260
  });
2261
+
2262
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2263
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2264
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2265
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2465
2266
  */
2267
+ const cmdLine = command + ' ' + args.join(' ');
2466
2268
  if (process.platform === 'win32' && command === 'npm') {
2467
2269
  // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2468
2270
  const argstring = 'npm ' + args.join(' ');
@@ -2482,9 +2284,11 @@ export class Matterbridge extends EventEmitter {
2482
2284
  this.log.error(`Failed to start child process: ${err.message}`);
2483
2285
  reject(err); // Reject the promise on error
2484
2286
  });
2485
- childProcess.on('close', (code) => {
2287
+ childProcess.on('close', (code, signal) => {
2288
+ this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
2486
2289
  if (code === 0) {
2487
- this.log.debug(`Child process stdio streams have closed with code ${code}`);
2290
+ if (cmdLine.startsWith('npm install -g'))
2291
+ this.log.notice(`${cmdLine.replace('npm install -g ', '')} installed correctly`);
2488
2292
  resolve();
2489
2293
  }
2490
2294
  else {
@@ -2493,8 +2297,8 @@ export class Matterbridge extends EventEmitter {
2493
2297
  }
2494
2298
  });
2495
2299
  childProcess.on('exit', (code, signal) => {
2300
+ this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
2496
2301
  if (code === 0) {
2497
- this.log.debug(`Child process exited with code ${code} and signal ${signal}`);
2498
2302
  resolve();
2499
2303
  }
2500
2304
  else {
@@ -2509,15 +2313,13 @@ export class Matterbridge extends EventEmitter {
2509
2313
  if (childProcess.stdout) {
2510
2314
  childProcess.stdout.on('data', (data) => {
2511
2315
  const message = data.toString().trim();
2512
- // this.log.info('\n' + message);
2513
- this.wssSendMessage('Matterbridge:spawn', 'spawn', message);
2316
+ this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
2514
2317
  });
2515
2318
  }
2516
2319
  if (childProcess.stderr) {
2517
2320
  childProcess.stderr.on('data', (data) => {
2518
2321
  const message = data.toString().trim();
2519
- // this.log.debug('\n' + message);
2520
- this.wssSendMessage('Matterbridge:spawn', 'spawn', message);
2322
+ this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
2521
2323
  });
2522
2324
  }
2523
2325
  });
@@ -2525,20 +2327,45 @@ export class Matterbridge extends EventEmitter {
2525
2327
  /**
2526
2328
  * Sends a WebSocket message to all connected clients.
2527
2329
  *
2528
- * @param {string} type - The type of the message: Matterbridge, Plugin, Device, ...
2529
- * @param {string} subType - The subtype of the message: debug info warn error ....
2330
+ * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2331
+ * @param {string} time - The time string of the message
2332
+ * @param {string} name - The logger name of the message
2530
2333
  * @param {string} message - The content of the message.
2531
2334
  */
2532
- wssSendMessage(type, subType, message) {
2335
+ wssSendMessage(level, time, name, message) {
2336
+ if (!level || !time || !name || !message)
2337
+ return;
2533
2338
  // Remove ANSI escape codes from the message
2534
2339
  // eslint-disable-next-line no-control-regex
2535
- const cleanMessage = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2340
+ message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2536
2341
  // Remove leading asterisks from the message
2537
- const finalMessage = cleanMessage.replace(/^\*+/, '');
2342
+ message = message.replace(/^\*+/, '');
2343
+ // Replace all occurrences of \t and \n
2344
+ message = message.replace(/[\t\n]/g, '');
2345
+ // Remove non-printable characters
2346
+ // eslint-disable-next-line no-control-regex
2347
+ message = message.replace(/[\x00-\x1F\x7F]/g, '');
2348
+ // Replace all occurrences of \" with "
2349
+ message = message.replace(/\\"/g, '"');
2350
+ // Define the maximum allowed length for continuous characters without a space
2351
+ const maxContinuousLength = 100;
2352
+ const keepStartLength = 20;
2353
+ const keepEndLength = 20;
2354
+ // Split the message into words
2355
+ message = message
2356
+ .split(' ')
2357
+ .map((word) => {
2358
+ // If the word length exceeds the max continuous length, insert spaces and truncate
2359
+ if (word.length > maxContinuousLength) {
2360
+ return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2361
+ }
2362
+ return word;
2363
+ })
2364
+ .join(' ');
2538
2365
  // Send the message to all connected clients
2539
2366
  this.webSocketServer?.clients.forEach((client) => {
2540
2367
  if (client.readyState === WebSocket.OPEN) {
2541
- client.send(JSON.stringify({ type, subType, message: finalMessage }));
2368
+ client.send(JSON.stringify({ level, time, name, message }));
2542
2369
  }
2543
2370
  });
2544
2371
  }
@@ -2639,17 +2466,17 @@ export class Matterbridge extends EventEmitter {
2639
2466
  this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
2640
2467
  this.webSocketServer.on('connection', (ws, request) => {
2641
2468
  const clientIp = request.socket.remoteAddress;
2642
- this.log.info(`WebSocketServer client ${clientIp} connected`);
2643
- this.log.setGlobalCallback(this.wssSendMessage.bind(this));
2469
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
2644
2470
  this.log.debug('WebSocketServer logger global callback added');
2645
- this.wssSendMessage('Matterbridge', 'info', `WebSocketServer client ${clientIp} connected to Matterbridge`);
2471
+ this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
2472
+ // this.wssSendMessage('info', this.log.now(), 'Matterbridge', `WebSocketServer client "${clientIp}" connected to Matterbridge`);
2646
2473
  ws.on('message', (message) => {
2647
2474
  this.log.debug(`WebSocket client message: ${message}`);
2648
2475
  });
2649
2476
  ws.on('close', () => {
2650
2477
  this.log.info('WebSocket client disconnected');
2651
2478
  if (this.webSocketServer?.clients.size === 0) {
2652
- this.log.setGlobalCallback(undefined);
2479
+ AnsiLogger.setGlobalCallback(undefined);
2653
2480
  this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
2654
2481
  }
2655
2482
  });
@@ -2685,6 +2512,7 @@ export class Matterbridge extends EventEmitter {
2685
2512
  this.log.warn('/api/login error wrong password');
2686
2513
  res.json({ valid: false });
2687
2514
  }
2515
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2688
2516
  }
2689
2517
  catch (error) {
2690
2518
  this.log.error('/api/login error getting password');
@@ -2704,6 +2532,7 @@ export class Matterbridge extends EventEmitter {
2704
2532
  try {
2705
2533
  qrPairingCode = await this.matterbridgeContext.get('qrPairingCode');
2706
2534
  manualPairingCode = await this.matterbridgeContext.get('manualPairingCode');
2535
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2707
2536
  }
2708
2537
  catch (error) {
2709
2538
  if (this.bridgeMode === 'bridge')
@@ -2711,26 +2540,28 @@ export class Matterbridge extends EventEmitter {
2711
2540
  }
2712
2541
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
2713
2542
  this.matterbridgeInformation.restartMode = this.restartMode;
2714
- this.matterbridgeInformation.debugEnabled = this.debugEnabled;
2543
+ this.matterbridgeInformation.loggerLevel = this.log.logLevel;
2715
2544
  this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
2716
2545
  this.matterbridgeInformation.matterbridgePaired = this.matterbridgePaired;
2717
2546
  this.matterbridgeInformation.matterbridgeConnected = this.matterbridgeConnected;
2718
2547
  this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
2719
2548
  this.matterbridgeInformation.matterbridgeSessionInformations = this.matterbridgeSessionInformations;
2720
- const response = { wssHost, qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2549
+ if (this.profile)
2550
+ this.matterbridgeInformation.profile = this.profile;
2551
+ const response = { wssHost, ssl: hasParameter('ssl'), qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2721
2552
  // this.log.debug('Response:', debugStringify(response));
2722
- this.log.debug(`WebSocketServer logger local callback: ${this.log.getCallback() ? 'active' : 'inactive'}`);
2723
- this.log.debug(`WebSocketServer logger global callback: ${this.log.getGlobalCallback() ? 'active' : 'inactive'}`);
2724
- if (this.webSocketServer && this.webSocketServer.clients.size > 0 && !this.log.getGlobalCallback()) {
2725
- this.log.setGlobalCallback(this.wssSendMessage.bind(this));
2726
- this.log.debug('WebSocketServer logger global callback added');
2553
+ /*
2554
+ if (this.webSocketServer && this.webSocketServer.clients.size > 0 && !AnsiLogger.getGlobalCallback()) {
2555
+ AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), LogLevel.DEBUG);
2556
+ this.log.debug('WebSocketServer logger global callback added');
2727
2557
  }
2558
+ */
2728
2559
  res.json(response);
2729
2560
  });
2730
2561
  // Endpoint to provide plugins
2731
2562
  this.expressApp.get('/api/plugins', async (req, res) => {
2732
2563
  this.log.debug('The frontend sent /api/plugins');
2733
- const response = await this.getBaseRegisteredPlugins(true);
2564
+ const response = await this.getBaseRegisteredPlugins();
2734
2565
  // this.log.debug('Response:', debugStringify(response));
2735
2566
  res.json(response);
2736
2567
  });
@@ -2837,6 +2668,39 @@ export class Matterbridge extends EventEmitter {
2837
2668
  });
2838
2669
  res.json(data);
2839
2670
  });
2671
+ // Endpoint to send the log
2672
+ this.expressApp.get('/api/view-log', async (req, res) => {
2673
+ this.log.debug('The frontend sent /api/log');
2674
+ try {
2675
+ const data = await fs.readFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'utf8');
2676
+ res.type('text/plain');
2677
+ res.send(data);
2678
+ }
2679
+ catch (error) {
2680
+ this.log.error(`Error reading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
2681
+ res.status(500).send('Error reading log file');
2682
+ }
2683
+ });
2684
+ // Endpoint to download the log
2685
+ this.expressApp.get('/api/download-mblog', (req, res) => {
2686
+ this.log.debug('The frontend sent /api/download-mblog');
2687
+ res.download(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'matterbridge.log', (error) => {
2688
+ if (error) {
2689
+ this.log.error(`Error downloading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
2690
+ res.status(500).send('Error downloading the matterbridge log file');
2691
+ }
2692
+ });
2693
+ });
2694
+ // Endpoint to download the log
2695
+ this.expressApp.get('/api/download-mjlog', (req, res) => {
2696
+ this.log.debug('The frontend sent /api/download-mjlog');
2697
+ res.download(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'matter.log', (error) => {
2698
+ if (error) {
2699
+ this.log.error(`Error downloading log file ${this.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
2700
+ res.status(500).send('Error downloading the matter log file');
2701
+ }
2702
+ });
2703
+ });
2840
2704
  // Endpoint to receive commands
2841
2705
  this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
2842
2706
  const command = req.params.command;
@@ -2859,12 +2723,31 @@ export class Matterbridge extends EventEmitter {
2859
2723
  if (command === 'setmbloglevel') {
2860
2724
  this.log.debug('Matterbridge log level:', param);
2861
2725
  if (param === 'Debug') {
2862
- this.log.setLogDebug(true);
2863
- this.debugEnabled = true;
2726
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
2864
2727
  }
2865
2728
  else if (param === 'Info') {
2866
- this.log.setLogDebug(false);
2867
- this.debugEnabled = false;
2729
+ this.log.logLevel = "info" /* LogLevel.INFO */;
2730
+ }
2731
+ else if (param === 'Notice') {
2732
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
2733
+ }
2734
+ else if (param === 'Warn') {
2735
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
2736
+ }
2737
+ else if (param === 'Error') {
2738
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
2739
+ }
2740
+ else if (param === 'Fatal') {
2741
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
2742
+ }
2743
+ await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
2744
+ MatterbridgeDevice.logLevel = this.log.logLevel;
2745
+ this.plugins.logLevel = this.log.logLevel;
2746
+ for (const plugin of this.plugins) {
2747
+ if (!plugin.platform || !plugin.platform.config)
2748
+ continue;
2749
+ plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
2750
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
2868
2751
  }
2869
2752
  res.json({ message: 'Command received' });
2870
2753
  return;
@@ -2890,6 +2773,7 @@ export class Matterbridge extends EventEmitter {
2890
2773
  else if (param === 'Fatal') {
2891
2774
  Logger.defaultLogLevel = Level.FATAL;
2892
2775
  }
2776
+ await this.nodeContext?.set('matterLogLevel', Logger.defaultLogLevel);
2893
2777
  res.json({ message: 'Command received' });
2894
2778
  return;
2895
2779
  }
@@ -2929,6 +2813,7 @@ export class Matterbridge extends EventEmitter {
2929
2813
  try {
2930
2814
  await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2931
2815
  this.log.info('Matterbridge has been updated. Full restart required.');
2816
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2932
2817
  }
2933
2818
  catch (error) {
2934
2819
  this.log.error('Error updating matterbridge');
@@ -2961,6 +2846,7 @@ export class Matterbridge extends EventEmitter {
2961
2846
  try {
2962
2847
  await this.spawnCommand('npm', ['install', '-g', param]);
2963
2848
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
2849
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2964
2850
  }
2965
2851
  catch (error) {
2966
2852
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
@@ -2975,12 +2861,6 @@ export class Matterbridge extends EventEmitter {
2975
2861
  const plugin = await this.plugins.add(param);
2976
2862
  if (plugin) {
2977
2863
  this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
2978
- /*
2979
- plugin.platform = await this.plugins.load(plugin, false, 'The plugin has been added');
2980
- if (plugin.platform) {
2981
- await this.plugins.start(plugin, 'The plugin has been added', true);
2982
- }
2983
- */
2984
2864
  }
2985
2865
  res.json({ message: 'Command received' });
2986
2866
  return;
@@ -3017,12 +2897,6 @@ export class Matterbridge extends EventEmitter {
3017
2897
  plugin.addedDevices = undefined;
3018
2898
  await this.plugins.enable(param);
3019
2899
  this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
3020
- /*
3021
- plugin.platform = await this.plugins.load(plugin, false, 'The plugin has been enabled');
3022
- if (plugin.platform) {
3023
- await this.plugins.start(plugin, 'The plugin has been enabled', true);
3024
- }
3025
- */
3026
2900
  }
3027
2901
  }
3028
2902
  res.json({ message: 'Command received' });
@@ -3053,9 +2927,9 @@ export class Matterbridge extends EventEmitter {
3053
2927
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
3054
2928
  }
3055
2929
  /**
3056
- * Retrieves the cluster text from a given device.
3057
- * @param device - The MatterbridgeDevice object.
3058
- * @returns The attributes of the cluster servers in the device.
2930
+ * Retrieves the cluster text description from a given device.
2931
+ * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
2932
+ * @returns {string} The attributes description of the cluster servers in the device.
3059
2933
  */
3060
2934
  getClusterTextFromDevice(device) {
3061
2935
  const stringifyFixedLabel = (endpoint) => {
@@ -3072,41 +2946,54 @@ export class Matterbridge extends EventEmitter {
3072
2946
  // this.log.debug(`getClusterTextFromDevice: ${device.name}`);
3073
2947
  const clusterServers = device.getAllClusterServers();
3074
2948
  clusterServers.forEach((clusterServer) => {
3075
- // this.log.debug(`***--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3076
- if (clusterServer.name === 'OnOff')
3077
- attributes += `OnOff: ${clusterServer.getOnOffAttribute()} `;
3078
- if (clusterServer.name === 'Switch')
3079
- attributes += `Position: ${clusterServer.getCurrentPositionAttribute()} `;
3080
- if (clusterServer.name === 'WindowCovering')
3081
- attributes += `Cover position: ${clusterServer.attributes.currentPositionLiftPercent100ths.getLocal() / 100}% `;
3082
- if (clusterServer.name === 'DoorLock')
3083
- attributes += `State: ${clusterServer.attributes.lockState.getLocal() === 1 ? 'Locked' : 'Not locked'} `;
3084
- if (clusterServer.name === 'Thermostat')
3085
- attributes += `Temperature: ${clusterServer.attributes.localTemperature.getLocal() / 100}°C `;
3086
- if (clusterServer.name === 'LevelControl')
3087
- attributes += `Level: ${clusterServer.getCurrentLevelAttribute()}% `;
3088
- if (clusterServer.name === 'ColorControl')
3089
- attributes += `Hue: ${Math.round(clusterServer.getCurrentHueAttribute())} Saturation: ${Math.round(clusterServer.getCurrentSaturationAttribute())}% `;
3090
- if (clusterServer.name === 'BooleanState')
3091
- attributes += `Contact: ${clusterServer.getStateValueAttribute()} `;
3092
- if (clusterServer.name === 'OccupancySensing')
3093
- attributes += `Occupancy: ${clusterServer.getOccupancyAttribute().occupied} `;
3094
- if (clusterServer.name === 'IlluminanceMeasurement')
3095
- attributes += `Illuminance: ${clusterServer.getMeasuredValueAttribute()} `;
3096
- if (clusterServer.name === 'AirQuality')
3097
- attributes += `Air quality: ${clusterServer.getAirQualityAttribute()} `;
3098
- if (clusterServer.name === 'TvocMeasurement')
3099
- attributes += `Voc: ${clusterServer.getMeasuredValueAttribute()} `;
3100
- if (clusterServer.name === 'TemperatureMeasurement')
3101
- attributes += `Temperature: ${clusterServer.getMeasuredValueAttribute() / 100}°C `;
3102
- if (clusterServer.name === 'RelativeHumidityMeasurement')
3103
- attributes += `Humidity: ${clusterServer.getMeasuredValueAttribute() / 100}% `;
3104
- if (clusterServer.name === 'PressureMeasurement')
3105
- attributes += `Pressure: ${clusterServer.getMeasuredValueAttribute()} `;
3106
- if (clusterServer.name === 'FlowMeasurement')
3107
- attributes += `Flow: ${clusterServer.getMeasuredValueAttribute()} `;
3108
- if (clusterServer.name === 'FixedLabel')
3109
- attributes += `${stringifyFixedLabel(device)} `;
2949
+ try {
2950
+ // this.log.debug(`***--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
2951
+ if (clusterServer.name === 'OnOff')
2952
+ attributes += `OnOff: ${clusterServer.getOnOffAttribute()} `;
2953
+ if (clusterServer.name === 'Switch')
2954
+ attributes += `Position: ${clusterServer.getCurrentPositionAttribute()} `;
2955
+ if (clusterServer.name === 'WindowCovering')
2956
+ attributes += `Cover position: ${clusterServer.attributes.currentPositionLiftPercent100ths.getLocal() / 100}% `;
2957
+ if (clusterServer.name === 'DoorLock')
2958
+ attributes += `State: ${clusterServer.attributes.lockState.getLocal() === 1 ? 'Locked' : 'Not locked'} `;
2959
+ if (clusterServer.name === 'Thermostat')
2960
+ attributes += `Temperature: ${clusterServer.attributes.localTemperature.getLocal() / 100}°C `;
2961
+ if (clusterServer.name === 'LevelControl')
2962
+ attributes += `Level: ${clusterServer.getCurrentLevelAttribute()}% `;
2963
+ if (clusterServer.name === 'ColorControl' && clusterServer.isAttributeSupportedByName('currentHue'))
2964
+ attributes += `Hue: ${Math.round(clusterServer.getCurrentHueAttribute())} Saturation: ${Math.round(clusterServer.getCurrentSaturationAttribute())}% `;
2965
+ if (clusterServer.name === 'ColorControl' && clusterServer.isAttributeSupportedByName('colorTemperatureMireds'))
2966
+ attributes += `ColorTemp: ${Math.round(clusterServer.getColorTemperatureMiredsAttribute())} `;
2967
+ if (clusterServer.name === 'BooleanState')
2968
+ attributes += `Contact: ${clusterServer.getStateValueAttribute()} `;
2969
+ if (clusterServer.name === 'BooleanStateConfiguration' && clusterServer.isAttributeSupportedByName('alarmsActive'))
2970
+ attributes += `Active alarms: ${stringify(clusterServer.getAlarmsActiveAttribute())} `;
2971
+ if (clusterServer.name === 'FanControl')
2972
+ attributes += `Mode: ${clusterServer.getFanModeAttribute()} Speed: ${clusterServer.getPercentCurrentAttribute()} `;
2973
+ if (clusterServer.name === 'FanControl' && clusterServer.isAttributeSupportedByName('speedCurrent'))
2974
+ attributes += `MultiSpeed: ${clusterServer.getSpeedCurrentAttribute()} `;
2975
+ if (clusterServer.name === 'OccupancySensing')
2976
+ attributes += `Occupancy: ${clusterServer.getOccupancyAttribute().occupied} `;
2977
+ if (clusterServer.name === 'IlluminanceMeasurement')
2978
+ attributes += `Illuminance: ${clusterServer.getMeasuredValueAttribute()} `;
2979
+ if (clusterServer.name === 'AirQuality')
2980
+ attributes += `Air quality: ${clusterServer.getAirQualityAttribute()} `;
2981
+ if (clusterServer.name === 'TvocMeasurement')
2982
+ attributes += `Voc: ${clusterServer.getMeasuredValueAttribute()} `;
2983
+ if (clusterServer.name === 'TemperatureMeasurement')
2984
+ attributes += `Temperature: ${clusterServer.getMeasuredValueAttribute() / 100}°C `;
2985
+ if (clusterServer.name === 'RelativeHumidityMeasurement')
2986
+ attributes += `Humidity: ${clusterServer.getMeasuredValueAttribute() / 100}% `;
2987
+ if (clusterServer.name === 'PressureMeasurement')
2988
+ attributes += `Pressure: ${clusterServer.getMeasuredValueAttribute()} `;
2989
+ if (clusterServer.name === 'FlowMeasurement')
2990
+ attributes += `Flow: ${clusterServer.getMeasuredValueAttribute()} `;
2991
+ if (clusterServer.name === 'FixedLabel')
2992
+ attributes += `${stringifyFixedLabel(device)} `;
2993
+ }
2994
+ catch (error) {
2995
+ this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
2996
+ }
3110
2997
  });
3111
2998
  return attributes;
3112
2999
  }
@@ -3116,14 +3003,13 @@ export class Matterbridge extends EventEmitter {
3116
3003
  *
3117
3004
  * @returns A Promise that resolves when the initialization is complete.
3118
3005
  */
3119
- async startExtension(dataPath, debugEnabled, extensionVersion, port = 5560) {
3006
+ async startExtension(dataPath, extensionVersion, port = 5540) {
3120
3007
  // Set the bridge mode
3121
3008
  this.bridgeMode = 'bridge';
3122
3009
  // Set the first port to use
3123
3010
  this.port = port;
3124
3011
  // Set Matterbridge logger
3125
- this.debugEnabled = debugEnabled;
3126
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled });
3012
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
3127
3013
  this.log.debug('Matterbridge extension is starting...');
3128
3014
  // Initialize NodeStorage
3129
3015
  this.matterbridgeDirectory = dataPath;
@@ -3148,10 +3034,10 @@ export class Matterbridge extends EventEmitter {
3148
3034
  await this.logNodeAndSystemInfo();
3149
3035
  this.matterbridgeDirectory = dataPath;
3150
3036
  // Set matter.js logger level and format
3151
- Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
3037
+ Logger.defaultLogLevel = Level.INFO;
3152
3038
  Logger.format = Format.ANSI;
3153
3039
  // Start the storage and create matterbridgeContext
3154
- await this.startStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
3040
+ await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
3155
3041
  if (!this.storageManager)
3156
3042
  return false;
3157
3043
  this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge zigbee2MQTT', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'zigbee2MQTT Matter extension');
@@ -3195,7 +3081,7 @@ export class Matterbridge extends EventEmitter {
3195
3081
  // Clearing the session manager
3196
3082
  // this.matterbridgeContext?.createContext('SessionManager').clear();
3197
3083
  // Closing storage
3198
- await this.stopStorage();
3084
+ await this.stopMatterStorage();
3199
3085
  this.log.info('Matter server stopped');
3200
3086
  }
3201
3087
  /**