matterbridge 2.1.5-dev.7 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +4 -2
  2. package/README-DOCKER.md +6 -0
  3. package/dist/cli.d.ts +25 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +26 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/cluster/export.d.ts +2 -0
  8. package/dist/cluster/export.d.ts.map +1 -0
  9. package/dist/cluster/export.js +2 -0
  10. package/dist/cluster/export.js.map +1 -0
  11. package/dist/defaultConfigSchema.d.ts +27 -0
  12. package/dist/defaultConfigSchema.d.ts.map +1 -0
  13. package/dist/defaultConfigSchema.js +23 -0
  14. package/dist/defaultConfigSchema.js.map +1 -0
  15. package/dist/deviceManager.d.ts +114 -0
  16. package/dist/deviceManager.d.ts.map +1 -0
  17. package/dist/deviceManager.js +94 -1
  18. package/dist/deviceManager.js.map +1 -0
  19. package/dist/frontend.d.ts +143 -0
  20. package/dist/frontend.d.ts.map +1 -0
  21. package/dist/frontend.js +270 -24
  22. package/dist/frontend.js.map +1 -0
  23. package/dist/index.d.ts +35 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +28 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/logger/export.d.ts +2 -0
  28. package/dist/logger/export.d.ts.map +1 -0
  29. package/dist/logger/export.js +1 -0
  30. package/dist/logger/export.js.map +1 -0
  31. package/dist/matter/behaviors.d.ts +2 -0
  32. package/dist/matter/behaviors.d.ts.map +1 -0
  33. package/dist/matter/behaviors.js +2 -0
  34. package/dist/matter/behaviors.js.map +1 -0
  35. package/dist/matter/clusters.d.ts +2 -0
  36. package/dist/matter/clusters.d.ts.map +1 -0
  37. package/dist/matter/clusters.js +2 -0
  38. package/dist/matter/clusters.js.map +1 -0
  39. package/dist/matter/devices.d.ts +2 -0
  40. package/dist/matter/devices.d.ts.map +1 -0
  41. package/dist/matter/devices.js +2 -0
  42. package/dist/matter/devices.js.map +1 -0
  43. package/dist/matter/endpoints.d.ts +2 -0
  44. package/dist/matter/endpoints.d.ts.map +1 -0
  45. package/dist/matter/endpoints.js +2 -0
  46. package/dist/matter/endpoints.js.map +1 -0
  47. package/dist/matter/export.d.ts +5 -0
  48. package/dist/matter/export.d.ts.map +1 -0
  49. package/dist/matter/export.js +2 -0
  50. package/dist/matter/export.js.map +1 -0
  51. package/dist/matter/types.d.ts +3 -0
  52. package/dist/matter/types.d.ts.map +1 -0
  53. package/dist/matter/types.js +2 -0
  54. package/dist/matter/types.js.map +1 -0
  55. package/dist/matterbridge.d.ts +409 -0
  56. package/dist/matterbridge.d.ts.map +1 -0
  57. package/dist/matterbridge.js +750 -41
  58. package/dist/matterbridge.js.map +1 -0
  59. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  60. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  61. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  62. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  63. package/dist/matterbridgeBehaviors.d.ts +1056 -0
  64. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  65. package/dist/matterbridgeBehaviors.js +32 -1
  66. package/dist/matterbridgeBehaviors.js.map +1 -0
  67. package/dist/matterbridgeDeviceTypes.d.ts +177 -0
  68. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  69. package/dist/matterbridgeDeviceTypes.js +112 -11
  70. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  71. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  72. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  73. package/dist/matterbridgeDynamicPlatform.js +33 -0
  74. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  75. package/dist/matterbridgeEndpoint.d.ts +835 -0
  76. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  77. package/dist/matterbridgeEndpoint.js +690 -6
  78. package/dist/matterbridgeEndpoint.js.map +1 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
  80. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  81. package/dist/matterbridgeEndpointHelpers.js +117 -9
  82. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  83. package/dist/matterbridgePlatform.d.ts +159 -0
  84. package/dist/matterbridgePlatform.d.ts.map +1 -0
  85. package/dist/matterbridgePlatform.js +121 -5
  86. package/dist/matterbridgePlatform.js.map +1 -0
  87. package/dist/matterbridgeTypes.d.ts +169 -0
  88. package/dist/matterbridgeTypes.d.ts.map +1 -0
  89. package/dist/matterbridgeTypes.js +24 -0
  90. package/dist/matterbridgeTypes.js.map +1 -0
  91. package/dist/pluginManager.d.ts +236 -0
  92. package/dist/pluginManager.d.ts.map +1 -0
  93. package/dist/pluginManager.js +231 -4
  94. package/dist/pluginManager.js.map +1 -0
  95. package/dist/storage/export.d.ts +2 -0
  96. package/dist/storage/export.d.ts.map +1 -0
  97. package/dist/storage/export.js +1 -0
  98. package/dist/storage/export.js.map +1 -0
  99. package/dist/utils/colorUtils.d.ts +61 -0
  100. package/dist/utils/colorUtils.d.ts.map +1 -0
  101. package/dist/utils/colorUtils.js +205 -2
  102. package/dist/utils/colorUtils.js.map +1 -0
  103. package/dist/utils/export.d.ts +3 -0
  104. package/dist/utils/export.d.ts.map +1 -0
  105. package/dist/utils/export.js +1 -0
  106. package/dist/utils/export.js.map +1 -0
  107. package/dist/utils/utils.d.ts +231 -0
  108. package/dist/utils/utils.d.ts.map +1 -0
  109. package/dist/utils/utils.js +264 -10
  110. package/dist/utils/utils.js.map +1 -0
  111. package/npm-shrinkwrap.json +2 -2
  112. package/package.json +2 -1
@@ -1,3 +1,26 @@
1
+ /**
2
+ * This file contains the class Matterbridge.
3
+ *
4
+ * @file matterbridge.ts
5
+ * @author Luca Liguori
6
+ * @date 2023-12-29
7
+ * @version 1.5.2
8
+ *
9
+ * Copyright 2023, 2024, 2025 Luca Liguori.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License. *
22
+ */
23
+ // Node.js modules
1
24
  import { fileURLToPath } from 'url';
2
25
  import { promises as fs } from 'fs';
3
26
  import { exec, spawn } from 'child_process';
@@ -5,20 +28,27 @@ import EventEmitter from 'events';
5
28
  import os from 'os';
6
29
  import path from 'path';
7
30
  import { randomBytes } from 'crypto';
31
+ // NodeStorage and AnsiLogger modules
8
32
  import { NodeStorageManager } from './storage/export.js';
9
33
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from './logger/export.js';
34
+ // Matterbridge
10
35
  import { logInterfaces, copyDirectory, getParameter, getIntParameter, hasParameter, getNpmPackageVersion } from './utils/utils.js';
11
36
  import { PluginManager } from './pluginManager.js';
12
37
  import { DeviceManager } from './deviceManager.js';
13
38
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
14
39
  import { bridge } from './matterbridgeDeviceTypes.js';
15
40
  import { Frontend } from './frontend.js';
41
+ // @matter
16
42
  import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
17
43
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
18
44
  import { AggregatorEndpoint } from '@matter/main/endpoints';
45
+ // Default colors
19
46
  const plg = '\u001B[38;5;33m';
20
47
  const dev = '\u001B[38;5;79m';
21
48
  const typ = '\u001B[38;5;207m';
