matterbridge 2.1.5-dev.8 → 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 +268 -28
  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 +748 -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 +230 -3
  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,46 +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;
252
343
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
344
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
253
345
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
254
346
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
255
347
  this.matterbridgeInformation.fileLogger = true;
256
348
  }
257
349
  this.log.notice('Matterbridge is starting...');
258
350
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
351
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
259
352
  if (hasParameter('matterlogger')) {
260
353
  const level = getParameter('matterlogger');
261
354
  if (level === 'debug') {
@@ -287,6 +380,7 @@ export class Matterbridge extends EventEmitter {
287
380
  Logger.format = MatterLogFormat.ANSI;
288
381
  Logger.setLogger('default', this.createMatterLogger());
289
382
  this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
383
+ // Create the file logger for matter.js (context: matterFileLog)
290
384
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
291
385
  this.matterbridgeInformation.matterFileLogger = true;
292
386
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -295,6 +389,7 @@ export class Matterbridge extends EventEmitter {
295
389
  });
296
390
  }
297
391
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
392
+ // Set the interface to use for matter server node mdnsInterface
298
393
  if (hasParameter('mdnsinterface')) {
299
394
  this.mdnsInterface = getParameter('mdnsinterface');
300
395
  }
@@ -303,6 +398,7 @@ export class Matterbridge extends EventEmitter {
303
398
  if (this.mdnsInterface === '')
304
399
  this.mdnsInterface = undefined;
305
400
  }
401
+ // Validate mdnsInterface
306
402
  if (this.mdnsInterface) {
307
403
  const networkInterfaces = os.networkInterfaces();
308
404
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -316,6 +412,7 @@ export class Matterbridge extends EventEmitter {
316
412
  }
317
413
  if (this.mdnsInterface)
318
414
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
415
+ // Set the listeningAddressIpv4 for the matter commissioning server
319
416
  if (hasParameter('ipv4address')) {
320
417
  this.ipv4address = getParameter('ipv4address');
321
418
  }
@@ -324,6 +421,7 @@ export class Matterbridge extends EventEmitter {
324
421
  if (this.ipv4address === '')
325
422
  this.ipv4address = undefined;
326
423
  }
424
+ // Set the listeningAddressIpv6 for the matter commissioning server
327
425
  if (hasParameter('ipv6address')) {
328
426
  this.ipv6address = getParameter('ipv6address');
329
427
  }
@@ -332,14 +430,19 @@ export class Matterbridge extends EventEmitter {
332
430
  if (this.ipv6address === '')
333
431
  this.ipv6address = undefined;
334
432
  }
433
+ // Initialize PluginManager
335
434
  this.plugins = new PluginManager(this);
336
435
  await this.plugins.loadFromStorage();
337
436
  this.plugins.logLevel = this.log.logLevel;
437
+ // Initialize DeviceManager
338
438
  this.devices = new DeviceManager(this, this.nodeContext);
339
439
  this.devices.logLevel = this.log.logLevel;
440
+ // Get the plugins from node storage and create the plugins node storage contexts
340
441
  for (const plugin of this.plugins) {
341
442
  const packageJson = await this.plugins.parse(plugin);
342
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
343
446
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
344
447
  try {
345
448
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -361,6 +464,7 @@ export class Matterbridge extends EventEmitter {
361
464
  await plugin.nodeContext.set('description', plugin.description);
362
465
  await plugin.nodeContext.set('author', plugin.author);
363
466
  }
467
+ // Log system info and create .matterbridge directory
364
468
  await this.logNodeAndSystemInfo();
365
469
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
366
470
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -368,6 +472,7 @@ export class Matterbridge extends EventEmitter {
368
472
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
369
473
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
370
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
371
476
  const minNodeVersion = 18;
372
477
  const nodeVersion = process.versions.node;
373
478
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -375,9 +480,15 @@ export class Matterbridge extends EventEmitter {
375
480
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
376
481
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
377
482
  }
483
+ // Parse command line
378
484
  await this.parseCommandLine();
379
485
  this.initialized = true;
380
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
+ */
381
492
  async parseCommandLine() {
382
493
  if (hasParameter('help')) {
383
494
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -487,6 +598,7 @@ export class Matterbridge extends EventEmitter {
487
598
  await this.shutdownProcessAndFactoryReset();
488
599
  return;
489
600
  }
601
+ // Start the matter storage and create the matterbridge context
490
602
  try {
491
603
  await this.startMatterStorage();
492
604
  }
@@ -494,10 +606,12 @@ export class Matterbridge extends EventEmitter {
494
606
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
495
607
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
496
608
  }
609
+ // Clear the matterbridge context if the reset parameter is set
497
610
  if (hasParameter('reset') && getParameter('reset') === undefined) {
498
611
  await this.shutdownProcessAndReset();
499
612
  return;
500
613
  }
614
+ // Clear matterbridge plugin context if the reset parameter is set
501
615
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
502
616
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
503
617
  const plugin = this.plugins.get(getParameter('reset'));
@@ -519,13 +633,16 @@ export class Matterbridge extends EventEmitter {
519
633
  this.emit('shutdown');
520
634
  return;
521
635
  }
636
+ // Initialize frontend
522
637
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
523
638
  await this.frontend.start(getIntParameter('frontend'));
524
639
  this.frontend.logLevel = this.log.logLevel;
640
+ // Check now the latest versions of matterbridge and plugins
525
641
  this.getMatterbridgeLatestVersion();
526
642
  for (const plugin of this.plugins) {
527
643
  this.getPluginLatestVersion(plugin);
528
644
  }
645
+ // Check each 60 minutes the latest versions
529
646
  this.checkUpdateInterval = setInterval(() => {
530
647
  this.getMatterbridgeLatestVersion();
531
648
  for (const plugin of this.plugins) {
@@ -533,20 +650,24 @@ export class Matterbridge extends EventEmitter {
533
650
  }
534
651
  this.frontend.wssSendRefreshRequired();
535
652
  }, 60 * 60 * 1000);
653
+ // Start the matterbridge in mode test
536
654
  if (hasParameter('test')) {
537
655
  this.bridgeMode = 'bridge';
538
656
  MatterbridgeEndpoint.bridgeMode = 'bridge';
539
657
  return;
540
658
  }
659
+ // Start the matterbridge in mode controller
541
660
  if (hasParameter('controller')) {
542
661
  this.bridgeMode = 'controller';
543
662
  await this.startController();
544
663
  return;
545
664
  }
665
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
546
666
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
547
667
  this.log.info('Setting default matterbridge start mode to bridge');
548
668
  await this.nodeContext?.set('bridgeMode', 'bridge');
549
669
  }
670
+ // Start matterbridge in bridge mode
550
671
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
551
672
  this.bridgeMode = 'bridge';
552
673
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -554,6 +675,7 @@ export class Matterbridge extends EventEmitter {
554
675
  await this.startBridge();
555
676
  return;
556
677
  }
678
+ // Start matterbridge in childbridge mode
557
679
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
558
680
  this.bridgeMode = 'childbridge';
559
681
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -562,16 +684,28 @@ export class Matterbridge extends EventEmitter {
562
684
  return;
563
685
  }
564
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
+ */
565
695
  async startPlugins() {
696
+ // Check, load and start the plugins
566
697
  for (const plugin of this.plugins) {
567
698
  plugin.configJson = await this.plugins.loadConfig(plugin);
568
699
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
700
+ // Check if the plugin is available
569
701
  if (!(await this.plugins.resolve(plugin.path))) {
570
702
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
571
703
  plugin.enabled = false;
572
704
  plugin.error = true;
573
705
  continue;
574
706
  }
707
+ // Check if the plugin has a new version
708
+ // this.getPluginLatestVersion(plugin); // No await do it asyncronously
575
709
  if (!plugin.enabled) {
576
710
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
577
711
  continue;
@@ -585,20 +719,26 @@ export class Matterbridge extends EventEmitter {
585
719
  plugin.addedDevices = undefined;
586
720
  plugin.qrPairingCode = undefined;
587
721
  plugin.manualPairingCode = undefined;
588
- this.plugins.load(plugin, true, 'Matterbridge is starting');
722
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
589
723
  }
590
724
  this.frontend.wssSendRefreshRequired();
591
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
+ */
592
730
  registerProcessHandlers() {
593
731
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
594
732
  process.removeAllListeners('uncaughtException');
595
733
  process.removeAllListeners('unhandledRejection');
596
734
  this.exceptionHandler = async (error) => {
597
735
  this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
736
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
598
737
  };
599
738
  process.on('uncaughtException', this.exceptionHandler);
600
739
  this.rejectionHandler = async (reason, promise) => {
601
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...');
602
742
  };
603
743
  process.on('unhandledRejection', this.rejectionHandler);
604
744
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -611,6 +751,9 @@ export class Matterbridge extends EventEmitter {
611
751
  };
612
752
  process.on('SIGTERM', this.sigtermHandler);
613
753
  }
754
+ /**
755
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
756
+ */
614
757
  deregisterProcesslHandlers() {
615
758
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
616
759
  if (this.exceptionHandler)
@@ -627,12 +770,17 @@ export class Matterbridge extends EventEmitter {
627
770
  process.off('SIGTERM', this.sigtermHandler);
628
771
  this.sigtermHandler = undefined;
629
772
  }
773
+ /**
774
+ * Logs the node and system information.
775
+ */
630
776
  async logNodeAndSystemInfo() {
777
+ // IP address information
631
778
  const networkInterfaces = os.networkInterfaces();
632
779
  this.systemInformation.interfaceName = '';
633
780
  this.systemInformation.ipv4Address = '';
634
781
  this.systemInformation.ipv6Address = '';
635
782
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
783
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
636
784
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
637
785
  continue;
638
786
  if (!interfaceDetails) {
@@ -658,19 +806,22 @@ export class Matterbridge extends EventEmitter {
658
806
  break;
659
807
  }
660
808
  }
809
+ // Node information
661
810
  this.systemInformation.nodeVersion = process.versions.node;
662
811
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
663
812
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
664
813
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
814
+ // Host system information
665
815
  this.systemInformation.hostname = os.hostname();
666
816
  this.systemInformation.user = os.userInfo().username;
667
- this.systemInformation.osType = os.type();
668
- this.systemInformation.osRelease = os.release();
669
- this.systemInformation.osPlatform = os.platform();
670
- this.systemInformation.osArch = os.arch();
671
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
672
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
673
- 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
674
825
  this.log.debug('Host System Information:');
675
826
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
676
827
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -686,15 +837,19 @@ export class Matterbridge extends EventEmitter {
686
837
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
687
838
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
688
839
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
840
+ // Home directory
689
841
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
690
842
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
691
843
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
844
+ // Package root directory
692
845
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
693
846
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
694
847
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
695
848
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
849
+ // Global node_modules directory
696
850
  if (this.nodeContext)
697
851
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
852
+ // First run of Matterbridge so the node storage is empty
698
853
  if (this.globalModulesDirectory === '') {
699
854
  try {
700
855
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -708,6 +863,20 @@ export class Matterbridge extends EventEmitter {
708
863
  }
709
864
  else
710
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
711
880
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
712
881
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
713
882
  try {
@@ -731,6 +900,7 @@ export class Matterbridge extends EventEmitter {
731
900
  }
732
901
  }
733
902
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
903
+ // Create the plugin directory Matterbridge in the home directory
734
904
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
735
905
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
736
906
  try {
@@ -754,18 +924,28 @@ export class Matterbridge extends EventEmitter {
754
924
  }
755
925
  }
756
926
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
927
+ // Matterbridge version
757
928
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
758
929
  this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
759
930
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
760
931
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
932
+ // Matterbridge latest version
761
933
  if (this.nodeContext)
762
934
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
763
935
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
936
+ // this.getMatterbridgeLatestVersion();
937
+ // Current working directory
764
938
  const currentDir = process.cwd();
765
939
  this.log.debug(`Current Working Directory: ${currentDir}`);
940
+ // Command line arguments (excluding 'node' and the script name)
766
941
  const cmdArgs = process.argv.slice(2).join(' ');
767
942
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
768
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
+ */
769
949
  async getLatestVersion(packageName) {
770
950
  return new Promise((resolve, reject) => {
771
951
  this.execRunningCount++;
@@ -780,6 +960,10 @@ export class Matterbridge extends EventEmitter {
780
960
  });
781
961
  });
782
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
+ */
783
967
  async getGlobalNodeModules() {
784
968
  return new Promise((resolve, reject) => {
785
969
  this.execRunningCount++;
@@ -794,6 +978,13 @@ export class Matterbridge extends EventEmitter {
794
978
  });
795
979
  });
796
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
+ */
797
988
  async getMatterbridgeLatestVersion() {
798
989
  getNpmPackageVersion('matterbridge')
799
990
  .then(async (version) => {
@@ -812,6 +1003,14 @@ export class Matterbridge extends EventEmitter {
812
1003
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
813
1004
  });
814
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
+ */
815
1014
  async getPluginLatestVersion(plugin) {
816
1015
  getNpmPackageVersion(plugin.name)
817
1016
  .then((version) => {
@@ -825,38 +1024,51 @@ export class Matterbridge extends EventEmitter {
825
1024
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
826
1025
  });
827
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
+ */
828
1032
  createMatterLogger() {
829
- 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 */ });
830
1034
  return (_level, formattedLog) => {
831
1035
  const logger = formattedLog.slice(44, 44 + 20).trim();
832
1036
  const message = formattedLog.slice(65);
833
1037
  matterLogger.logName = logger;
834
1038
  switch (_level) {
835
1039
  case MatterLogLevel.DEBUG:
836
- matterLogger.log("debug", message);
1040
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
837
1041
  break;
838
1042
  case MatterLogLevel.INFO:
839
- matterLogger.log("info", message);
1043
+ matterLogger.log("info" /* LogLevel.INFO */, message);
840
1044
  break;
841
1045
  case MatterLogLevel.NOTICE:
842
- matterLogger.log("notice", message);
1046
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
843
1047
  break;
844
1048
  case MatterLogLevel.WARN:
845
- matterLogger.log("warn", message);
1049
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
846
1050
  break;
847
1051
  case MatterLogLevel.ERROR:
848
- matterLogger.log("error", message);
1052
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
849
1053
  break;
850
1054
  case MatterLogLevel.FATAL:
851
- matterLogger.log("fatal", message);
1055
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
852
1056
  break;
853
1057
  default:
854
- matterLogger.log("debug", message);
1058
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
855
1059
  break;
856
1060
  }
857
1061
  };
858
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
+ */
859
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
860
1072
  let fileSize = 0;
861
1073
  if (unlink) {
862
1074
  try {
@@ -905,12 +1117,21 @@ export class Matterbridge extends EventEmitter {
905
1117
  }
906
1118
  };
907
1119
  }
1120
+ /**
1121
+ * Restarts the process by exiting the current instance and loading a new instance.
1122
+ */
908
1123
  async restartProcess() {
909
1124
  await this.cleanup('restarting...', true);
910
1125
  }
1126
+ /**
1127
+ * Shut down the process by exiting the current process.
1128
+ */
911
1129
  async shutdownProcess() {
912
1130
  await this.cleanup('shutting down...', false);
913
1131
  }
1132
+ /**
1133
+ * Update matterbridge and and shut down the process.
1134
+ */
914
1135
  async updateProcess() {
915
1136
  this.log.info('Updating matterbridge...');
916
1137
  try {
@@ -923,46 +1144,66 @@ export class Matterbridge extends EventEmitter {
923
1144
  this.frontend.wssSendRestartRequired();
924
1145
  await this.cleanup('updating...', false);
925
1146
  }
1147
+ /**
1148
+ * Unregister all devices and shut down the process.
1149
+ */
926
1150
  async unregisterAndShutdownProcess() {
927
1151
  this.log.info('Unregistering all devices and shutting down...');
928
1152
  for (const plugin of this.plugins) {
929
1153
  await this.removeAllBridgedEndpoints(plugin.name);
930
1154
  }
931
1155
  this.log.debug('Waiting for the MessageExchange to finish...');
932
- await new Promise((resolve) => setTimeout(resolve, 1000));
1156
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
933
1157
  this.log.debug('Cleaning up and shutting down...');
934
1158
  await this.cleanup('unregistered all devices and shutting down...', false);
935
1159
  }
1160
+ /**
1161
+ * Reset commissioning and shut down the process.
1162
+ */
936
1163
  async shutdownProcessAndReset() {
937
1164
  await this.cleanup('shutting down with reset...', false);
938
1165
  }
1166
+ /**
1167
+ * Factory reset and shut down the process.
1168
+ */
939
1169
  async shutdownProcessAndFactoryReset() {
940
1170
  await this.cleanup('shutting down with factory reset...', false);
941
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
+ */
942
1178
  async cleanup(message, restart = false) {
943
1179
  if (this.initialized && !this.hasCleanupStarted) {
944
1180
  this.hasCleanupStarted = true;
945
1181
  this.log.info(message);
1182
+ // Clear the start matter interval
946
1183
  if (this.startMatterInterval) {
947
1184
  clearInterval(this.startMatterInterval);
948
1185
  this.startMatterInterval = undefined;
949
1186
  this.log.debug('Start matter interval cleared');
950
1187
  }
1188
+ // Clear the check update interval
951
1189
  if (this.checkUpdateInterval) {
952
1190
  clearInterval(this.checkUpdateInterval);
953
1191
  this.checkUpdateInterval = undefined;
954
1192
  this.log.debug('Check update interval cleared');
955
1193
  }
1194
+ // Clear the configure timeout
956
1195
  if (this.configureTimeout) {
957
1196
  clearTimeout(this.configureTimeout);
958
1197
  this.configureTimeout = undefined;
959
1198
  this.log.debug('Matterbridge configure timeout cleared');
960
1199
  }
1200
+ // Clear the reachability timeout
961
1201
  if (this.reachabilityTimeout) {
962
1202
  clearTimeout(this.reachabilityTimeout);
963
1203
  this.reachabilityTimeout = undefined;
964
1204
  this.log.debug('Matterbridge reachability timeout cleared');
965
1205
  }
1206
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
966
1207
  for (const plugin of this.plugins) {
967
1208
  if (!plugin.enabled || plugin.error)
968
1209
  continue;
@@ -973,9 +1214,10 @@ export class Matterbridge extends EventEmitter {
973
1214
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
974
1215
  }
975
1216
  }
1217
+ // Stopping matter server nodes
976
1218
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
977
1219
  this.log.debug('Waiting for the MessageExchange to finish...');
978
- await new Promise((resolve) => setTimeout(resolve, 1000));
1220
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
979
1221
  if (this.bridgeMode === 'bridge') {
980
1222
  if (this.serverNode) {
981
1223
  await this.stopServerNode(this.serverNode);
@@ -991,6 +1233,7 @@ export class Matterbridge extends EventEmitter {
991
1233
  }
992
1234
  }
993
1235
  this.log.notice('Stopped matter server nodes');
1236
+ // Matter commisioning reset
994
1237
  if (message === 'shutting down with reset...') {
995
1238
  this.log.info('Resetting Matterbridge commissioning information...');
996
1239
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1000,17 +1243,37 @@ export class Matterbridge extends EventEmitter {
1000
1243
  await this.matterbridgeContext?.clearAll();
1001
1244
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1002
1245
  }
1246
+ // Stop matter storage
1003
1247
  await this.stopMatterStorage();
1248
+ // Stop the frontend
1004
1249
  await this.frontend.stop();
1250
+ // Remove the matterfilelogger
1005
1251
  try {
1006
1252
  Logger.removeLogger('matterfilelogger');
1253
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1007
1254
  }
1008
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}`);
1009
1257
  }
1258
+ // Serialize registeredDevices
1010
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)
1011
1273
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1012
1274
  await this.nodeContext.close();
1013
1275
  this.nodeContext = undefined;
1276
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1014
1277
  for (const plugin of this.plugins) {
1015
1278
  if (plugin.nodeContext) {
1016
1279
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1027,8 +1290,10 @@ export class Matterbridge extends EventEmitter {
1027
1290
  }
1028
1291
  this.plugins.clear();
1029
1292
  this.devices.clear();
1293
+ // Factory reset
1030
1294
  if (message === 'shutting down with factory reset...') {
1031
1295
  try {
1296
+ // Delete old matter storage file and backup
1032
1297
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1033
1298
  this.log.info(`Unlinking old matter storage file: ${file}`);
1034
1299
  await fs.unlink(file);
@@ -1042,6 +1307,7 @@ export class Matterbridge extends EventEmitter {
1042
1307
  }
1043
1308
  }
1044
1309
  try {
1310
+ // Delete matter node storage directory with its subdirectories and backup
1045
1311
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1046
1312
  this.log.info(`Removing matter node storage directory: ${dir}`);
1047
1313
  await fs.rm(dir, { recursive: true });
@@ -1055,6 +1321,7 @@ export class Matterbridge extends EventEmitter {
1055
1321
  }
1056
1322
  }
1057
1323
  try {
1324
+ // Delete node storage directory with its subdirectories and backup
1058
1325
  const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1059
1326
  this.log.info(`Removing storage directory: ${dir}`);
1060
1327
  await fs.rm(dir, { recursive: true });
@@ -1069,12 +1336,13 @@ export class Matterbridge extends EventEmitter {
1069
1336
  }
1070
1337
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1071
1338
  }
1339
+ // Deregisters the process handlers
1072
1340
  this.deregisterProcesslHandlers();
1073
1341
  if (restart) {
1074
1342
  if (message === 'updating...') {
1075
1343
  this.log.info('Cleanup completed. Updating...');
1076
1344
  Matterbridge.instance = undefined;
1077
- this.emit('update');
1345
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1078
1346
  }
1079
1347
  else if (message === 'restarting...') {
1080
1348
  this.log.info('Cleanup completed. Restarting...');
@@ -1094,6 +1362,14 @@ export class Matterbridge extends EventEmitter {
1094
1362
  this.log.debug('Cleanup already started...');
1095
1363
  }
1096
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
+ */
1097
1373
  async createAccessoryPlugin(plugin, device, start = false) {
1098
1374
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1099
1375
  plugin.locked = true;
@@ -1105,6 +1381,13 @@ export class Matterbridge extends EventEmitter {
1105
1381
  await this.startServerNode(plugin.serverNode);
1106
1382
  }
1107
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
+ */
1108
1391
  async createDynamicPlugin(plugin, start = false) {
1109
1392
  if (!plugin.locked) {
1110
1393
  plugin.locked = true;
@@ -1116,7 +1399,13 @@ export class Matterbridge extends EventEmitter {
1116
1399
  await this.startServerNode(plugin.serverNode);
1117
1400
  }
1118
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
+ */
1119
1407
  async startBridge() {
1408
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1120
1409
  if (!this.matterStorageManager)
1121
1410
  throw new Error('No storage manager initialized');
1122
1411
  if (!this.matterbridgeContext)
@@ -1153,7 +1442,9 @@ export class Matterbridge extends EventEmitter {
1153
1442
  clearInterval(this.startMatterInterval);
1154
1443
  this.startMatterInterval = undefined;
1155
1444
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1445
+ // Start the Matter server node
1156
1446
  this.startServerNode(this.serverNode);
1447
+ // Configure the plugins
1157
1448
  this.configureTimeout = setTimeout(async () => {
1158
1449
  for (const plugin of this.plugins) {
1159
1450
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1168,6 +1459,7 @@ export class Matterbridge extends EventEmitter {
1168
1459
  }
1169
1460
  this.frontend.wssSendRefreshRequired();
1170
1461
  }, 30 * 1000);
1462
+ // Setting reachability to true
1171
1463
  this.reachabilityTimeout = setTimeout(() => {
1172
1464
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1173
1465
  if (this.serverNode)
@@ -1178,7 +1470,14 @@ export class Matterbridge extends EventEmitter {
1178
1470
  }, 60 * 1000);
1179
1471
  }, 1000);
1180
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
+ */
1181
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
1182
1481
  if (!this.matterStorageManager)
1183
1482
  throw new Error('No storage manager initialized');
1184
1483
  for (const plugin of this.plugins) {
@@ -1225,12 +1524,13 @@ export class Matterbridge extends EventEmitter {
1225
1524
  clearInterval(this.startMatterInterval);
1226
1525
  this.startMatterInterval = undefined;
1227
1526
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1527
+ // Configure the plugins
1228
1528
  this.configureTimeout = setTimeout(async () => {
1229
1529
  for (const plugin of this.plugins) {
1230
1530
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1231
1531
  continue;
1232
1532
  try {
1233
- await this.plugins.configure(plugin);
1533
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1234
1534
  }
1235
1535
  catch (error) {
1236
1536
  plugin.error = true;
@@ -1258,7 +1558,9 @@ export class Matterbridge extends EventEmitter {
1258
1558
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1259
1559
  continue;
1260
1560
  }
1561
+ // Start the Matter server node
1261
1562
  this.startServerNode(plugin.serverNode);
1563
+ // Setting reachability to true
1262
1564
  plugin.reachabilityTimeout = setTimeout(() => {
1263
1565
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1264
1566
  if (plugin.serverNode)
@@ -1272,9 +1574,219 @@ export class Matterbridge extends EventEmitter {
1272
1574
  }
1273
1575
  }, 1000);
1274
1576
  }
1577
+ /**
1578
+ * Starts the Matterbridge controller.
1579
+ * @private
1580
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1581
+ */
1275
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
+ */
1276
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
+ */
1277
1788
  async startMatterStorage() {
1789
+ // Setup Matter storage
1278
1790
  this.log.info(`Starting matter node storage...`);
1279
1791
  this.matterStorageService = this.environment.get(StorageService);
1280
1792
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1282,13 +1794,25 @@ export class Matterbridge extends EventEmitter {
1282
1794
  this.log.info('Matter node storage manager "Matterbridge" created');
1283
1795
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
1284
1796
  this.log.info('Matter node storage started');
1797
+ // Backup matter storage since it is created/opened correctly
1285
1798
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1286
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
+ */
1287
1807
  async backupMatterStorage(storageName, backupName) {
1288
1808
  this.log.info('Creating matter node storage backup...');
1289
1809
  await copyDirectory(storageName, backupName);
1290
1810
  this.log.info('Created matter node storage backup');
1291
1811
  }
1812
+ /**
1813
+ * Stops the matter storage.
1814
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1815
+ */
1292
1816
  async stopMatterStorage() {
1293
1817
  this.log.info('Closing matter node storage...');
1294
1818
  this.matterStorageManager?.close();
@@ -1297,6 +1821,19 @@ export class Matterbridge extends EventEmitter {
1297
1821
  this.matterbridgeContext = undefined;
1298
1822
  this.log.info('Matter node storage closed');
1299
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
+ */
1300
1837
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1301
1838
  if (!this.matterStorageService)
1302
1839
  throw new Error('No storage service initialized');
@@ -1329,6 +1866,15 @@ export class Matterbridge extends EventEmitter {
1329
1866
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1330
1867
  return storageContext;
1331
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
+ */
1332
1878
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1333
1879
  const storeId = await storageContext.get('storeId');
1334
1880
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1338,21 +1884,33 @@ export class Matterbridge extends EventEmitter {
1338
1884
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1339
1885
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1340
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
+ */
1341
1890
  const serverNode = await ServerNode.create({
1891
+ // Required: Give the Node a unique ID which is used to store the state of this node
1342
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
1343
1895
  network: {
1344
1896
  listeningAddressIpv4: this.ipv4address,
1345
1897
  listeningAddressIpv6: this.ipv6address,
1346
1898
  port,
1347
1899
  },
1900
+ // Provide Commissioning relevant settings
1901
+ // Optional for development/testing purposes
1348
1902
  commissioning: {
1349
1903
  passcode,
1350
1904
  discriminator,
1351
1905
  },
1906
+ // Provide Node announcement settings
1907
+ // Optional: If Ommitted some development defaults are used
1352
1908
  productDescription: {
1353
1909
  name: await storageContext.get('deviceName'),
1354
1910
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1355
1911
  },
1912
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1913
+ // Optional: If Omitted some development defaults are used
1356
1914
  basicInformation: {
1357
1915
  vendorId: VendorId(await storageContext.get('vendorId')),
1358
1916
  vendorName: await storageContext.get('vendorName'),
@@ -1369,12 +1927,13 @@ export class Matterbridge extends EventEmitter {
1369
1927
  },
1370
1928
  });
1371
1929
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
1930
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1372
1931
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1373
1932
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1374
1933
  if (this.bridgeMode === 'bridge') {
1375
1934
  this.matterbridgeFabricInformations = sanitizedFabrics;
1376
1935
  if (resetSessions)
1377
- this.matterbridgeSessionInformations = undefined;
1936
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1378
1937
  this.matterbridgePaired = true;
1379
1938
  }
1380
1939
  if (this.bridgeMode === 'childbridge') {
@@ -1382,13 +1941,19 @@ export class Matterbridge extends EventEmitter {
1382
1941
  if (plugin) {
1383
1942
  plugin.fabricInformations = sanitizedFabrics;
1384
1943
  if (resetSessions)
1385
- plugin.sessionInformations = undefined;
1944
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1386
1945
  plugin.paired = true;
1387
1946
  }
1388
1947
  }
1389
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
+ */
1390
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. */
1391
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. */
1392
1957
  serverNode.lifecycle.online.on(async () => {
1393
1958
  this.log.notice(`Server node for ${storeId} is online`);
1394
1959
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1434,6 +1999,7 @@ export class Matterbridge extends EventEmitter {
1434
1999
  }
1435
2000
  this.frontend.wssSendRefreshRequired();
1436
2001
  });
2002
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1437
2003
  serverNode.lifecycle.offline.on(() => {
1438
2004
  this.log.notice(`Server node for ${storeId} is offline`);
1439
2005
  if (this.bridgeMode === 'bridge') {
@@ -1455,6 +2021,10 @@ export class Matterbridge extends EventEmitter {
1455
2021
  }
1456
2022
  this.frontend.wssSendRefreshRequired();
1457
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
+ */
1458
2028
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1459
2029
  let action = '';
1460
2030
  switch (fabricAction) {
@@ -1488,16 +2058,24 @@ export class Matterbridge extends EventEmitter {
1488
2058
  }
1489
2059
  }
1490
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
+ */
1491
2065
  serverNode.events.sessions.opened.on((session) => {
1492
2066
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1493
2067
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1494
2068
  this.frontend.wssSendRefreshRequired();
1495
2069
  });
2070
+ /**
2071
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2072
+ */
1496
2073
  serverNode.events.sessions.closed.on((session) => {
1497
2074
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1498
2075
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1499
2076
  this.frontend.wssSendRefreshRequired();
1500
2077
  });
2078
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1501
2079
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1502
2080
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1503
2081
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1506,38 +2084,57 @@ export class Matterbridge extends EventEmitter {
1506
2084
  this.log.info(`Created server node for ${storeId}`);
1507
2085
  return serverNode;
1508
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
+ */
1509
2093
  async startServerNode(matterServerNode) {
1510
2094
  if (!matterServerNode)
1511
2095
  return;
1512
2096
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1513
2097
  await matterServerNode.start();
1514
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
+ */
1515
2105
  async stopServerNode(matterServerNode) {
1516
2106
  if (!matterServerNode)
1517
2107
  return;
1518
2108
  this.log.notice(`Closing ${matterServerNode.id} server node`);
2109
+ // Helper function to add a timeout to a promise
1519
2110
  const withTimeout = (promise, ms) => {
1520
2111
  return new Promise((resolve, reject) => {
1521
2112
  const timer = setTimeout(() => reject(new Error('Operation timed out')), ms);
1522
2113
  promise
1523
2114
  .then((result) => {
1524
- clearTimeout(timer);
2115
+ clearTimeout(timer); // Prevent memory leak
1525
2116
  resolve(result);
1526
2117
  })
1527
2118
  .catch((error) => {
1528
- clearTimeout(timer);
2119
+ clearTimeout(timer); // Ensure timeout does not fire if promise rejects first
1529
2120
  reject(error);
1530
2121
  });
1531
2122
  });
1532
2123
  };
1533
2124
  try {
1534
- await withTimeout(matterServerNode.close(), 30000);
2125
+ await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
1535
2126
  this.log.info(`Closed ${matterServerNode.id} server node`);
1536
2127
  }
1537
2128
  catch (error) {
1538
2129
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1539
2130
  }
1540
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
+ */
1541
2138
  async advertiseServerNode(matterServerNode) {
1542
2139
  if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
1543
2140
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1547,17 +2144,32 @@ export class Matterbridge extends EventEmitter {
1547
2144
  }
1548
2145
  return undefined;
1549
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
+ */
1550
2153
  async createAggregatorNode(storageContext) {
1551
2154
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
1552
2155
  const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1553
2156
  return aggregatorNode;
1554
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
+ */
1555
2165
  async addBridgedEndpoint(pluginName, device) {
2166
+ // Check if the plugin is registered
1556
2167
  const plugin = this.plugins.get(pluginName);
1557
2168
  if (!plugin) {
1558
2169
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1559
2170
  return;
1560
2171
  }
2172
+ // Register and add the device to the matterbridge aggregator node
1561
2173
  if (this.bridgeMode === 'bridge') {
1562
2174
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1563
2175
  if (!this.aggregatorNode)
@@ -1580,16 +2192,26 @@ export class Matterbridge extends EventEmitter {
1580
2192
  plugin.registeredDevices++;
1581
2193
  if (plugin.addedDevices !== undefined)
1582
2194
  plugin.addedDevices++;
2195
+ // Add the device to the DeviceManager
1583
2196
  this.devices.set(device);
1584
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}`);
1585
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
+ */
1586
2206
  async removeBridgedEndpoint(pluginName, device) {
1587
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
1588
2209
  const plugin = this.plugins.get(pluginName);
1589
2210
  if (!plugin) {
1590
2211
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1591
2212
  return;
1592
2213
  }
2214
+ // Register and add the device to the matterbridge aggregator node
1593
2215
  if (this.bridgeMode === 'bridge') {
1594
2216
  if (!this.aggregatorNode) {
1595
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`);
@@ -1604,6 +2226,7 @@ export class Matterbridge extends EventEmitter {
1604
2226
  }
1605
2227
  else if (this.bridgeMode === 'childbridge') {
1606
2228
  if (plugin.type === 'AccessoryPlatform') {
2229
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1607
2230
  }
1608
2231
  else if (plugin.type === 'DynamicPlatform') {
1609
2232
  if (!plugin.aggregatorNode) {
@@ -1617,6 +2240,7 @@ export class Matterbridge extends EventEmitter {
1617
2240
  plugin.registeredDevices--;
1618
2241
  if (plugin.addedDevices !== undefined)
1619
2242
  plugin.addedDevices--;
2243
+ // Close the server node TODO check if this is correct
1620
2244
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
1621
2245
  if (plugin.serverNode) {
1622
2246
  await this.stopServerNode(plugin.serverNode);
@@ -1627,14 +2251,27 @@ export class Matterbridge extends EventEmitter {
1627
2251
  }
1628
2252
  }
1629
2253
  }
2254
+ // Remove the device from the DeviceManager
1630
2255
  this.devices.remove(device);
1631
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
+ */
1632
2263
  async removeAllBridgedEndpoints(pluginName) {
1633
2264
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
1634
2265
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
1635
2266
  await this.removeBridgedEndpoint(pluginName, device);
1636
2267
  }
1637
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
+ */
1638
2275
  sanitizeFabricInformations(fabricInfo) {
1639
2276
  return fabricInfo.map((info) => {
1640
2277
  return {
@@ -1648,6 +2285,12 @@ export class Matterbridge extends EventEmitter {
1648
2285
  };
1649
2286
  });
1650
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
+ */
1651
2294
  sanitizeSessionInformation(sessionInfo) {
1652
2295
  return sessionInfo
1653
2296
  .filter((session) => session.isPeerActive)
@@ -1675,11 +2318,51 @@ export class Matterbridge extends EventEmitter {
1675
2318
  };
1676
2319
  });
1677
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
1678
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
+ */
1679
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
1680
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
+ */
1681
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
1682
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
+ */
1683
2366
  }
1684
2367
  getVendorIdName = (vendorId) => {
1685
2368
  if (!vendorId)
@@ -1722,13 +2405,36 @@ export class Matterbridge extends EventEmitter {
1722
2405
  }
1723
2406
  return vendorName;
1724
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
+ */
1725
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
+ */
1726
2428
  const cmdLine = command + ' ' + args.join(' ');
1727
2429
  if (process.platform === 'win32' && command === 'npm') {
2430
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
1728
2431
  const argstring = 'npm ' + args.join(' ');
1729
2432
  args.splice(0, args.length, '/c', argstring);
1730
2433
  command = 'cmd.exe';
1731
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
1732
2438
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
1733
2439
  args.unshift(command);
1734
2440
  command = 'sudo';
@@ -1787,3 +2493,4 @@ export class Matterbridge extends EventEmitter {
1787
2493
  });
1788
2494
  }
1789
2495
  }
2496
+ //# sourceMappingURL=matterbridge.js.map