49
+ /**
50
+ * Represents the Matterbridge application.
51
+ */
22
52
  export class Matterbridge extends EventEmitter {
23
53
  systemInformation = {
24
54
  interfaceName: '',
@@ -57,7 +87,7 @@ export class Matterbridge extends EventEmitter {
57
87
  restartMode: '',
58
88
  readOnly: hasParameter('readonly'),
59
89
  profile: getParameter('profile'),
60
- loggerLevel: "info",
90
+ loggerLevel: "info" /* LogLevel.INFO */,
61
91
  fileLogger: false,
62
92
  matterLoggerLevel: MatterLogLevel.INFO,
63
93
  matterFileLogger: false,
@@ -92,9 +122,11 @@ export class Matterbridge extends EventEmitter {
92
122
  plugins;
93
123
  devices;
94
124
  frontend = new Frontend(this);
125
+ // Matterbridge storage
95
126
  nodeStorage;
96
127
  nodeContext;
97
128
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
129
+ // Cleanup
98
130
  hasCleanupStarted = false;
99
131
  initialized = false;
100
132
  execRunningCount = 0;
@@ -106,34 +138,57 @@ export class Matterbridge extends EventEmitter {
106
138
  sigtermHandler;
107
139
  exceptionHandler;
108
140
  rejectionHandler;
141
+ // Matter environment
109
142
  environment = Environment.default;
143
+ // Matter storage
110
144
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
111
145
  matterStorageService;
112
146
  matterStorageManager;
113
147
  matterbridgeContext;
114
148
  mattercontrollerContext;
115
- mdnsInterface;
116
- ipv4address;
117
- ipv6address;
118
- port;
119
- passcode;
120
- discriminator;
149
+ // Matter parameters
150
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
151
+ ipv4address; // matter server node listeningAddressIpv4
152
+ ipv6address; // matter server node listeningAddressIpv6
153
+ port; // first server node port
154
+ passcode; // first server node passcode
155
+ discriminator; // first server node discriminator
121
156
  serverNode;
122
157
  aggregatorNode;
123
158
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
124
159
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
125
160
  static instance;
161
+ // We load asyncronously so is private
126
162
  constructor() {
127
163
  super();
128
164
  }
165
+ /**
166
+ * Retrieves the list of Matterbridge devices.
167
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
168
+ */
129
169
  getDevices() {
130
170
  return this.devices.array();
131
171
  }
172
+ /**
173
+ * Retrieves the list of registered plugins.
174
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
175
+ */
132
176
  getPlugins() {
133
177
  return this.plugins.array();
134
178
  }
179
+ /** ***********************************************************************************************************************************/
180
+ /** loadInstance() and cleanup() methods */
181
+ /** ***********************************************************************************************************************************/
182
+ /**
183
+ * Loads an instance of the Matterbridge class.
184
+ * If an instance already exists, return that instance.
185
+ *
186
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
187
+ * @returns The loaded Matterbridge instance.
188
+ */
135
189
  static async loadInstance(initialize = false) {
136
190
  if (!Matterbridge.instance) {
191
+ // eslint-disable-next-line no-console
137
192
  if (hasParameter('debug'))
138
193
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
139
194
  Matterbridge.instance = new Matterbridge();
@@ -142,7 +197,13 @@ export class Matterbridge extends EventEmitter {
142
197
  }
143
198
  return Matterbridge.instance;
144
199
  }
200
+ /**
201
+ * Call cleanup().
202
+ * @deprecated This method is deprecated and is only used for jest tests.
203
+ *
204
+ */
145
205
  async destroyInstance() {
206
+ // Save server nodes to close
146
207
  const servers = [];
147
208
  if (this.bridgeMode === 'bridge') {
148
209
  if (this.serverNode)
@@ -154,54 +215,80 @@ export class Matterbridge extends EventEmitter {
154
215
  servers.push(plugin.serverNode);
155
216
  }
156
217
  }
218
+ // Cleanup
157
219
  await this.cleanup('destroying instance...', false);
220
+ // Close servers mdns service
158
221
  for (const server of servers) {
159
222
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
160
223
  this.log.info(`Closed ${server.id} MdnsService`);
161
224
  }
225
+ // Wait for the cleanup to finish
162
226
  await new Promise((resolve) => {
163
227
  setTimeout(resolve, 1000);
164
228
  });
165
229
  }
230
+ /**
231
+ * Initializes the Matterbridge application.
232
+ *
233
+ * @remarks
234
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
235
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
236
+ * node version, registers signal handlers, initializes storage, and parses the command line.
237
+ *
238
+ * @returns A Promise that resolves when the initialization is complete.
239
+ */
166
240
  async initialize() {
241
+ // Set the restart mode
167
242
  if (hasParameter('service'))
168
243
  this.restartMode = 'service';
169
244
  if (hasParameter('docker'))
170
245
  this.restartMode = 'docker';
246
+ // Set the matterbridge directory
171
247
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
172
248
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
249
+ // Setup the matter environment
173
250
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
174
251
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
175
252
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
176
253
  this.environment.vars.set('runtime.signals', false);
177
254
  this.environment.vars.set('runtime.exitcode', false);
178
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
255
+ // Create the matterbridge logger
256
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
257
+ // Register process handlers
179
258
  this.registerProcessHandlers();
259
+ // Initialize nodeStorage and nodeContext
180
260
  try {
181
261
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
182
262
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
183
263
  this.log.debug('Creating node storage context for matterbridge');
184
264
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
265
+ // TODO: Remove this code when node-persist-manager is updated
266
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
267
  const keys = (await this.nodeStorage?.storage.keys());
186
268
  for (const key of keys) {
187
269
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
270
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
188
271
  await this.nodeStorage?.storage.get(key);
189
272
  }
190
273
  const storages = await this.nodeStorage.getStorageNames();
191
274
  for (const storage of storages) {
192
275
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
193
276
  const nodeContext = await this.nodeStorage?.createStorage(storage);
277
+ // TODO: Remove this code when node-persist-manager is updated
278
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
194
279
  const keys = (await nodeContext?.storage.keys());
195
280
  keys.forEach(async (key) => {
196
281
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
197
282
  await nodeContext?.get(key);
198
283
  });
199
284
  }
285
+ // Creating a backup of the node storage since it is not corrupted
200
286
  this.log.debug('Creating node storage backup...');
201
287
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
202
288
  this.log.debug('Created node storage backup');
203
289
  }
204
290
  catch (error) {
291
+ // Restoring the backup of the node storage since it is corrupted
205
292
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
206
293
  if (hasParameter('norestore')) {
207
294
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -216,45 +303,52 @@ export class Matterbridge extends EventEmitter {
216
303
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
217
304
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
218
305
  }
306
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
219
307
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
308
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
220
309
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
310
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
221
311
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
222
312
  this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
313
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
223
314
  if (hasParameter('logger')) {
224
315
  const level = getParameter('logger');
225
316
  if (level === 'debug') {
226
- this.log.logLevel = "debug";
317
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
227
318
  }
228
319
  else if (level === 'info') {
229
- this.log.logLevel = "info";
320
+ this.log.logLevel = "info" /* LogLevel.INFO */;
230
321
  }
231
322
  else if (level === 'notice') {
232
- this.log.logLevel = "notice";
323
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
233
324
  }
234
325
  else if (level === 'warn') {
235
- this.log.logLevel = "warn";
326
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
236
327
  }
237
328
  else if (level === 'error') {
238
- this.log.logLevel = "error";
329
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
239
330
  }
240
331
  else if (level === 'fatal') {
241
- this.log.logLevel = "fatal";
332
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
242
333
  }
243
334
  else {
244
335
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
245
- this.log.logLevel = "info";
336
+ this.log.logLevel = "info" /* LogLevel.INFO */;
246
337
  }
247
338
  }
248
339
  else {
249
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
340
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
250
341
  }
251
342
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
343
+ this.matterbridgeInformation.loggerLevel = this.log.logLevel;
344
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
252
345
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
253
346
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
254
347
  this.matterbridgeInformation.fileLogger = true;
255
348
  }
256
349
  this.log.notice('Matterbridge is starting...');
257
350
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
351
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
258
352
  if (hasParameter('matterlogger')) {
259
353
  const level = getParameter('matterlogger');
260
354
  if (level === 'debug') {
@@ -285,6 +379,8 @@ export class Matterbridge extends EventEmitter {
285
379
  }
286
380
  Logger.format = MatterLogFormat.ANSI;
287
381
  Logger.setLogger('default', this.createMatterLogger());
382
+ this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
383
+ // Create the file logger for matter.js (context: matterFileLog)
288
384
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
289
385
  this.matterbridgeInformation.matterFileLogger = true;
290
386
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -293,6 +389,7 @@ export class Matterbridge extends EventEmitter {
293
389
  });
294
390
  }
295
391
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
392
+ // Set the interface to use for matter server node mdnsInterface
296
393
  if (hasParameter('mdnsinterface')) {
297
394
  this.mdnsInterface = getParameter('mdnsinterface');
298
395
  }
@@ -301,6 +398,7 @@ export class Matterbridge extends EventEmitter {
301
398
  if (this.mdnsInterface === '')
302
399
  this.mdnsInterface = undefined;
303
400
  }
401
+ // Validate mdnsInterface
304
402
  if (this.mdnsInterface) {
305
403
  const networkInterfaces = os.networkInterfaces();
306
404
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -314,6 +412,7 @@ export class Matterbridge extends EventEmitter {
314
412
  }
315
413
  if (this.mdnsInterface)
316
414
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
415
+ // Set the listeningAddressIpv4 for the matter commissioning server
317
416
  if (hasParameter('ipv4address')) {
318
417
  this.ipv4address = getParameter('ipv4address');
319
418
  }
@@ -322,6 +421,7 @@ export class Matterbridge extends EventEmitter {
322
421
  if (this.ipv4address === '')
323
422
  this.ipv4address = undefined;
324
423
  }
424
+ // Set the listeningAddressIpv6 for the matter commissioning server
325
425
  if (hasParameter('ipv6address')) {
326
426
  this.ipv6address = getParameter('ipv6address');
327
427
  }
@@ -330,14 +430,19 @@ export class Matterbridge extends EventEmitter {
330
430
  if (this.ipv6address === '')
331
431
  this.ipv6address = undefined;
332
432
  }
433
+ // Initialize PluginManager
333
434
  this.plugins = new PluginManager(this);
334
435
  await this.plugins.loadFromStorage();
335
436
  this.plugins.logLevel = this.log.logLevel;
437
+ // Initialize DeviceManager
336
438
  this.devices = new DeviceManager(this, this.nodeContext);
337
439
  this.devices.logLevel = this.log.logLevel;
440
+ // Get the plugins from node storage and create the plugins node storage contexts
338
441
  for (const plugin of this.plugins) {
339
442
  const packageJson = await this.plugins.parse(plugin);
340
443
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
444
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
445
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
341
446
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
342
447
  try {
343
448
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -359,6 +464,7 @@ export class Matterbridge extends EventEmitter {
359
464
  await plugin.nodeContext.set('description', plugin.description);
360
465
  await plugin.nodeContext.set('author', plugin.author);
361
466
  }
467
+ // Log system info and create .matterbridge directory
362
468
  await this.logNodeAndSystemInfo();
363
469
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
364
470
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -366,6 +472,7 @@ export class Matterbridge extends EventEmitter {
366
472
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
367
473
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
368
474
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
475
+ // Check node version and throw error
369
476
  const minNodeVersion = 18;
370
477
  const nodeVersion = process.versions.node;
371
478
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -373,9 +480,15 @@ export class Matterbridge extends EventEmitter {
373
480
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
374
481
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
375
482
  }
483
+ // Parse command line
376
484
  await this.parseCommandLine();
377
485
  this.initialized = true;
378
486
  }
487
+ /**
488
+ * Parses the command line arguments and performs the corresponding actions.
489
+ * @private
490
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
491
+ */
379
492
  async parseCommandLine() {
380
493
  if (hasParameter('help')) {
381
494
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -485,6 +598,7 @@ export class Matterbridge extends EventEmitter {
485
598
  await this.shutdownProcessAndFactoryReset();
486
599
  return;
487
600
  }
601
+ // Start the matter storage and create the matterbridge context
488
602
  try {
489
603
  await this.startMatterStorage();
490
604
  }
@@ -492,10 +606,12 @@ export class Matterbridge extends EventEmitter {
492
606
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
493
607
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
494
608
  }
609
+ // Clear the matterbridge context if the reset parameter is set
495
610
  if (hasParameter('reset') && getParameter('reset') === undefined) {
496
611
  await this.shutdownProcessAndReset();
497
612
  return;
498
613
  }
614
+ // Clear matterbridge plugin context if the reset parameter is set
499
615
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
500
616
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
501
617
  const plugin = this.plugins.get(getParameter('reset'));
@@ -517,13 +633,16 @@ export class Matterbridge extends EventEmitter {
517
633
  this.emit('shutdown');
518
634
  return;
519
635
  }
636
+ // Initialize frontend
520
637
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
521
638
  await this.frontend.start(getIntParameter('frontend'));
522
639
  this.frontend.logLevel = this.log.logLevel;
640
+ // Check now the latest versions of matterbridge and plugins
523
641
  this.getMatterbridgeLatestVersion();
524
642
  for (const plugin of this.plugins) {
525
643
  this.getPluginLatestVersion(plugin);
526
644
  }
645
+ // Check each 60 minutes the latest versions
527
646
  this.checkUpdateInterval = setInterval(() => {
528
647
  this.getMatterbridgeLatestVersion();
529
648
  for (const plugin of this.plugins) {
@@ -531,20 +650,24 @@ export class Matterbridge extends EventEmitter {
531
650
  }
532
651
  this.frontend.wssSendRefreshRequired();
533
652
  }, 60 * 60 * 1000);
653
+ // Start the matterbridge in mode test
534
654
  if (hasParameter('test')) {
535
655
  this.bridgeMode = 'bridge';
536
656
  MatterbridgeEndpoint.bridgeMode = 'bridge';
537
657
  return;
538
658
  }
659
+ // Start the matterbridge in mode controller
539
660
  if (hasParameter('controller')) {
540
661
  this.bridgeMode = 'controller';
541
662
  await this.startController();
542
663
  return;
543
664
  }
665
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
544
666
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
545
667
  this.log.info('Setting default matterbridge start mode to bridge');
546
668
  await this.nodeContext?.set('bridgeMode', 'bridge');
547
669
  }
670
+ // Start matterbridge in bridge mode
548
671
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
549
672
  this.bridgeMode = 'bridge';
550
673
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -552,6 +675,7 @@ export class Matterbridge extends EventEmitter {
552
675
  await this.startBridge();
553
676
  return;
554
677
  }
678
+ // Start matterbridge in childbridge mode
555
679
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
556
680
  this.bridgeMode = 'childbridge';
557
681
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -560,16 +684,28 @@ export class Matterbridge extends EventEmitter {
560
684
  return;
561
685
  }
562
686
  }
687
+ /**
688
+ * Asynchronously loads and starts the registered plugins.
689
+ *
690
+ * This method is responsible for initializing and staarting all enabled plugins.
691
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
692
+ *
693
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
694
+ */
563
695
  async startPlugins() {
696
+ // Check, load and start the plugins
564
697
  for (const plugin of this.plugins) {
565
698
  plugin.configJson = await this.plugins.loadConfig(plugin);
566
699
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
700
+ // Check if the plugin is available
567
701
  if (!(await this.plugins.resolve(plugin.path))) {
568
702
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
569
703
  plugin.enabled = false;
570
704
  plugin.error = true;
571
705
  continue;
572
706
  }
707
+ // Check if the plugin has a new version
708
+ // this.getPluginLatestVersion(plugin); // No await do it asyncronously
573
709
  if (!plugin.enabled) {
574
710
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
575
711
  continue;
@@ -583,20 +719,26 @@ export class Matterbridge extends EventEmitter {
583
719
  plugin.addedDevices = undefined;
584
720
  plugin.qrPairingCode = undefined;
585
721
  plugin.manualPairingCode = undefined;
586
- this.plugins.load(plugin, true, 'Matterbridge is starting');
722
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
587
723
  }
588
724
  this.frontend.wssSendRefreshRequired();
589
725
  }
726
+ /**
727
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
728
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
729
+ */
590
730
  registerProcessHandlers() {
591
731
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
592
732
  process.removeAllListeners('uncaughtException');
593
733
  process.removeAllListeners('unhandledRejection');
594
734
  this.exceptionHandler = async (error) => {
595
735
  this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
736
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
596
737
  };
597
738
  process.on('uncaughtException', this.exceptionHandler);
598
739
  this.rejectionHandler = async (reason, promise) => {
599
740
  this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
741
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
600
742
  };
601
743
  process.on('unhandledRejection', this.rejectionHandler);
602
744
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -609,6 +751,9 @@ export class Matterbridge extends EventEmitter {
609
751
  };
610
752
  process.on('SIGTERM', this.sigtermHandler);
611
753
  }
754
+ /**
755
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
756
+ */
612
757
  deregisterProcesslHandlers() {
613
758
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
614
759
  if (this.exceptionHandler)
@@ -625,12 +770,17 @@ export class Matterbridge extends EventEmitter {
625
770
  process.off('SIGTERM', this.sigtermHandler);
626
771
  this.sigtermHandler = undefined;
627
772
  }
773
+ /**
774
+ * Logs the node and system information.
775
+ */
628
776
  async logNodeAndSystemInfo() {
777
+ // IP address information
629
778
  const networkInterfaces = os.networkInterfaces();
630
779
  this.systemInformation.interfaceName = '';
631
780
  this.systemInformation.ipv4Address = '';
632
781
  this.systemInformation.ipv6Address = '';
633
782
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
783
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
634
784
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
635
785
  continue;
636
786
  if (!interfaceDetails) {
@@ -656,19 +806,22 @@ export class Matterbridge extends EventEmitter {
656
806
  break;
657
807
  }
658
808
  }
809
+ // Node information
659
810
  this.systemInformation.nodeVersion = process.versions.node;
660
811
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
661
812
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
662
813
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
814
+ // Host system information
663
815
  this.systemInformation.hostname = os.hostname();
664
816
  this.systemInformation.user = os.userInfo().username;
665
- this.systemInformation.osType = os.type();
666
- this.systemInformation.osRelease = os.release();
667
- this.systemInformation.osPlatform = os.platform();
668
- this.systemInformation.osArch = os.arch();
669
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
670
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
671
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
817
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
818
+ this.systemInformation.osRelease = os.release(); // Kernel version
819
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
820
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
821
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
822
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
823
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
824
+ // Log the system information
672
825
  this.log.debug('Host System Information:');
673
826
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
674
827
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -684,15 +837,19 @@ export class Matterbridge extends EventEmitter {
684
837
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
685
838
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
686
839
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
840
+ // Home directory
687
841
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
688
842
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
689
843
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
844
+ // Package root directory
690
845
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
691
846
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
692
847
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
693
848
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
849
+ // Global node_modules directory
694
850
  if (this.nodeContext)
695
851
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
852
+ // First run of Matterbridge so the node storage is empty
696
853
  if (this.globalModulesDirectory === '') {
697
854
  try {
698
855
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -706,6 +863,20 @@ export class Matterbridge extends EventEmitter {
706
863
  }
707
864
  else
708
865
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
866
+ /* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
867
+ else {
868
+ this.getGlobalNodeModules()
869
+ .then(async (globalModulesDirectory) => {
870
+ this.globalModulesDirectory = globalModulesDirectory;
871
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
872
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
873
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
874
+ })
875
+ .catch((error) => {
876
+ this.log.error(`Error getting global node_modules directory: ${error}`);
877
+ });
878
+ }*/
879
+ // Create the data directory .matterbridge in the home directory
709
880
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
710
881
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
711
882
  try {
@@ -729,6 +900,7 @@ export class Matterbridge extends EventEmitter {
729
900
  }
730
901
  }
731
902
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
903
+ // Create the plugin directory Matterbridge in the home directory
732
904
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
733
905
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
734
906
  try {
@@ -752,18 +924,28 @@ export class Matterbridge extends EventEmitter {
752
924
  }
753
925
  }
754
926
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
927
+ // Matterbridge version
755
928
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
756
929
  this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
757
930
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
758
931
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
932
+ // Matterbridge latest version
759
933
  if (this.nodeContext)
760
934
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
761
935
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
936
+ // this.getMatterbridgeLatestVersion();
937
+ // Current working directory
762
938
  const currentDir = process.cwd();
763
939
  this.log.debug(`Current Working Directory: ${currentDir}`);
940
+ // Command line arguments (excluding 'node' and the script name)
764
941
  const cmdArgs = process.argv.slice(2).join(' ');
765
942
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
766
943
  }
944
+ /**
945
+ * Retrieves the latest version of a package from the npm registry.
946
+ * @param packageName - The name of the package.
947
+ * @returns A Promise that resolves to the latest version of the package.
948
+ */
767
949
  async getLatestVersion(packageName) {
768
950
  return new Promise((resolve, reject) => {
769
951
  this.execRunningCount++;
@@ -778,6 +960,10 @@ export class Matterbridge extends EventEmitter {
778
960
  });
779
961
  });
780
962
  }
963
+ /**
964
+ * Retrieves the path to the global Node.js modules directory.
965
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
966
+ */
781
967
  async getGlobalNodeModules() {
782
968
  return new Promise((resolve, reject) => {
783
969
  this.execRunningCount++;
@@ -792,6 +978,13 @@ export class Matterbridge extends EventEmitter {
792
978
  });
793
979
  });
794
980
  }
981
+ /**
982
+ * Retrieves the latest version of Matterbridge and updates the matterbridgeLatestVersion property.
983
+ * If there is an error retrieving the latest version, logs an error message.
984
+ *
985
+ * @private
986
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved.
987
+ */
795
988
  async getMatterbridgeLatestVersion() {
796
989
  getNpmPackageVersion('matterbridge')
797
990
  .then(async (version) => {
@@ -810,6 +1003,14 @@ export class Matterbridge extends EventEmitter {
810
1003
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
811
1004
  });
812
1005
  }
1006
+ /**
1007
+ * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
1008
+ * If there is an error retrieving the latest version, logs an error message.
1009
+ *
1010
+ * @private
1011
+ * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
1012
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved.
1013
+ */
813
1014
  async getPluginLatestVersion(plugin) {
814
1015
  getNpmPackageVersion(plugin.name)
815
1016
  .then((version) => {
@@ -823,38 +1024,51 @@ export class Matterbridge extends EventEmitter {
823
1024
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
824
1025
  });
825
1026
  }
1027
+ /**
1028
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1029
+ *
1030
+ * @returns {Function} The MatterLogger function.
1031
+ */
826
1032
  createMatterLogger() {
827
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1033
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
828
1034
  return (_level, formattedLog) => {
829
1035
  const logger = formattedLog.slice(44, 44 + 20).trim();
830
1036
  const message = formattedLog.slice(65);
831
1037
  matterLogger.logName = logger;
832
1038
  switch (_level) {
833
1039
  case MatterLogLevel.DEBUG:
834
- matterLogger.log("debug", message);
1040
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
835
1041
  break;
836
1042
  case MatterLogLevel.INFO:
837
- matterLogger.log("info", message);
1043
+ matterLogger.log("info" /* LogLevel.INFO */, message);
838
1044
  break;
839
1045
  case MatterLogLevel.NOTICE:
840
- matterLogger.log("notice", message);
1046
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
841
1047
  break;
842
1048
  case MatterLogLevel.WARN:
843
- matterLogger.log("warn", message);
1049
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
844
1050
  break;
845
1051
  case MatterLogLevel.ERROR:
846
- matterLogger.log("error", message);
1052
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
847
1053
  break;
848
1054
  case MatterLogLevel.FATAL:
849
- matterLogger.log("fatal", message);
1055
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
850
1056
  break;
851
1057
  default:
852
- matterLogger.log("debug", message);
1058
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
853
1059
  break;
854
1060
  }
855
1061
  };
856
1062
  }
1063
+ /**
1064
+ * Creates a Matter File Logger.
1065
+ *
1066
+ * @param {string} filePath - The path to the log file.
1067
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1068
+ * @returns {Function} - A function that logs formatted messages to the log file.
1069
+ */
857
1070
  async createMatterFileLogger(filePath, unlink = false) {
1071
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
858
1072
  let fileSize = 0;
859
1073
  if (unlink) {
860
1074
  try {
@@ -903,12 +1117,21 @@ export class Matterbridge extends EventEmitter {
903
1117
  }
904
1118
  };
905
1119
  }
1120
+ /**
1121
+ * Restarts the process by exiting the current instance and loading a new instance.
1122
+ */
906
1123
  async restartProcess() {
907
1124
  await this.cleanup('restarting...', true);
908
1125
  }
1126
+ /**
1127
+ * Shut down the process by exiting the current process.
1128
+ */
909
1129
  async shutdownProcess() {
910
1130
  await this.cleanup('shutting down...', false);
911
1131
  }
1132
+ /**
1133
+ * Update matterbridge and and shut down the process.
1134
+ */
912
1135
  async updateProcess() {
913
1136
  this.log.info('Updating matterbridge...');
914
1137
  try {
@@ -921,46 +1144,66 @@ export class Matterbridge extends EventEmitter {
921
1144
  this.frontend.wssSendRestartRequired();
922
1145
  await this.cleanup('updating...', false);
923
1146
  }
1147
+ /**
1148
+ * Unregister all devices and shut down the process.
1149
+ */
924
1150
  async unregisterAndShutdownProcess() {
925
1151
  this.log.info('Unregistering all devices and shutting down...');
926
1152
  for (const plugin of this.plugins) {
927
1153
  await this.removeAllBridgedEndpoints(plugin.name);
928
1154
  }
929
1155
  this.log.debug('Waiting for the MessageExchange to finish...');
930
- await new Promise((resolve) => setTimeout(resolve, 1000));
1156
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
931
1157
  this.log.debug('Cleaning up and shutting down...');
932
1158
  await this.cleanup('unregistered all devices and shutting down...', false);
933
1159
  }
1160
+ /**
1161
+ * Reset commissioning and shut down the process.
1162
+ */
934
1163
  async shutdownProcessAndReset() {
935
1164
  await this.cleanup('shutting down with reset...', false);
936
1165
  }
1166
+ /**
1167
+ * Factory reset and shut down the process.
1168
+ */
937
1169
  async shutdownProcessAndFactoryReset() {
938
1170
  await this.cleanup('shutting down with factory reset...', false);
939
1171
  }
1172
+ /**
1173
+ * Cleans up the Matterbridge instance.
1174
+ * @param message - The cleanup message.
1175
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1176
+ * @returns A promise that resolves when the cleanup is completed.
1177
+ */
940
1178
  async cleanup(message, restart = false) {
941
1179
  if (this.initialized && !this.hasCleanupStarted) {
942
1180
  this.hasCleanupStarted = true;
943
1181
  this.log.info(message);
1182
+ // Clear the start matter interval
944
1183
  if (this.startMatterInterval) {
945
1184
  clearInterval(this.startMatterInterval);
946
1185
  this.startMatterInterval = undefined;
947
1186
  this.log.debug('Start matter interval cleared');
948
1187
  }
1188
+ // Clear the check update interval
949
1189
  if (this.checkUpdateInterval) {
950
1190
  clearInterval(this.checkUpdateInterval);
951
1191
  this.checkUpdateInterval = undefined;
952
1192
  this.log.debug('Check update interval cleared');
953
1193
  }
1194
+ // Clear the configure timeout
954
1195
  if (this.configureTimeout) {
955
1196
  clearTimeout(this.configureTimeout);
956
1197
  this.configureTimeout = undefined;
957
1198
  this.log.debug('Matterbridge configure timeout cleared');
958
1199
  }
1200
+ // Clear the reachability timeout
959
1201
  if (this.reachabilityTimeout) {
960
1202
  clearTimeout(this.reachabilityTimeout);
961
1203
  this.reachabilityTimeout = undefined;
962
1204
  this.log.debug('Matterbridge reachability timeout cleared');
963
1205
  }
1206
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
964
1207
  for (const plugin of this.plugins) {
965
1208
  if (!plugin.enabled || plugin.error)
966
1209
  continue;
@@ -971,9 +1214,10 @@ export class Matterbridge extends EventEmitter {
971
1214
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
972
1215
  }
973
1216
  }
1217
+ // Stopping matter server nodes
974
1218
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
975
1219
  this.log.debug('Waiting for the MessageExchange to finish...');
976
- await new Promise((resolve) => setTimeout(resolve, 1000));
1220
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
977
1221
  if (this.bridgeMode === 'bridge') {
978
1222
  if (this.serverNode) {
979
1223
  await this.stopServerNode(this.serverNode);
@@ -989,6 +1233,7 @@ export class Matterbridge extends EventEmitter {
989
1233
  }
990
1234
  }
991
1235
  this.log.notice('Stopped matter server nodes');
1236
+ // Matter commisioning reset
992
1237
  if (message === 'shutting down with reset...') {
993
1238
  this.log.info('Resetting Matterbridge commissioning information...');
994
1239
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -998,17 +1243,37 @@ export class Matterbridge extends EventEmitter {
998
1243
  await this.matterbridgeContext?.clearAll();
999
1244
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1000
1245
  }
1246
+ // Stop matter storage
1001
1247
  await this.stopMatterStorage();
1248
+ // Stop the frontend
1002
1249
  await this.frontend.stop();
1250
+ // Remove the matterfilelogger
1003
1251
  try {
1004
1252
  Logger.removeLogger('matterfilelogger');
1253
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1005
1254
  }
1006
1255
  catch (error) {
1256
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1007
1257
  }
1258
+ // Serialize registeredDevices
1008
1259
  if (this.nodeStorage && this.nodeContext) {
1260
+ /*
1261
+ TODO: Implement serialization of registered devices in edge mode
1262
+ this.log.info('Saving registered devices...');
1263
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1264
+ this.devices.forEach(async (device) => {
1265
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1266
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1267
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1268
+ });
1269
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1270
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1271
+ */
1272
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1009
1273
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1010
1274
  await this.nodeContext.close();
1011
1275
  this.nodeContext = undefined;
1276
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1012
1277
  for (const plugin of this.plugins) {
1013
1278
  if (plugin.nodeContext) {
1014
1279
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1025,8 +1290,10 @@ export class Matterbridge extends EventEmitter {
1025
1290
  }
1026
1291
  this.plugins.clear();
1027
1292
  this.devices.clear();
1293
+ // Factory reset
1028
1294
  if (message === 'shutting down with factory reset...') {
1029
1295
  try {
1296
+ // Delete old matter storage file and backup
1030
1297
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1031
1298
  this.log.info(`Unlinking old matter storage file: ${file}`);
1032
1299
  await fs.unlink(file);
@@ -1040,6 +1307,7 @@ export class Matterbridge extends EventEmitter {
1040
1307
  }
1041
1308
  }
1042
1309
  try {
1310
+ // Delete matter node storage directory with its subdirectories and backup
1043
1311
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1044
1312
  this.log.info(`Removing matter node storage directory: ${dir}`);
1045
1313
  await fs.rm(dir, { recursive: true });
@@ -1053,6 +1321,7 @@ export class Matterbridge extends EventEmitter {
1053
1321
  }
1054
1322
  }
1055
1323
  try {
1324
+ // Delete node storage directory with its subdirectories and backup
1056
1325
  const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1057
1326
  this.log.info(`Removing storage directory: ${dir}`);
1058
1327
  await fs.rm(dir, { recursive: true });
@@ -1067,12 +1336,13 @@ export class Matterbridge extends EventEmitter {
1067
1336
  }
1068
1337
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1069
1338
  }
1339
+ // Deregisters the process handlers
1070
1340
  this.deregisterProcesslHandlers();
1071
1341
  if (restart) {
1072
1342
  if (message === 'updating...') {
1073
1343
  this.log.info('Cleanup completed. Updating...');
1074
1344
  Matterbridge.instance = undefined;
1075
- this.emit('update');
1345
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1076
1346
  }
1077
1347
  else if (message === 'restarting...') {
1078
1348
  this.log.info('Cleanup completed. Restarting...');
@@ -1092,6 +1362,14 @@ export class Matterbridge extends EventEmitter {
1092
1362
  this.log.debug('Cleanup already started...');
1093
1363
  }
1094
1364
  }
1365
+ /**
1366
+ * Creates and configures the server node for an accessory plugin for a given device.
1367
+ *
1368
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1369
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1370
+ * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1371
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1372
+ */
1095
1373
  async createAccessoryPlugin(plugin, device, start = false) {
1096
1374
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1097
1375
  plugin.locked = true;
@@ -1103,6 +1381,13 @@ export class Matterbridge extends EventEmitter {
1103
1381
  await this.startServerNode(plugin.serverNode);
1104
1382
  }
1105
1383
  }
1384
+ /**
1385
+ * Creates and configures the server node for a dynamic plugin.
1386
+ *
1387
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1388
+ * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1389
+ * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1390
+ */
1106
1391
  async createDynamicPlugin(plugin, start = false) {
1107
1392
  if (!plugin.locked) {
1108
1393
  plugin.locked = true;
@@ -1114,7 +1399,13 @@ export class Matterbridge extends EventEmitter {
1114
1399
  await this.startServerNode(plugin.serverNode);
1115
1400
  }
1116
1401
  }
1402
+ /**
1403
+ * Starts the Matterbridge in bridge mode.
1404
+ * @private
1405
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1406
+ */
1117
1407
  async startBridge() {
1408
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1118
1409
  if (!this.matterStorageManager)
1119
1410
  throw new Error('No storage manager initialized');
1120
1411
  if (!this.matterbridgeContext)
@@ -1151,7 +1442,9 @@ export class Matterbridge extends EventEmitter {
1151
1442
  clearInterval(this.startMatterInterval);
1152
1443
  this.startMatterInterval = undefined;
1153
1444
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1445
+ // Start the Matter server node
1154
1446
  this.startServerNode(this.serverNode);
1447
+ // Configure the plugins
1155
1448
  this.configureTimeout = setTimeout(async () => {
1156
1449
  for (const plugin of this.plugins) {
1157
1450
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1166,6 +1459,7 @@ export class Matterbridge extends EventEmitter {
1166
1459
  }
1167
1460
  this.frontend.wssSendRefreshRequired();
1168
1461
  }, 30 * 1000);
1462
+ // Setting reachability to true
1169
1463
  this.reachabilityTimeout = setTimeout(() => {
1170
1464
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1171
1465
  if (this.serverNode)
@@ -1176,7 +1470,14 @@ export class Matterbridge extends EventEmitter {
1176
1470
  }, 60 * 1000);
1177
1471
  }, 1000);
1178
1472
  }
1473
+ /**
1474
+ * Starts the Matterbridge in childbridge mode.
1475
+ * @private
1476
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1477
+ */
1179
1478
  async startChildbridge() {
1479
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1480
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1180
1481
  if (!this.matterStorageManager)
1181
1482
  throw new Error('No storage manager initialized');
1182
1483
  for (const plugin of this.plugins) {
@@ -1223,12 +1524,13 @@ export class Matterbridge extends EventEmitter {
1223
1524
  clearInterval(this.startMatterInterval);
1224
1525
  this.startMatterInterval = undefined;
1225
1526
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1527
+ // Configure the plugins
1226
1528
  this.configureTimeout = setTimeout(async () => {
1227
1529
  for (const plugin of this.plugins) {
1228
1530
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1229
1531
  continue;
1230
1532
  try {
1231
- await this.plugins.configure(plugin);
1533
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1232
1534
  }
1233
1535
  catch (error) {
1234
1536
  plugin.error = true;
@@ -1256,7 +1558,9 @@ export class Matterbridge extends EventEmitter {
1256
1558
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1257
1559
  continue;
1258
1560
  }
1561
+ // Start the Matter server node
1259
1562
  this.startServerNode(plugin.serverNode);
1563
+ // Setting reachability to true
1260
1564
  plugin.reachabilityTimeout = setTimeout(() => {
1261
1565
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1262
1566
  if (plugin.serverNode)
@@ -1270,9 +1574,219 @@ export class Matterbridge extends EventEmitter {
1270
1574
  }
1271
1575
  }, 1000);
1272
1576
  }
1577
+ /**
1578
+ * Starts the Matterbridge controller.
1579
+ * @private
1580
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1581
+ */
1273
1582
  async startController() {
1583
+ /*
1584
+ if (!this.storageManager) {
1585
+ this.log.error('No storage manager initialized');
1586
+ await this.cleanup('No storage manager initialized');
1587
+ return;
1588
+ }
1589
+ this.log.info('Creating context: mattercontrollerContext');
1590
+ this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
1591
+ if (!this.mattercontrollerContext) {
1592
+ this.log.error('No storage context mattercontrollerContext initialized');
1593
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1594
+ return;
1595
+ }
1596
+
1597
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1598
+ this.matterServer = await this.createMatterServer(this.storageManager);
1599
+ this.log.info('Creating matter commissioning controller');
1600
+ this.commissioningController = new CommissioningController({
1601
+ autoConnect: false,
1602
+ });
1603
+ this.log.info('Adding matter commissioning controller to matter server');
1604
+ await this.matterServer.addCommissioningController(this.commissioningController);
1605
+
1606
+ this.log.info('Starting matter server');
1607
+ await this.matterServer.start();
1608
+ this.log.info('Matter server started');
1609
+
1610
+ if (hasParameter('pairingcode')) {
1611
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1612
+ const pairingCode = getParameter('pairingcode');
1613
+ const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
1614
+ const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
1615
+
1616
+ let longDiscriminator, setupPin, shortDiscriminator;
1617
+ if (pairingCode !== undefined) {
1618
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1619
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1620
+ longDiscriminator = undefined;
1621
+ setupPin = pairingCodeCodec.passcode;
1622
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1623
+ } else {
1624
+ longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1625
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1626
+ setupPin = this.mattercontrollerContext.get('pin', 20202021);
1627
+ }
1628
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1629
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1630
+ }
1631
+
1632
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1633
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1634
+ regulatoryCountryCode: 'XX',
1635
+ };
1636
+ const options = {
1637
+ commissioning: commissioningOptions,
1638
+ discovery: {
1639
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1640
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1641
+ },
1642
+ passcode: setupPin,
1643
+ } as NodeCommissioningOptions;
1644
+ this.log.info('Commissioning with options:', options);
1645
+ const nodeId = await this.commissioningController.commissionNode(options);
1646
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1647
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1648
+ } // (hasParameter('pairingcode'))
1649
+
1650
+ if (hasParameter('unpairall')) {
1651
+ this.log.info('***Commissioning controller unpairing all nodes...');
1652
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1653
+ for (const nodeId of nodeIds) {
1654
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1655
+ await this.commissioningController.removeNode(nodeId);
1656
+ }
1657
+ return;
1658
+ }
1659
+
1660
+ if (hasParameter('discover')) {
1661
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1662
+ // console.log(discover);
1663
+ }
1664
+
1665
+ if (!this.commissioningController.isCommissioned()) {
1666
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1667
+ return;
1668
+ }
1669
+
1670
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1671
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1672
+ for (const nodeId of nodeIds) {
1673
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1674
+
1675
+ const node = await this.commissioningController.connectNode(nodeId, {
1676
+ autoSubscribe: false,
1677
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1678
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1679
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1680
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1681
+ stateInformationCallback: (peerNodeId, info) => {
1682
+ switch (info) {
1683
+ case NodeStateInformation.Connected:
1684
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1685
+ break;
1686
+ case NodeStateInformation.Disconnected:
1687
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1688
+ break;
1689
+ case NodeStateInformation.Reconnecting:
1690
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1691
+ break;
1692
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1693
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1694
+ break;
1695
+ case NodeStateInformation.StructureChanged:
1696
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1697
+ break;
1698
+ case NodeStateInformation.Decommissioned:
1699
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1700
+ break;
1701
+ default:
1702
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1703
+ break;
1704
+ }
1705
+ },
1706
+ });
1707
+
1708
+ node.logStructure();
1709
+
1710
+ // Get the interaction client
1711
+ this.log.info('Getting the interaction client');
1712
+ const interactionClient = await node.getInteractionClient();
1713
+ let cluster;
1714
+ let attributes;
1715
+
1716
+ // Log BasicInformationCluster
1717
+ cluster = BasicInformationCluster;
1718
+ attributes = await interactionClient.getMultipleAttributes({
1719
+ attributes: [{ clusterId: cluster.id }],
1720
+ });
1721
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1722
+ attributes.forEach((attribute) => {
1723
+ this.log.info(
1724
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1725
+ );
1726
+ });
1727
+
1728
+ // Log PowerSourceCluster
1729
+ cluster = PowerSourceCluster;
1730
+ attributes = await interactionClient.getMultipleAttributes({
1731
+ attributes: [{ clusterId: cluster.id }],
1732
+ });
1733
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1734
+ attributes.forEach((attribute) => {
1735
+ this.log.info(
1736
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1737
+ );
1738
+ });
1739
+
1740
+ // Log ThreadNetworkDiagnostics
1741
+ cluster = ThreadNetworkDiagnosticsCluster;
1742
+ attributes = await interactionClient.getMultipleAttributes({
1743
+ attributes: [{ clusterId: cluster.id }],
1744
+ });
1745
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1746
+ attributes.forEach((attribute) => {
1747
+ this.log.info(
1748
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1749
+ );
1750
+ });
1751
+
1752
+ // Log SwitchCluster
1753
+ cluster = SwitchCluster;
1754
+ attributes = await interactionClient.getMultipleAttributes({
1755
+ attributes: [{ clusterId: cluster.id }],
1756
+ });
1757
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1758
+ attributes.forEach((attribute) => {
1759
+ this.log.info(
1760
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1761
+ );
1762
+ });
1763
+
1764
+ this.log.info('Subscribing to all attributes and events');
1765
+ await node.subscribeAllAttributesAndEvents({
1766
+ ignoreInitialTriggers: false,
1767
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1768
+ this.log.info(
1769
+ `***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`,
1770
+ ),
1771
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1772
+ this.log.info(
1773
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1774
+ );
1775
+ },
1776
+ });
1777
+ this.log.info('Subscribed to all attributes and events');
1778
+ }
1779
+ */
1274
1780
  }
1781
+ /** ***********************************************************************************************************************************/
1782
+ /** Matter.js methods */
1783
+ /** ***********************************************************************************************************************************/
1784
+ /**
1785
+ * Starts the matter storage process with name Matterbridge.
1786
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1787
+ */
1275
1788
  async startMatterStorage() {
1789
+ // Setup Matter storage
1276
1790
  this.log.info(`Starting matter node storage...`);
1277
1791
  this.matterStorageService = this.environment.get(StorageService);
1278
1792
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1280,13 +1794,25 @@ export class Matterbridge extends EventEmitter {
1280
1794
  this.log.info('Matter node storage manager "Matterbridge" created');
1281
1795
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
1282
1796
  this.log.info('Matter node storage started');
1797
+ // Backup matter storage since it is created/opened correctly
1283
1798
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1284
1799
  }
1800
+ /**
1801
+ * Makes a backup copy of the specified matter storage directory.
1802
+ *
1803
+ * @param storageName - The name of the storage directory to be backed up.
1804
+ * @param backupName - The name of the backup directory to be created.
1805
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1806
+ */
1285
1807
  async backupMatterStorage(storageName, backupName) {
1286
1808
  this.log.info('Creating matter node storage backup...');
1287
1809
  await copyDirectory(storageName, backupName);
1288
1810
  this.log.info('Created matter node storage backup');
1289
1811
  }
1812
+ /**
1813
+ * Stops the matter storage.
1814
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1815
+ */
1290
1816
  async stopMatterStorage() {
1291
1817
  this.log.info('Closing matter node storage...');
1292
1818
  this.matterStorageManager?.close();
@@ -1295,6 +1821,19 @@ export class Matterbridge extends EventEmitter {
1295
1821
  this.matterbridgeContext = undefined;
1296
1822
  this.log.info('Matter node storage closed');
1297
1823
  }
1824
+ /**
1825
+ * Creates a server node storage context.
1826
+ *
1827
+ * @param {string} pluginName - The name of the plugin.
1828
+ * @param {string} deviceName - The name of the device.
1829
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1830
+ * @param {number} vendorId - The vendor ID.
1831
+ * @param {string} vendorName - The vendor name.
1832
+ * @param {number} productId - The product ID.
1833
+ * @param {string} productName - The product name.
1834
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1835
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1836
+ */
1298
1837
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1299
1838
  if (!this.matterStorageService)
1300
1839
  throw new Error('No storage service initialized');
@@ -1327,6 +1866,15 @@ export class Matterbridge extends EventEmitter {
1327
1866
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1328
1867
  return storageContext;
1329
1868
  }
1869
+ /**
1870
+ * Creates a server node.
1871
+ *
1872
+ * @param {StorageContext} storageContext - The storage context for the server node.
1873
+ * @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
1874
+ * @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
1875
+ * @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
1876
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1877
+ */
1330
1878
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1331
1879
  const storeId = await storageContext.get('storeId');
1332
1880
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1336,21 +1884,33 @@ export class Matterbridge extends EventEmitter {
1336
1884
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1337
1885
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1338
1886
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1887
+ /**
1888
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1889
+ */
1339
1890
  const serverNode = await ServerNode.create({
1891
+ // Required: Give the Node a unique ID which is used to store the state of this node
1340
1892
  id: storeId,
1893
+ // Provide Network relevant configuration like the port
1894
+ // Optional when operating only one device on a host, Default port is 5540
1341
1895
  network: {
1342
1896
  listeningAddressIpv4: this.ipv4address,
1343
1897
  listeningAddressIpv6: this.ipv6address,
1344
1898
  port,
1345
1899
  },
1900
+ // Provide Commissioning relevant settings
1901
+ // Optional for development/testing purposes
1346
1902
  commissioning: {
1347
1903
  passcode,
1348
1904
  discriminator,
1349
1905
  },
1906
+ // Provide Node announcement settings
1907
+ // Optional: If Ommitted some development defaults are used
1350
1908
  productDescription: {
1351
1909
  name: await storageContext.get('deviceName'),
1352
1910
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1353
1911
  },
1912
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1913
+ // Optional: If Omitted some development defaults are used
1354
1914
  basicInformation: {
1355
1915
  vendorId: VendorId(await storageContext.get('vendorId')),
1356
1916
  vendorName: await storageContext.get('vendorName'),
@@ -1367,12 +1927,13 @@ export class Matterbridge extends EventEmitter {
1367
1927
  },
1368
1928
  });
1369
1929
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
1930
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1370
1931
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1371
1932
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1372
1933
  if (this.bridgeMode === 'bridge') {
1373
1934
  this.matterbridgeFabricInformations = sanitizedFabrics;
1374
1935
  if (resetSessions)
1375
- this.matterbridgeSessionInformations = undefined;
1936
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1376
1937
  this.matterbridgePaired = true;
1377
1938
  }
1378
1939
  if (this.bridgeMode === 'childbridge') {
@@ -1380,13 +1941,19 @@ export class Matterbridge extends EventEmitter {
1380
1941
  if (plugin) {
1381
1942
  plugin.fabricInformations = sanitizedFabrics;
1382
1943
  if (resetSessions)
1383
- plugin.sessionInformations = undefined;
1944
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1384
1945
  plugin.paired = true;
1385
1946
  }
1386
1947
  }
1387
1948
  };
1949
+ /**
1950
+ * This event is triggered when the device is initially commissioned successfully.
1951
+ * This means: It is added to the first fabric.
1952
+ */
1388
1953
  serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
1954
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1389
1955
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1956
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1390
1957
  serverNode.lifecycle.online.on(async () => {
1391
1958
  this.log.notice(`Server node for ${storeId} is online`);
1392
1959
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1432,6 +1999,7 @@ export class Matterbridge extends EventEmitter {
1432
1999
  }
1433
2000
  this.frontend.wssSendRefreshRequired();
1434
2001
  });
2002
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1435
2003
  serverNode.lifecycle.offline.on(() => {
1436
2004
  this.log.notice(`Server node for ${storeId} is offline`);
1437
2005
  if (this.bridgeMode === 'bridge') {
@@ -1453,6 +2021,10 @@ export class Matterbridge extends EventEmitter {
1453
2021
  }
1454
2022
  this.frontend.wssSendRefreshRequired();
1455
2023
  });
2024
+ /**
2025
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2026
+ * information is needed.
2027
+ */
1456
2028
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1457
2029
  let action = '';
1458
2030
  switch (fabricAction) {
@@ -1486,16 +2058,24 @@ export class Matterbridge extends EventEmitter {
1486
2058
  }
1487
2059
  }
1488
2060
  };
2061
+ /**
2062
+ * This event is triggered when an operative new session was opened by a Controller.
2063
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2064
+ */
1489
2065
  serverNode.events.sessions.opened.on((session) => {
1490
2066
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1491
2067
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1492
2068
  this.frontend.wssSendRefreshRequired();
1493
2069
  });
2070
+ /**
2071
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2072
+ */
1494
2073
  serverNode.events.sessions.closed.on((session) => {
1495
2074
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1496
2075
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1497
2076
  this.frontend.wssSendRefreshRequired();
1498
2077
  });
2078
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1499
2079
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1500
2080
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1501
2081
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1504,38 +2084,57 @@ export class Matterbridge extends EventEmitter {
1504
2084
  this.log.info(`Created server node for ${storeId}`);
1505
2085
  return serverNode;
1506
2086
  }
2087
+ /**
2088
+ * Starts the specified server node.
2089
+ *
2090
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2091
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2092
+ */
1507
2093
  async startServerNode(matterServerNode) {
1508
2094
  if (!matterServerNode)
1509
2095
  return;
1510
2096
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1511
2097
  await matterServerNode.start();
1512
2098
  }
2099
+ /**
2100
+ * Stops the specified server node.
2101
+ *
2102
+ * @param {ServerNode} matterServerNode - The server node to stop.
2103
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2104
+ */
1513
2105
  async stopServerNode(matterServerNode) {
1514
2106
  if (!matterServerNode)
1515
2107
  return;
1516
2108
  this.log.notice(`Closing ${matterServerNode.id} server node`);
2109
+ // Helper function to add a timeout to a promise
1517
2110
  const withTimeout = (promise, ms) => {
1518
2111
  return new Promise((resolve, reject) => {
1519
2112
  const timer = setTimeout(() => reject(new Error('Operation timed out')), ms);
1520
2113
  promise
1521
2114
  .then((result) => {
1522
- clearTimeout(timer);
2115
+ clearTimeout(timer); // Prevent memory leak
1523
2116
  resolve(result);
1524
2117
  })
1525
2118
  .catch((error) => {
1526
- clearTimeout(timer);
2119
+ clearTimeout(timer); // Ensure timeout does not fire if promise rejects first
1527
2120
  reject(error);
1528
2121
  });
1529
2122
  });
1530
2123
  };
1531
2124
  try {
1532
- await withTimeout(matterServerNode.close(), 30000);
2125
+ await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
1533
2126
  this.log.info(`Closed ${matterServerNode.id} server node`);
1534
2127
  }
1535
2128
  catch (error) {
1536
2129
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1537
2130
  }
1538
2131
  }
2132
+ /**
2133
+ * Advertises the specified server node if it is commissioned.
2134
+ *
2135
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2136
+ * @returns {Promise<{ qrPairingCode: string, manualPairingCode: string } | undefined>} A promise that resolves to the pairing codes if the server node is advertised, or undefined if not.
2137
+ */
1539
2138
  async advertiseServerNode(matterServerNode) {
1540
2139
  if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
1541
2140
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1545,17 +2144,32 @@ export class Matterbridge extends EventEmitter {
1545
2144
  }
1546
2145
  return undefined;
1547
2146
  }
2147
+ /**
2148
+ * Creates an aggregator node with the specified storage context.
2149
+ *
2150
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2151
+ * @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2152
+ */
1548
2153
  async createAggregatorNode(storageContext) {
1549
2154
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
1550
2155
  const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1551
2156
  return aggregatorNode;
1552
2157
  }
2158
+ /**
2159
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2160
+ *
2161
+ * @param {string} pluginName - The name of the plugin.
2162
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2163
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2164
+ */
1553
2165
  async addBridgedEndpoint(pluginName, device) {
2166
+ // Check if the plugin is registered
1554
2167
  const plugin = this.plugins.get(pluginName);
1555
2168
  if (!plugin) {
1556
2169
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1557
2170
  return;
1558
2171
  }
2172
+ // Register and add the device to the matterbridge aggregator node
1559
2173
  if (this.bridgeMode === 'bridge') {
1560
2174
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1561
2175
  if (!this.aggregatorNode)
@@ -1578,16 +2192,26 @@ export class Matterbridge extends EventEmitter {
1578
2192
  plugin.registeredDevices++;
1579
2193
  if (plugin.addedDevices !== undefined)
1580
2194
  plugin.addedDevices++;
2195
+ // Add the device to the DeviceManager
1581
2196
  this.devices.set(device);
1582
2197
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1583
2198
  }
2199
+ /**
2200
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2201
+ *
2202
+ * @param {string} pluginName - The name of the plugin.
2203
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2204
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2205
+ */
1584
2206
  async removeBridgedEndpoint(pluginName, device) {
1585
2207
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2208
+ // Check if the plugin is registered
1586
2209
  const plugin = this.plugins.get(pluginName);
1587
2210
  if (!plugin) {
1588
2211
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1589
2212
  return;
1590
2213
  }
2214
+ // Register and add the device to the matterbridge aggregator node
1591
2215
  if (this.bridgeMode === 'bridge') {
1592
2216
  if (!this.aggregatorNode) {
1593
2217
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1602,6 +2226,7 @@ export class Matterbridge extends EventEmitter {
1602
2226
  }
1603
2227
  else if (this.bridgeMode === 'childbridge') {
1604
2228
  if (plugin.type === 'AccessoryPlatform') {
2229
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1605
2230
  }
1606
2231
  else if (plugin.type === 'DynamicPlatform') {
1607
2232
  if (!plugin.aggregatorNode) {
@@ -1615,6 +2240,7 @@ export class Matterbridge extends EventEmitter {
1615
2240
  plugin.registeredDevices--;
1616
2241
  if (plugin.addedDevices !== undefined)
1617
2242
  plugin.addedDevices--;
2243
+ // Close the server node TODO check if this is correct
1618
2244
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
1619
2245
  if (plugin.serverNode) {
1620
2246
  await this.stopServerNode(plugin.serverNode);
@@ -1625,14 +2251,27 @@ export class Matterbridge extends EventEmitter {
1625
2251
  }
1626
2252
  }
1627
2253
  }
2254
+ // Remove the device from the DeviceManager
1628
2255
  this.devices.remove(device);
1629
2256
  }
2257
+ /**
2258
+ * Removes all bridged endpoints from the specified plugin.
2259
+ *
2260
+ * @param {string} pluginName - The name of the plugin.
2261
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2262
+ */
1630
2263
  async removeAllBridgedEndpoints(pluginName) {
1631
2264
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
1632
2265
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
1633
2266
  await this.removeBridgedEndpoint(pluginName, device);
1634
2267
  }
1635
2268
  }
2269
+ /**
2270
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2271
+ *
2272
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2273
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2274
+ */
1636
2275
  sanitizeFabricInformations(fabricInfo) {
1637
2276
  return fabricInfo.map((info) => {
1638
2277
  return {
@@ -1646,6 +2285,12 @@ export class Matterbridge extends EventEmitter {
1646
2285
  };
1647
2286
  });
1648
2287
  }
2288
+ /**
2289
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2290
+ *
2291
+ * @param {SessionInformation[]} sessionInfo - The array of session information objects.
2292
+ * @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
2293
+ */
1649
2294
  sanitizeSessionInformation(sessionInfo) {
1650
2295
  return sessionInfo
1651
2296
  .filter((session) => session.isPeerActive)
@@ -1673,11 +2318,51 @@ export class Matterbridge extends EventEmitter {
1673
2318
  };
1674
2319
  });
1675
2320
  }
2321
+ /**
2322
+ * Sets the reachability of a matter server node and trigger ReachableChanged event.
2323
+ *
2324
+ * @param {ServerNode<ServerNode.RootEndpoint>} serverNode - The commissioning server to set the reachability for.
2325
+ * @param {boolean} reachable - The new reachability status.
2326
+ */
2327
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1676
2328
  setServerNodeReachability(serverNode, reachable) {
2329
+ /*
2330
+ const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
2331
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2332
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2333
+ */
1677
2334
  }
2335
+ /**
2336
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2337
+ * @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The matter aggregator to set the reachability for.
2338
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2339
+ */
2340
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1678
2341
  setAggregatorReachability(aggregatorNode, reachable) {
2342
+ /*
2343
+ const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
2344
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2345
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2346
+ matterAggregator.getBridgedDevices().forEach((device) => {
2347
+ this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
2348
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
2349
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2350
+ });
2351
+ */
1679
2352
  }
2353
+ /**
2354
+ * Sets the reachability of a device and trigger.
2355
+ *
2356
+ * @param {MatterbridgeEndpoint} device - The device to set the reachability for.
2357
+ * @param {boolean} reachable - The new reachability status of the device.
2358
+ */
2359
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1680
2360
  setDeviceReachability(device, reachable) {
2361
+ /*
2362
+ const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
2363
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2364
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2365
+ */
1681
2366
  }
1682
2367
  getVendorIdName = (vendorId) => {
1683
2368
  if (!vendorId)
@@ -1720,13 +2405,36 @@ export class Matterbridge extends EventEmitter {
1720
2405
  }
1721
2406
  return vendorName;
1722
2407
  };
2408
+ /**
2409
+ * Spawns a child process with the given command and arguments.
2410
+ * @param {string} command - The command to execute.
2411
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2412
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2413
+ */
1723
2414
  async spawnCommand(command, args = []) {
2415
+ /*
2416
+ npm > npm.cmd on windows
2417
+ cmd.exe ['dir'] on windows
2418
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2419
+ process.on('unhandledRejection', (reason, promise) => {
2420
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2421
+ });
2422
+
2423
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2424
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2425
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2426
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2427
+ */
1724
2428
  const cmdLine = command + ' ' + args.join(' ');
1725
2429
  if (process.platform === 'win32' && command === 'npm') {
2430
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
1726
2431
  const argstring = 'npm ' + args.join(' ');
1727
2432
  args.splice(0, args.length, '/c', argstring);
1728
2433
  command = 'cmd.exe';
1729
2434
  }
2435
+ // Decide when using sudo on linux
2436
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2437
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
1730
2438
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
1731
2439
  args.unshift(command);
1732
2440
  command = 'sudo';
@@ -1785,3 +2493,4 @@ export class Matterbridge extends EventEmitter {
1785
2493
  });
1786
2494
  }
1787
2495
  }
2496
+ //# sourceMappingURL=matterbridge.js.map