matterbridge 2.1.4-dev.3 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +4 -1
  2. package/README-DOCKER.md +11 -7
  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 +110 -0
  20. package/dist/frontend.d.ts.map +1 -0
  21. package/dist/frontend.js +232 -23
  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 +749 -39
  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 +834 -0
  76. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  77. package/dist/matterbridgeEndpoint.js +691 -6
  78. package/dist/matterbridgeEndpoint.js.map +1 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts +2264 -0
  80. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  81. package/dist/matterbridgeEndpointHelpers.js +105 -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 +122 -6
  86. package/dist/matterbridgePlatform.js.map +1 -0
  87. package/dist/matterbridgeTypes.d.ts +167 -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 +307 -9
  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 } 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: '',
@@ -55,7 +85,7 @@ export class Matterbridge extends EventEmitter {
55
85
  restartMode: '',
56
86
  readOnly: hasParameter('readonly'),
57
87
  profile: getParameter('profile'),
58
- loggerLevel: "info",
88
+ loggerLevel: "info" /* LogLevel.INFO */,
59
89
  fileLogger: false,
60
90
  matterLoggerLevel: MatterLogLevel.INFO,
61
91
  matterFileLogger: false,
@@ -90,9 +120,11 @@ export class Matterbridge extends EventEmitter {
90
120
  plugins;
91
121
  devices;
92
122
  frontend = new Frontend(this);
123
+ // Matterbridge storage
93
124
  nodeStorage;
94
125
  nodeContext;
95
126
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
127
+ // Cleanup
96
128
  hasCleanupStarted = false;
97
129
  initialized = false;
98
130
  execRunningCount = 0;
@@ -104,34 +136,57 @@ export class Matterbridge extends EventEmitter {
104
136
  sigtermHandler;
105
137
  exceptionHandler;
106
138
  rejectionHandler;
139
+ // Matter environment
107
140
  environment = Environment.default;
141
+ // Matter storage
108
142
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
109
143
  matterStorageService;
110
144
  matterStorageManager;
111
145
  matterbridgeContext;
112
146
  mattercontrollerContext;
113
- mdnsInterface;
114
- ipv4address;
115
- ipv6address;
116
- port;
117
- passcode;
118
- discriminator;
147
+ // Matter parameters
148
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
149
+ ipv4address; // matter server node listeningAddressIpv4
150
+ ipv6address; // matter server node listeningAddressIpv6
151
+ port; // first server node port
152
+ passcode; // first server node passcode
153
+ discriminator; // first server node discriminator
119
154
  serverNode;
120
155
  aggregatorNode;
121
156
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
122
157
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
123
158
  static instance;
159
+ // We load asyncronously so is private
124
160
  constructor() {
125
161
  super();
126
162
  }
163
+ /**
164
+ * Retrieves the list of Matterbridge devices.
165
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
166
+ */
127
167
  getDevices() {
128
168
  return this.devices.array();
129
169
  }
170
+ /**
171
+ * Retrieves the list of registered plugins.
172
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
173
+ */
130
174
  getPlugins() {
131
175
  return this.plugins.array();
132
176
  }
177
+ /** ***********************************************************************************************************************************/
178
+ /** loadInstance() and cleanup() methods */
179
+ /** ***********************************************************************************************************************************/
180
+ /**
181
+ * Loads an instance of the Matterbridge class.
182
+ * If an instance already exists, return that instance.
183
+ *
184
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
185
+ * @returns The loaded Matterbridge instance.
186
+ */
133
187
  static async loadInstance(initialize = false) {
134
188
  if (!Matterbridge.instance) {
189
+ // eslint-disable-next-line no-console
135
190
  if (hasParameter('debug'))
136
191
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
137
192
  Matterbridge.instance = new Matterbridge();
@@ -140,7 +195,13 @@ export class Matterbridge extends EventEmitter {
140
195
  }
141
196
  return Matterbridge.instance;
142
197
  }
198
+ /**
199
+ * Call cleanup().
200
+ * @deprecated This method is deprecated and is only used for jest tests.
201
+ *
202
+ */
143
203
  async destroyInstance() {
204
+ // Save server nodes to close
144
205
  const servers = [];
145
206
  if (this.bridgeMode === 'bridge') {
146
207
  if (this.serverNode)
@@ -152,54 +213,80 @@ export class Matterbridge extends EventEmitter {
152
213
  servers.push(plugin.serverNode);
153
214
  }
154
215
  }
216
+ // Cleanup
155
217
  await this.cleanup('destroying instance...', false);
218
+ // Close servers mdns service
156
219
  for (const server of servers) {
157
220
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
158
221
  this.log.info(`Closed ${server.id} MdnsService`);
159
222
  }
223
+ // Wait for the cleanup to finish
160
224
  await new Promise((resolve) => {
161
225
  setTimeout(resolve, 1000);
162
226
  });
163
227
  }
228
+ /**
229
+ * Initializes the Matterbridge application.
230
+ *
231
+ * @remarks
232
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
233
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
234
+ * node version, registers signal handlers, initializes storage, and parses the command line.
235
+ *
236
+ * @returns A Promise that resolves when the initialization is complete.
237
+ */
164
238
  async initialize() {
239
+ // Set the restart mode
165
240
  if (hasParameter('service'))
166
241
  this.restartMode = 'service';
167
242
  if (hasParameter('docker'))
168
243
  this.restartMode = 'docker';
244
+ // Set the matterbridge directory
169
245
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
170
246
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
247
+ // Setup the matter environment
171
248
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
172
249
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
173
250
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
174
251
  this.environment.vars.set('runtime.signals', false);
175
252
  this.environment.vars.set('runtime.exitcode', false);
176
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
253
+ // Create the matterbridge logger
254
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
255
+ // Register process handlers
177
256
  this.registerProcessHandlers();
257
+ // Initialize nodeStorage and nodeContext
178
258
  try {
179
259
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
180
260
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
181
261
  this.log.debug('Creating node storage context for matterbridge');
182
262
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
263
+ // TODO: Remove this code when node-persist-manager is updated
264
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
265
  const keys = (await this.nodeStorage?.storage.keys());
184
266
  for (const key of keys) {
185
267
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
268
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
269
  await this.nodeStorage?.storage.get(key);
187
270
  }
188
271
  const storages = await this.nodeStorage.getStorageNames();
189
272
  for (const storage of storages) {
190
273
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
191
274
  const nodeContext = await this.nodeStorage?.createStorage(storage);
275
+ // TODO: Remove this code when node-persist-manager is updated
276
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
277
  const keys = (await nodeContext?.storage.keys());
193
278
  keys.forEach(async (key) => {
194
279
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
195
280
  await nodeContext?.get(key);
196
281
  });
197
282
  }
283
+ // Creating a backup of the node storage since it is not corrupted
198
284
  this.log.debug('Creating node storage backup...');
199
285
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
200
286
  this.log.debug('Created node storage backup');
201
287
  }
202
288
  catch (error) {
289
+ // Restoring the backup of the node storage since it is corrupted
203
290
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
204
291
  if (hasParameter('norestore')) {
205
292
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -214,45 +301,51 @@ export class Matterbridge extends EventEmitter {
214
301
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
215
302
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
216
303
  }
304
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
217
305
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
306
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
218
307
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
308
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
219
309
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
220
310
  this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
311
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
221
312
  if (hasParameter('logger')) {
222
313
  const level = getParameter('logger');
223
314
  if (level === 'debug') {
224
- this.log.logLevel = "debug";
315
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
225
316
  }
226
317
  else if (level === 'info') {
227
- this.log.logLevel = "info";
318
+ this.log.logLevel = "info" /* LogLevel.INFO */;
228
319
  }
229
320
  else if (level === 'notice') {
230
- this.log.logLevel = "notice";
321
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
231
322
  }
232
323
  else if (level === 'warn') {
233
- this.log.logLevel = "warn";
324
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
234
325
  }
235
326
  else if (level === 'error') {
236
- this.log.logLevel = "error";
327
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
237
328
  }
238
329
  else if (level === 'fatal') {
239
- this.log.logLevel = "fatal";
330
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
240
331
  }
241
332
  else {
242
333
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
243
- this.log.logLevel = "info";
334
+ this.log.logLevel = "info" /* LogLevel.INFO */;
244
335
  }
245
336
  }
246
337
  else {
247
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
338
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
248
339
  }
249
340
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
341
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
250
342
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
251
343
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
252
344
  this.matterbridgeInformation.fileLogger = true;
253
345
  }
254
346
  this.log.notice('Matterbridge is starting...');
255
347
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
348
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
256
349
  if (hasParameter('matterlogger')) {
257
350
  const level = getParameter('matterlogger');
258
351
  if (level === 'debug') {
@@ -283,6 +376,7 @@ export class Matterbridge extends EventEmitter {
283
376
  }
284
377
  Logger.format = MatterLogFormat.ANSI;
285
378
  Logger.setLogger('default', this.createMatterLogger());
379
+ // Create the file logger for matter.js (context: matterFileLog)
286
380
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
287
381
  this.matterbridgeInformation.matterFileLogger = true;
288
382
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -291,6 +385,7 @@ export class Matterbridge extends EventEmitter {
291
385
  });
292
386
  }
293
387
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
388
+ // Set the interface to use for matter server node mdnsInterface
294
389
  if (hasParameter('mdnsinterface')) {
295
390
  this.mdnsInterface = getParameter('mdnsinterface');
296
391
  }
@@ -299,6 +394,7 @@ export class Matterbridge extends EventEmitter {
299
394
  if (this.mdnsInterface === '')
300
395
  this.mdnsInterface = undefined;
301
396
  }
397
+ // Validate mdnsInterface
302
398
  if (this.mdnsInterface) {
303
399
  const networkInterfaces = os.networkInterfaces();
304
400
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -312,6 +408,7 @@ export class Matterbridge extends EventEmitter {
312
408
  }
313
409
  if (this.mdnsInterface)
314
410
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
411
+ // Set the listeningAddressIpv4 for the matter commissioning server
315
412
  if (hasParameter('ipv4address')) {
316
413
  this.ipv4address = getParameter('ipv4address');
317
414
  }
@@ -320,6 +417,7 @@ export class Matterbridge extends EventEmitter {
320
417
  if (this.ipv4address === '')
321
418
  this.ipv4address = undefined;
322
419
  }
420
+ // Set the listeningAddressIpv6 for the matter commissioning server
323
421
  if (hasParameter('ipv6address')) {
324
422
  this.ipv6address = getParameter('ipv6address');
325
423
  }
@@ -328,14 +426,19 @@ export class Matterbridge extends EventEmitter {
328
426
  if (this.ipv6address === '')
329
427
  this.ipv6address = undefined;
330
428
  }
429
+ // Initialize PluginManager
331
430
  this.plugins = new PluginManager(this);
332
431
  await this.plugins.loadFromStorage();
333
432
  this.plugins.logLevel = this.log.logLevel;
433
+ // Initialize DeviceManager
334
434
  this.devices = new DeviceManager(this, this.nodeContext);
335
435
  this.devices.logLevel = this.log.logLevel;
436
+ // Get the plugins from node storage and create the plugins node storage contexts
336
437
  for (const plugin of this.plugins) {
337
438
  const packageJson = await this.plugins.parse(plugin);
338
439
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
440
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
441
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
339
442
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
340
443
  try {
341
444
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -357,6 +460,7 @@ export class Matterbridge extends EventEmitter {
357
460
  await plugin.nodeContext.set('description', plugin.description);
358
461
  await plugin.nodeContext.set('author', plugin.author);
359
462
  }
463
+ // Log system info and create .matterbridge directory
360
464
  await this.logNodeAndSystemInfo();
361
465
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
362
466
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -364,6 +468,7 @@ export class Matterbridge extends EventEmitter {
364
468
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
365
469
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
366
470
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
471
+ // Check node version and throw error
367
472
  const minNodeVersion = 18;
368
473
  const nodeVersion = process.versions.node;
369
474
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -371,9 +476,15 @@ export class Matterbridge extends EventEmitter {
371
476
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
372
477
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
373
478
  }
479
+ // Parse command line
374
480
  await this.parseCommandLine();
375
481
  this.initialized = true;
376
482
  }
483
+ /**
484
+ * Parses the command line arguments and performs the corresponding actions.
485
+ * @private
486
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
487
+ */
377
488
  async parseCommandLine() {
378
489
  if (hasParameter('help')) {
379
490
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -483,6 +594,7 @@ export class Matterbridge extends EventEmitter {
483
594
  await this.shutdownProcessAndFactoryReset();
484
595
  return;
485
596
  }
597
+ // Start the matter storage and create the matterbridge context
486
598
  try {
487
599
  await this.startMatterStorage();
488
600
  }
@@ -490,10 +602,12 @@ export class Matterbridge extends EventEmitter {
490
602
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
491
603
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
492
604
  }
605
+ // Clear the matterbridge context if the reset parameter is set
493
606
  if (hasParameter('reset') && getParameter('reset') === undefined) {
494
607
  await this.shutdownProcessAndReset();
495
608
  return;
496
609
  }
610
+ // Clear matterbridge plugin context if the reset parameter is set
497
611
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
498
612
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
499
613
  const plugin = this.plugins.get(getParameter('reset'));
@@ -515,9 +629,11 @@ export class Matterbridge extends EventEmitter {
515
629
  this.emit('shutdown');
516
630
  return;
517
631
  }
632
+ // Initialize frontend
518
633
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
519
634
  await this.frontend.start(getIntParameter('frontend'));
520
635
  this.frontend.logLevel = this.log.logLevel;
636
+ // Check each 60 minutes the latest versions
521
637
  this.checkUpdateInterval = setInterval(() => {
522
638
  this.getMatterbridgeLatestVersion();
523
639
  for (const plugin of this.plugins) {
@@ -525,20 +641,24 @@ export class Matterbridge extends EventEmitter {
525
641
  }
526
642
  this.frontend.wssSendRefreshRequired();
527
643
  }, 60 * 60 * 1000);
644
+ // Start the matterbridge in mode test
528
645
  if (hasParameter('test')) {
529
646
  this.bridgeMode = 'bridge';
530
647
  MatterbridgeEndpoint.bridgeMode = 'bridge';
531
648
  return;
532
649
  }
650
+ // Start the matterbridge in mode controller
533
651
  if (hasParameter('controller')) {
534
652
  this.bridgeMode = 'controller';
535
653
  await this.startController();
536
654
  return;
537
655
  }
656
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
538
657
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
539
658
  this.log.info('Setting default matterbridge start mode to bridge');
540
659
  await this.nodeContext?.set('bridgeMode', 'bridge');
541
660
  }
661
+ // Start matterbridge in bridge mode
542
662
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
543
663
  this.bridgeMode = 'bridge';
544
664
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -546,6 +666,7 @@ export class Matterbridge extends EventEmitter {
546
666
  await this.startBridge();
547
667
  return;
548
668
  }
669
+ // Start matterbridge in childbridge mode
549
670
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
550
671
  this.bridgeMode = 'childbridge';
551
672
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -554,16 +675,28 @@ export class Matterbridge extends EventEmitter {
554
675
  return;
555
676
  }
556
677
  }
678
+ /**
679
+ * Asynchronously loads and starts the registered plugins.
680
+ *
681
+ * This method is responsible for initializing and staarting all enabled plugins.
682
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
683
+ *
684
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
685
+ */
557
686
  async startPlugins() {
687
+ // Check, load and start the plugins
558
688
  for (const plugin of this.plugins) {
559
689
  plugin.configJson = await this.plugins.loadConfig(plugin);
560
690
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
691
+ // Check if the plugin is available
561
692
  if (!(await this.plugins.resolve(plugin.path))) {
562
693
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
563
694
  plugin.enabled = false;
564
695
  plugin.error = true;
565
696
  continue;
566
697
  }
698
+ // Check if the plugin has a new version
699
+ // this.getPluginLatestVersion(plugin); // No await do it asyncronously
567
700
  if (!plugin.enabled) {
568
701
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
569
702
  continue;
@@ -577,20 +710,26 @@ export class Matterbridge extends EventEmitter {
577
710
  plugin.addedDevices = undefined;
578
711
  plugin.qrPairingCode = undefined;
579
712
  plugin.manualPairingCode = undefined;
580
- this.plugins.load(plugin, true, 'Matterbridge is starting');
713
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
581
714
  }
582
715
  this.frontend.wssSendRefreshRequired();
583
716
  }
717
+ /**
718
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
719
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
720
+ */
584
721
  registerProcessHandlers() {
585
722
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
586
723
  process.removeAllListeners('uncaughtException');
587
724
  process.removeAllListeners('unhandledRejection');
588
725
  this.exceptionHandler = async (error) => {
589
726
  this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
727
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
590
728
  };
591
729
  process.on('uncaughtException', this.exceptionHandler);
592
730
  this.rejectionHandler = async (reason, promise) => {
593
731
  this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
732
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
594
733
  };
595
734
  process.on('unhandledRejection', this.rejectionHandler);
596
735
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -603,6 +742,9 @@ export class Matterbridge extends EventEmitter {
603
742
  };
604
743
  process.on('SIGTERM', this.sigtermHandler);
605
744
  }
745
+ /**
746
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
747
+ */
606
748
  deregisterProcesslHandlers() {
607
749
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
608
750
  if (this.exceptionHandler)
@@ -619,12 +761,17 @@ export class Matterbridge extends EventEmitter {
619
761
  process.off('SIGTERM', this.sigtermHandler);
620
762
  this.sigtermHandler = undefined;
621
763
  }
764
+ /**
765
+ * Logs the node and system information.
766
+ */
622
767
  async logNodeAndSystemInfo() {
768
+ // IP address information
623
769
  const networkInterfaces = os.networkInterfaces();
624
770
  this.systemInformation.interfaceName = '';
625
771
  this.systemInformation.ipv4Address = '';
626
772
  this.systemInformation.ipv6Address = '';
627
773
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
774
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
628
775
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
629
776
  continue;
630
777
  if (!interfaceDetails) {
@@ -650,19 +797,22 @@ export class Matterbridge extends EventEmitter {
650
797
  break;
651
798
  }
652
799
  }
800
+ // Node information
653
801
  this.systemInformation.nodeVersion = process.versions.node;
654
802
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
655
803
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
656
804
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
805
+ // Host system information
657
806
  this.systemInformation.hostname = os.hostname();
658
807
  this.systemInformation.user = os.userInfo().username;
659
- this.systemInformation.osType = os.type();
660
- this.systemInformation.osRelease = os.release();
661
- this.systemInformation.osPlatform = os.platform();
662
- this.systemInformation.osArch = os.arch();
663
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
664
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
665
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
808
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
809
+ this.systemInformation.osRelease = os.release(); // Kernel version
810
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
811
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
812
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
813
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
814
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
815
+ // Log the system information
666
816
  this.log.debug('Host System Information:');
667
817
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
668
818
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -678,15 +828,19 @@ export class Matterbridge extends EventEmitter {
678
828
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
679
829
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
680
830
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
831
+ // Home directory
681
832
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
682
833
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
683
834
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
835
+ // Package root directory
684
836
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
685
837
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
686
838
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
687
839
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
840
+ // Global node_modules directory
688
841
  if (this.nodeContext)
689
842
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
843
+ // First run of Matterbridge so the node storage is empty
690
844
  if (this.globalModulesDirectory === '') {
691
845
  try {
692
846
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -700,6 +854,20 @@ export class Matterbridge extends EventEmitter {
700
854
  }
701
855
  else
702
856
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
857
+ /* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
858
+ else {
859
+ this.getGlobalNodeModules()
860
+ .then(async (globalModulesDirectory) => {
861
+ this.globalModulesDirectory = globalModulesDirectory;
862
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
863
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
864
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
865
+ })
866
+ .catch((error) => {
867
+ this.log.error(`Error getting global node_modules directory: ${error}`);
868
+ });
869
+ }*/
870
+ // Create the data directory .matterbridge in the home directory
703
871
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
704
872
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
705
873
  try {
@@ -723,6 +891,7 @@ export class Matterbridge extends EventEmitter {
723
891
  }
724
892
  }
725
893
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
894
+ // Create the plugin directory Matterbridge in the home directory
726
895
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
727
896
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
728
897
  try {
@@ -746,18 +915,28 @@ export class Matterbridge extends EventEmitter {
746
915
  }
747
916
  }
748
917
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
918
+ // Matterbridge version
749
919
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
750
920
  this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
751
921
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
752
922
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
923
+ // Matterbridge latest version
753
924
  if (this.nodeContext)
754
925
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
755
926
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
927
+ // this.getMatterbridgeLatestVersion();
928
+ // Current working directory
756
929
  const currentDir = process.cwd();
757
930
  this.log.debug(`Current Working Directory: ${currentDir}`);
931
+ // Command line arguments (excluding 'node' and the script name)
758
932
  const cmdArgs = process.argv.slice(2).join(' ');
759
933
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
760
934
  }
935
+ /**
936
+ * Retrieves the latest version of a package from the npm registry.
937
+ * @param packageName - The name of the package.
938
+ * @returns A Promise that resolves to the latest version of the package.
939
+ */
761
940
  async getLatestVersion(packageName) {
762
941
  return new Promise((resolve, reject) => {
763
942
  this.execRunningCount++;
@@ -772,6 +951,10 @@ export class Matterbridge extends EventEmitter {
772
951
  });
773
952
  });
774
953
  }
954
+ /**
955
+ * Retrieves the path to the global Node.js modules directory.
956
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
957
+ */
775
958
  async getGlobalNodeModules() {
776
959
  return new Promise((resolve, reject) => {
777
960
  this.execRunningCount++;
@@ -786,6 +969,11 @@ export class Matterbridge extends EventEmitter {
786
969
  });
787
970
  });
788
971
  }
972
+ /**
973
+ * Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
974
+ * @private
975
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
976
+ */
789
977
  async getMatterbridgeLatestVersion() {
790
978
  this.getLatestVersion('matterbridge')
791
979
  .then(async (matterbridgeLatestVersion) => {
@@ -802,8 +990,19 @@ export class Matterbridge extends EventEmitter {
802
990
  })
803
991
  .catch((error) => {
804
992
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
993
+ // error.stack && this.log.debug(error.stack);
805
994
  });
806
995
  }
996
+ /**
997
+ * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
998
+ * If the plugin's version is different from the latest version, logs a warning message.
999
+ * If the plugin's version is the same as the latest version, logs an info message.
1000
+ * If there is an error retrieving the latest version, logs an error message.
1001
+ *
1002
+ * @private
1003
+ * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
1004
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
1005
+ */
807
1006
  async getPluginLatestVersion(plugin) {
808
1007
  this.getLatestVersion(plugin.name)
809
1008
  .then(async (latestVersion) => {
@@ -815,40 +1014,54 @@ export class Matterbridge extends EventEmitter {
815
1014
  })
816
1015
  .catch((error) => {
817
1016
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
1017
+ // error.stack && this.log.debug(error.stack);
818
1018
  });
819
1019
  }
1020
+ /**
1021
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1022
+ *
1023
+ * @returns {Function} The MatterLogger function.
1024
+ */
820
1025
  createMatterLogger() {
821
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1026
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
822
1027
  return (_level, formattedLog) => {
823
1028
  const logger = formattedLog.slice(44, 44 + 20).trim();
824
1029
  const message = formattedLog.slice(65);
825
1030
  matterLogger.logName = logger;
826
1031
  switch (_level) {
827
1032
  case MatterLogLevel.DEBUG:
828
- matterLogger.log("debug", message);
1033
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
829
1034
  break;
830
1035
  case MatterLogLevel.INFO:
831
- matterLogger.log("info", message);
1036
+ matterLogger.log("info" /* LogLevel.INFO */, message);
832
1037
  break;
833
1038
  case MatterLogLevel.NOTICE:
834
- matterLogger.log("notice", message);
1039
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
835
1040
  break;
836
1041
  case MatterLogLevel.WARN:
837
- matterLogger.log("warn", message);
1042
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
838
1043
  break;
839
1044
  case MatterLogLevel.ERROR:
840
- matterLogger.log("error", message);
1045
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
841
1046
  break;
842
1047
  case MatterLogLevel.FATAL:
843
- matterLogger.log("fatal", message);
1048
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
844
1049
  break;
845
1050
  default:
846
- matterLogger.log("debug", message);
1051
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
847
1052
  break;
848
1053
  }
849
1054
  };
850
1055
  }
1056
+ /**
1057
+ * Creates a Matter File Logger.
1058
+ *
1059
+ * @param {string} filePath - The path to the log file.
1060
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1061
+ * @returns {Function} - A function that logs formatted messages to the log file.
1062
+ */
851
1063
  async createMatterFileLogger(filePath, unlink = false) {
1064
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
852
1065
  let fileSize = 0;
853
1066
  if (unlink) {
854
1067
  try {
@@ -897,12 +1110,21 @@ export class Matterbridge extends EventEmitter {
897
1110
  }
898
1111
  };
899
1112
  }
1113
+ /**
1114
+ * Restarts the process by exiting the current instance and loading a new instance.
1115
+ */
900
1116
  async restartProcess() {
901
1117
  await this.cleanup('restarting...', true);
902
1118
  }
1119
+ /**
1120
+ * Shut down the process by exiting the current process.
1121
+ */
903
1122
  async shutdownProcess() {
904
1123
  await this.cleanup('shutting down...', false);
905
1124
  }
1125
+ /**
1126
+ * Update matterbridge and and shut down the process.
1127
+ */
906
1128
  async updateProcess() {
907
1129
  this.log.info('Updating matterbridge...');
908
1130
  try {
@@ -915,6 +1137,9 @@ export class Matterbridge extends EventEmitter {
915
1137
  this.frontend.wssSendRestartRequired();
916
1138
  await this.cleanup('updating...', false);
917
1139
  }
1140
+ /**
1141
+ * Unregister all devices and shut down the process.
1142
+ */
918
1143
  async unregisterAndShutdownProcess() {
919
1144
  this.log.info('Unregistering all devices and shutting down...');
920
1145
  for (const plugin of this.plugins) {
@@ -922,6 +1147,9 @@ export class Matterbridge extends EventEmitter {
922
1147
  }
923
1148
  await this.cleanup('unregistered all devices and shutting down...', false);
924
1149
  }
1150
+ /**
1151
+ * Reset commissioning and shut down the process.
1152
+ */
925
1153
  async shutdownProcessAndReset() {
926
1154
  this.log.info('Resetting Matterbridge commissioning information...');
927
1155
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -933,8 +1161,12 @@ export class Matterbridge extends EventEmitter {
933
1161
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
934
1162
  await this.cleanup('shutting down with reset...', false);
935
1163
  }
1164
+ /**
1165
+ * Factory reset and shut down the process.
1166
+ */
936
1167
  async shutdownProcessAndFactoryReset() {
937
1168
  try {
1169
+ // Delete old matter storage file and backup
938
1170
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
939
1171
  this.log.info(`Unlinking old matter storage file: ${file}`);
940
1172
  await fs.unlink(file);
@@ -948,6 +1180,7 @@ export class Matterbridge extends EventEmitter {
948
1180
  }
949
1181
  }
950
1182
  try {
1183
+ // Delete matter node storage directory with its subdirectories and backup
951
1184
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
952
1185
  this.log.info(`Removing matter node storage directory: ${dir}`);
953
1186
  await fs.rm(dir, { recursive: true });
@@ -961,6 +1194,7 @@ export class Matterbridge extends EventEmitter {
961
1194
  }
962
1195
  }
963
1196
  try {
1197
+ // Delete node storage directory with its subdirectories and backup
964
1198
  const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
965
1199
  this.log.info(`Removing storage directory: ${dir}`);
966
1200
  await fs.rm(dir, { recursive: true });
@@ -980,30 +1214,41 @@ export class Matterbridge extends EventEmitter {
980
1214
  this.devices.clear();
981
1215
  await this.cleanup('shutting down with factory reset...', false);
982
1216
  }
1217
+ /**
1218
+ * Cleans up the Matterbridge instance.
1219
+ * @param message - The cleanup message.
1220
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1221
+ * @returns A promise that resolves when the cleanup is completed.
1222
+ */
983
1223
  async cleanup(message, restart = false) {
984
1224
  if (this.initialized && !this.hasCleanupStarted) {
985
1225
  this.hasCleanupStarted = true;
986
1226
  this.log.info(message);
1227
+ // Clear the start matter interval
987
1228
  if (this.startMatterInterval) {
988
1229
  clearInterval(this.startMatterInterval);
989
1230
  this.startMatterInterval = undefined;
990
1231
  this.log.debug('Start matter interval cleared');
991
1232
  }
1233
+ // Clear the check update interval
992
1234
  if (this.checkUpdateInterval) {
993
1235
  clearInterval(this.checkUpdateInterval);
994
1236
  this.checkUpdateInterval = undefined;
995
1237
  this.log.debug('Check update interval cleared');
996
1238
  }
1239
+ // Clear the configure timeout
997
1240
  if (this.configureTimeout) {
998
1241
  clearTimeout(this.configureTimeout);
999
1242
  this.configureTimeout = undefined;
1000
1243
  this.log.debug('Matterbridge configure timeout cleared');
1001
1244
  }
1245
+ // Clear the reachability timeout
1002
1246
  if (this.reachabilityTimeout) {
1003
1247
  clearTimeout(this.reachabilityTimeout);
1004
1248
  this.reachabilityTimeout = undefined;
1005
1249
  this.log.debug('Matterbridge reachability timeout cleared');
1006
1250
  }
1251
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
1007
1252
  for (const plugin of this.plugins) {
1008
1253
  if (!plugin.enabled || plugin.error)
1009
1254
  continue;
@@ -1014,6 +1259,7 @@ export class Matterbridge extends EventEmitter {
1014
1259
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1015
1260
  }
1016
1261
  }
1262
+ // Stopping matter server nodes
1017
1263
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1018
1264
  if (this.bridgeMode === 'bridge') {
1019
1265
  if (this.serverNode) {
@@ -1030,17 +1276,37 @@ export class Matterbridge extends EventEmitter {
1030
1276
  }
1031
1277
  }
1032
1278
  this.log.notice('Stopped matter server nodes');
1279
+ // Stop matter storage
1033
1280
  await this.stopMatterStorage();
1281
+ // Stop the frontend
1034
1282
  await this.frontend.stop();
1283
+ // Remove the matterfilelogger
1035
1284
  try {
1036
1285
  Logger.removeLogger('matterfilelogger');
1286
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1037
1287
  }
1038
1288
  catch (error) {
1289
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1039
1290
  }
1291
+ // Serialize registeredDevices
1040
1292
  if (this.nodeStorage && this.nodeContext) {
1293
+ /*
1294
+ TODO: Implement serialization of registered devices in edge mode
1295
+ this.log.info('Saving registered devices...');
1296
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1297
+ this.devices.forEach(async (device) => {
1298
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1299
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1300
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1301
+ });
1302
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1303
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1304
+ */
1305
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1041
1306
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1042
1307
  await this.nodeContext.close();
1043
1308
  this.nodeContext = undefined;
1309
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1044
1310
  for (const plugin of this.plugins) {
1045
1311
  if (plugin.nodeContext) {
1046
1312
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1057,12 +1323,13 @@ export class Matterbridge extends EventEmitter {
1057
1323
  }
1058
1324
  this.plugins.clear();
1059
1325
  this.devices.clear();
1326
+ // Deregisters the process handlers
1060
1327
  this.deregisterProcesslHandlers();
1061
1328
  if (restart) {
1062
1329
  if (message === 'updating...') {
1063
1330
  this.log.info('Cleanup completed. Updating...');
1064
1331
  Matterbridge.instance = undefined;
1065
- this.emit('update');
1332
+ this.emit('update'); // Restart the process but the update has been done before
1066
1333
  }
1067
1334
  else if (message === 'restarting...') {
1068
1335
  this.log.info('Cleanup completed. Restarting...');
@@ -1082,6 +1349,14 @@ export class Matterbridge extends EventEmitter {
1082
1349
  this.log.debug('Cleanup already started...');
1083
1350
  }
1084
1351
  }
1352
+ /**
1353
+ * Creates and configures the server node for an accessory plugin for a given device.
1354
+ *
1355
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1356
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1357
+ * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1358
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1359
+ */
1085
1360
  async createAccessoryPlugin(plugin, device, start = false) {
1086
1361
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1087
1362
  plugin.locked = true;
@@ -1093,6 +1368,13 @@ export class Matterbridge extends EventEmitter {
1093
1368
  await this.startServerNode(plugin.serverNode);
1094
1369
  }
1095
1370
  }
1371
+ /**
1372
+ * Creates and configures the server node for a dynamic plugin.
1373
+ *
1374
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1375
+ * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1376
+ * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1377
+ */
1096
1378
  async createDynamicPlugin(plugin, start = false) {
1097
1379
  if (!plugin.locked) {
1098
1380
  plugin.locked = true;
@@ -1104,7 +1386,13 @@ export class Matterbridge extends EventEmitter {
1104
1386
  await this.startServerNode(plugin.serverNode);
1105
1387
  }
1106
1388
  }
1389
+ /**
1390
+ * Starts the Matterbridge in bridge mode.
1391
+ * @private
1392
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1393
+ */
1107
1394
  async startBridge() {
1395
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1108
1396
  if (!this.matterStorageManager)
1109
1397
  throw new Error('No storage manager initialized');
1110
1398
  if (!this.matterbridgeContext)
@@ -1141,7 +1429,9 @@ export class Matterbridge extends EventEmitter {
1141
1429
  clearInterval(this.startMatterInterval);
1142
1430
  this.startMatterInterval = undefined;
1143
1431
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1432
+ // Start the Matter server node
1144
1433
  this.startServerNode(this.serverNode);
1434
+ // Configure the plugins
1145
1435
  this.configureTimeout = setTimeout(async () => {
1146
1436
  for (const plugin of this.plugins) {
1147
1437
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1156,6 +1446,7 @@ export class Matterbridge extends EventEmitter {
1156
1446
  }
1157
1447
  this.frontend.wssSendRefreshRequired();
1158
1448
  }, 30 * 1000);
1449
+ // Setting reachability to true
1159
1450
  this.reachabilityTimeout = setTimeout(() => {
1160
1451
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1161
1452
  if (this.serverNode)
@@ -1166,7 +1457,14 @@ export class Matterbridge extends EventEmitter {
1166
1457
  }, 60 * 1000);
1167
1458
  }, 1000);
1168
1459
  }
1460
+ /**
1461
+ * Starts the Matterbridge in childbridge mode.
1462
+ * @private
1463
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1464
+ */
1169
1465
  async startChildbridge() {
1466
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1467
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1170
1468
  if (!this.matterStorageManager)
1171
1469
  throw new Error('No storage manager initialized');
1172
1470
  for (const plugin of this.plugins) {
@@ -1213,12 +1511,13 @@ export class Matterbridge extends EventEmitter {
1213
1511
  clearInterval(this.startMatterInterval);
1214
1512
  this.startMatterInterval = undefined;
1215
1513
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1514
+ // Configure the plugins
1216
1515
  this.configureTimeout = setTimeout(async () => {
1217
1516
  for (const plugin of this.plugins) {
1218
1517
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1219
1518
  continue;
1220
1519
  try {
1221
- await this.plugins.configure(plugin);
1520
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1222
1521
  }
1223
1522
  catch (error) {
1224
1523
  plugin.error = true;
@@ -1246,7 +1545,9 @@ export class Matterbridge extends EventEmitter {
1246
1545
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1247
1546
  continue;
1248
1547
  }
1548
+ // Start the Matter server node
1249
1549
  this.startServerNode(plugin.serverNode);
1550
+ // Setting reachability to true
1250
1551
  plugin.reachabilityTimeout = setTimeout(() => {
1251
1552
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1252
1553
  if (plugin.serverNode)
@@ -1260,9 +1561,219 @@ export class Matterbridge extends EventEmitter {
1260
1561
  }
1261
1562
  }, 1000);
1262
1563
  }
1564
+ /**
1565
+ * Starts the Matterbridge controller.
1566
+ * @private
1567
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1568
+ */
1263
1569
  async startController() {
1570
+ /*
1571
+ if (!this.storageManager) {
1572
+ this.log.error('No storage manager initialized');
1573
+ await this.cleanup('No storage manager initialized');
1574
+ return;
1575
+ }
1576
+ this.log.info('Creating context: mattercontrollerContext');
1577
+ this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
1578
+ if (!this.mattercontrollerContext) {
1579
+ this.log.error('No storage context mattercontrollerContext initialized');
1580
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1581
+ return;
1582
+ }
1583
+
1584
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1585
+ this.matterServer = await this.createMatterServer(this.storageManager);
1586
+ this.log.info('Creating matter commissioning controller');
1587
+ this.commissioningController = new CommissioningController({
1588
+ autoConnect: false,
1589
+ });
1590
+ this.log.info('Adding matter commissioning controller to matter server');
1591
+ await this.matterServer.addCommissioningController(this.commissioningController);
1592
+
1593
+ this.log.info('Starting matter server');
1594
+ await this.matterServer.start();
1595
+ this.log.info('Matter server started');
1596
+
1597
+ if (hasParameter('pairingcode')) {
1598
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1599
+ const pairingCode = getParameter('pairingcode');
1600
+ const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
1601
+ const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
1602
+
1603
+ let longDiscriminator, setupPin, shortDiscriminator;
1604
+ if (pairingCode !== undefined) {
1605
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1606
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1607
+ longDiscriminator = undefined;
1608
+ setupPin = pairingCodeCodec.passcode;
1609
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1610
+ } else {
1611
+ longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1612
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1613
+ setupPin = this.mattercontrollerContext.get('pin', 20202021);
1614
+ }
1615
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1616
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1617
+ }
1618
+
1619
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1620
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1621
+ regulatoryCountryCode: 'XX',
1622
+ };
1623
+ const options = {
1624
+ commissioning: commissioningOptions,
1625
+ discovery: {
1626
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1627
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1628
+ },
1629
+ passcode: setupPin,
1630
+ } as NodeCommissioningOptions;
1631
+ this.log.info('Commissioning with options:', options);
1632
+ const nodeId = await this.commissioningController.commissionNode(options);
1633
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1634
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1635
+ } // (hasParameter('pairingcode'))
1636
+
1637
+ if (hasParameter('unpairall')) {
1638
+ this.log.info('***Commissioning controller unpairing all nodes...');
1639
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1640
+ for (const nodeId of nodeIds) {
1641
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1642
+ await this.commissioningController.removeNode(nodeId);
1643
+ }
1644
+ return;
1645
+ }
1646
+
1647
+ if (hasParameter('discover')) {
1648
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1649
+ // console.log(discover);
1650
+ }
1651
+
1652
+ if (!this.commissioningController.isCommissioned()) {
1653
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1654
+ return;
1655
+ }
1656
+
1657
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1658
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1659
+ for (const nodeId of nodeIds) {
1660
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1661
+
1662
+ const node = await this.commissioningController.connectNode(nodeId, {
1663
+ autoSubscribe: false,
1664
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1665
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1666
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1667
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1668
+ stateInformationCallback: (peerNodeId, info) => {
1669
+ switch (info) {
1670
+ case NodeStateInformation.Connected:
1671
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1672
+ break;
1673
+ case NodeStateInformation.Disconnected:
1674
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1675
+ break;
1676
+ case NodeStateInformation.Reconnecting:
1677
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1678
+ break;
1679
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1680
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1681
+ break;
1682
+ case NodeStateInformation.StructureChanged:
1683
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1684
+ break;
1685
+ case NodeStateInformation.Decommissioned:
1686
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1687
+ break;
1688
+ default:
1689
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1690
+ break;
1691
+ }
1692
+ },
1693
+ });
1694
+
1695
+ node.logStructure();
1696
+
1697
+ // Get the interaction client
1698
+ this.log.info('Getting the interaction client');
1699
+ const interactionClient = await node.getInteractionClient();
1700
+ let cluster;
1701
+ let attributes;
1702
+
1703
+ // Log BasicInformationCluster
1704
+ cluster = BasicInformationCluster;
1705
+ attributes = await interactionClient.getMultipleAttributes({
1706
+ attributes: [{ clusterId: cluster.id }],
1707
+ });
1708
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1709
+ attributes.forEach((attribute) => {
1710
+ this.log.info(
1711
+ `- 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}`,
1712
+ );
1713
+ });
1714
+
1715
+ // Log PowerSourceCluster
1716
+ cluster = PowerSourceCluster;
1717
+ attributes = await interactionClient.getMultipleAttributes({
1718
+ attributes: [{ clusterId: cluster.id }],
1719
+ });
1720
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1721
+ attributes.forEach((attribute) => {
1722
+ this.log.info(
1723
+ `- 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}`,
1724
+ );
1725
+ });
1726
+
1727
+ // Log ThreadNetworkDiagnostics
1728
+ cluster = ThreadNetworkDiagnosticsCluster;
1729
+ attributes = await interactionClient.getMultipleAttributes({
1730
+ attributes: [{ clusterId: cluster.id }],
1731
+ });
1732
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1733
+ attributes.forEach((attribute) => {
1734
+ this.log.info(
1735
+ `- 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}`,
1736
+ );
1737
+ });
1738
+
1739
+ // Log SwitchCluster
1740
+ cluster = SwitchCluster;
1741
+ attributes = await interactionClient.getMultipleAttributes({
1742
+ attributes: [{ clusterId: cluster.id }],
1743
+ });
1744
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1745
+ attributes.forEach((attribute) => {
1746
+ this.log.info(
1747
+ `- 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}`,
1748
+ );
1749
+ });
1750
+
1751
+ this.log.info('Subscribing to all attributes and events');
1752
+ await node.subscribeAllAttributesAndEvents({
1753
+ ignoreInitialTriggers: false,
1754
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1755
+ this.log.info(
1756
+ `***${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}`,
1757
+ ),
1758
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1759
+ this.log.info(
1760
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1761
+ );
1762
+ },
1763
+ });
1764
+ this.log.info('Subscribed to all attributes and events');
1765
+ }
1766
+ */
1264
1767
  }
1768
+ /** ***********************************************************************************************************************************/
1769
+ /** Matter.js methods */
1770
+ /** ***********************************************************************************************************************************/
1771
+ /**
1772
+ * Starts the matter storage process with name Matterbridge.
1773
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1774
+ */
1265
1775
  async startMatterStorage() {
1776
+ // Setup Matter storage
1266
1777
  this.log.info(`Starting matter node storage...`);
1267
1778
  this.matterStorageService = this.environment.get(StorageService);
1268
1779
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1270,13 +1781,25 @@ export class Matterbridge extends EventEmitter {
1270
1781
  this.log.info('Matter node storage manager "Matterbridge" created');
1271
1782
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
1272
1783
  this.log.info('Matter node storage started');
1784
+ // Backup matter storage since it is created/opened correctly
1273
1785
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1274
1786
  }
1787
+ /**
1788
+ * Makes a backup copy of the specified matter storage directory.
1789
+ *
1790
+ * @param storageName - The name of the storage directory to be backed up.
1791
+ * @param backupName - The name of the backup directory to be created.
1792
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1793
+ */
1275
1794
  async backupMatterStorage(storageName, backupName) {
1276
1795
  this.log.info('Creating matter node storage backup...');
1277
1796
  await copyDirectory(storageName, backupName);
1278
1797
  this.log.info('Created matter node storage backup');
1279
1798
  }
1799
+ /**
1800
+ * Stops the matter storage.
1801
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1802
+ */
1280
1803
  async stopMatterStorage() {
1281
1804
  this.log.info('Closing matter node storage...');
1282
1805
  this.matterStorageManager?.close();
@@ -1285,6 +1808,19 @@ export class Matterbridge extends EventEmitter {
1285
1808
  this.matterbridgeContext = undefined;
1286
1809
  this.log.info('Matter node storage closed');
1287
1810
  }
1811
+ /**
1812
+ * Creates a server node storage context.
1813
+ *
1814
+ * @param {string} pluginName - The name of the plugin.
1815
+ * @param {string} deviceName - The name of the device.
1816
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1817
+ * @param {number} vendorId - The vendor ID.
1818
+ * @param {string} vendorName - The vendor name.
1819
+ * @param {number} productId - The product ID.
1820
+ * @param {string} productName - The product name.
1821
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1822
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1823
+ */
1288
1824
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1289
1825
  if (!this.matterStorageService)
1290
1826
  throw new Error('No storage service initialized');
@@ -1317,6 +1853,15 @@ export class Matterbridge extends EventEmitter {
1317
1853
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1318
1854
  return storageContext;
1319
1855
  }
1856
+ /**
1857
+ * Creates a server node.
1858
+ *
1859
+ * @param {StorageContext} storageContext - The storage context for the server node.
1860
+ * @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
1861
+ * @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
1862
+ * @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
1863
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1864
+ */
1320
1865
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1321
1866
  const storeId = await storageContext.get('storeId');
1322
1867
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1326,21 +1871,33 @@ export class Matterbridge extends EventEmitter {
1326
1871
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1327
1872
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1328
1873
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1874
+ /**
1875
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1876
+ */
1329
1877
  const serverNode = await ServerNode.create({
1878
+ // Required: Give the Node a unique ID which is used to store the state of this node
1330
1879
  id: storeId,
1880
+ // Provide Network relevant configuration like the port
1881
+ // Optional when operating only one device on a host, Default port is 5540
1331
1882
  network: {
1332
1883
  listeningAddressIpv4: this.ipv4address,
1333
1884
  listeningAddressIpv6: this.ipv6address,
1334
1885
  port,
1335
1886
  },
1887
+ // Provide Commissioning relevant settings
1888
+ // Optional for development/testing purposes
1336
1889
  commissioning: {
1337
1890
  passcode,
1338
1891
  discriminator,
1339
1892
  },
1893
+ // Provide Node announcement settings
1894
+ // Optional: If Ommitted some development defaults are used
1340
1895
  productDescription: {
1341
1896
  name: await storageContext.get('deviceName'),
1342
1897
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1343
1898
  },
1899
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1900
+ // Optional: If Omitted some development defaults are used
1344
1901
  basicInformation: {
1345
1902
  vendorId: VendorId(await storageContext.get('vendorId')),
1346
1903
  vendorName: await storageContext.get('vendorName'),
@@ -1357,12 +1914,13 @@ export class Matterbridge extends EventEmitter {
1357
1914
  },
1358
1915
  });
1359
1916
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
1917
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1360
1918
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1361
1919
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1362
1920
  if (this.bridgeMode === 'bridge') {
1363
1921
  this.matterbridgeFabricInformations = sanitizedFabrics;
1364
1922
  if (resetSessions)
1365
- this.matterbridgeSessionInformations = undefined;
1923
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1366
1924
  this.matterbridgePaired = true;
1367
1925
  }
1368
1926
  if (this.bridgeMode === 'childbridge') {
@@ -1370,13 +1928,19 @@ export class Matterbridge extends EventEmitter {
1370
1928
  if (plugin) {
1371
1929
  plugin.fabricInformations = sanitizedFabrics;
1372
1930
  if (resetSessions)
1373
- plugin.sessionInformations = undefined;
1931
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1374
1932
  plugin.paired = true;
1375
1933
  }
1376
1934
  }
1377
1935
  };
1936
+ /**
1937
+ * This event is triggered when the device is initially commissioned successfully.
1938
+ * This means: It is added to the first fabric.
1939
+ */
1378
1940
  serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
1941
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1379
1942
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1943
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1380
1944
  serverNode.lifecycle.online.on(async () => {
1381
1945
  this.log.notice(`Server node for ${storeId} is online`);
1382
1946
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1422,6 +1986,7 @@ export class Matterbridge extends EventEmitter {
1422
1986
  }
1423
1987
  this.frontend.wssSendRefreshRequired();
1424
1988
  });
1989
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1425
1990
  serverNode.lifecycle.offline.on(() => {
1426
1991
  this.log.notice(`Server node for ${storeId} is offline`);
1427
1992
  if (this.bridgeMode === 'bridge') {
@@ -1443,6 +2008,10 @@ export class Matterbridge extends EventEmitter {
1443
2008
  }
1444
2009
  this.frontend.wssSendRefreshRequired();
1445
2010
  });
2011
+ /**
2012
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2013
+ * information is needed.
2014
+ */
1446
2015
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1447
2016
  let action = '';
1448
2017
  switch (fabricAction) {
@@ -1476,16 +2045,24 @@ export class Matterbridge extends EventEmitter {
1476
2045
  }
1477
2046
  }
1478
2047
  };
2048
+ /**
2049
+ * This event is triggered when an operative new session was opened by a Controller.
2050
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2051
+ */
1479
2052
  serverNode.events.sessions.opened.on((session) => {
1480
2053
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1481
2054
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1482
2055
  this.frontend.wssSendRefreshRequired();
1483
2056
  });
2057
+ /**
2058
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2059
+ */
1484
2060
  serverNode.events.sessions.closed.on((session) => {
1485
2061
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1486
2062
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1487
2063
  this.frontend.wssSendRefreshRequired();
1488
2064
  });
2065
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1489
2066
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1490
2067
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1491
2068
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1494,38 +2071,61 @@ export class Matterbridge extends EventEmitter {
1494
2071
  this.log.info(`Created server node for ${storeId}`);
1495
2072
  return serverNode;
1496
2073
  }
2074
+ /**
2075
+ * Starts the specified server node.
2076
+ *
2077
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2078
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2079
+ */
1497
2080
  async startServerNode(matterServerNode) {
1498
2081
  if (!matterServerNode)
1499
2082
  return;
1500
2083
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1501
2084
  await matterServerNode.start();
1502
2085
  }
2086
+ /**
2087
+ * Stops the specified server node.
2088
+ *
2089
+ * @param {ServerNode} matterServerNode - The server node to stop.
2090
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2091
+ */
1503
2092
  async stopServerNode(matterServerNode) {
1504
2093
  if (!matterServerNode)
1505
2094
  return;
1506
2095
  this.log.notice(`Closing ${matterServerNode.id} server node`);
2096
+ /*
2097
+ await matterServerNode.close();
2098
+ this.log.info(`Closed ${matterServerNode.id} server node`);
2099
+ */
2100
+ // Helper function to add a timeout to a promise
1507
2101
  const withTimeout = (promise, ms) => {
1508
2102
  return new Promise((resolve, reject) => {
1509
2103
  const timer = setTimeout(() => reject(new Error('Operation timed out')), ms);
1510
2104
  promise
1511
2105
  .then((result) => {
1512
- clearTimeout(timer);
2106
+ clearTimeout(timer); // Prevent memory leak
1513
2107
  resolve(result);
1514
2108
  })
1515
2109
  .catch((error) => {
1516
- clearTimeout(timer);
2110
+ clearTimeout(timer); // Ensure timeout does not fire if promise rejects first
1517
2111
  reject(error);
1518
2112
  });
1519
2113
  });
1520
2114
  };
1521
2115
  try {
1522
- await withTimeout(matterServerNode.close(), 5000);
2116
+ await withTimeout(matterServerNode.close(), 5000); // 5 seconds timeout
1523
2117
  this.log.info(`Closed ${matterServerNode.id} server node`);
1524
2118
  }
1525
2119
  catch (error) {
1526
2120
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1527
2121
  }
1528
2122
  }
2123
+ /**
2124
+ * Advertises the specified server node if it is commissioned.
2125
+ *
2126
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2127
+ * @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.
2128
+ */
1529
2129
  async advertiseServerNode(matterServerNode) {
1530
2130
  if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
1531
2131
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1535,17 +2135,32 @@ export class Matterbridge extends EventEmitter {
1535
2135
  }
1536
2136
  return undefined;
1537
2137
  }
2138
+ /**
2139
+ * Creates an aggregator node with the specified storage context.
2140
+ *
2141
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2142
+ * @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2143
+ */
1538
2144
  async createAggregatorNode(storageContext) {
1539
2145
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
1540
2146
  const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1541
2147
  return aggregatorNode;
1542
2148
  }
2149
+ /**
2150
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2151
+ *
2152
+ * @param {string} pluginName - The name of the plugin.
2153
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2154
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2155
+ */
1543
2156
  async addBridgedEndpoint(pluginName, device) {
2157
+ // Check if the plugin is registered
1544
2158
  const plugin = this.plugins.get(pluginName);
1545
2159
  if (!plugin) {
1546
2160
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1547
2161
  return;
1548
2162
  }
2163
+ // Register and add the device to the matterbridge aggregator node
1549
2164
  if (this.bridgeMode === 'bridge') {
1550
2165
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1551
2166
  if (!this.aggregatorNode)
@@ -1568,16 +2183,26 @@ export class Matterbridge extends EventEmitter {
1568
2183
  plugin.registeredDevices++;
1569
2184
  if (plugin.addedDevices !== undefined)
1570
2185
  plugin.addedDevices++;
2186
+ // Add the device to the DeviceManager
1571
2187
  this.devices.set(device);
1572
2188
  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}`);
1573
2189
  }
2190
+ /**
2191
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2192
+ *
2193
+ * @param {string} pluginName - The name of the plugin.
2194
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2195
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2196
+ */
1574
2197
  async removeBridgedEndpoint(pluginName, device) {
1575
2198
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2199
+ // Check if the plugin is registered
1576
2200
  const plugin = this.plugins.get(pluginName);
1577
2201
  if (!plugin) {
1578
2202
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1579
2203
  return;
1580
2204
  }
2205
+ // Register and add the device to the matterbridge aggregator node
1581
2206
  if (this.bridgeMode === 'bridge') {
1582
2207
  if (!this.aggregatorNode) {
1583
2208
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1592,6 +2217,7 @@ export class Matterbridge extends EventEmitter {
1592
2217
  }
1593
2218
  else if (this.bridgeMode === 'childbridge') {
1594
2219
  if (plugin.type === 'AccessoryPlatform') {
2220
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1595
2221
  }
1596
2222
  else if (plugin.type === 'DynamicPlatform') {
1597
2223
  if (!plugin.aggregatorNode) {
@@ -1605,6 +2231,7 @@ export class Matterbridge extends EventEmitter {
1605
2231
  plugin.registeredDevices--;
1606
2232
  if (plugin.addedDevices !== undefined)
1607
2233
  plugin.addedDevices--;
2234
+ // Close the server node TODO check if this is correct
1608
2235
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
1609
2236
  if (plugin.serverNode) {
1610
2237
  await this.stopServerNode(plugin.serverNode);
@@ -1615,14 +2242,27 @@ export class Matterbridge extends EventEmitter {
1615
2242
  }
1616
2243
  }
1617
2244
  }
2245
+ // Remove the device from the DeviceManager
1618
2246
  this.devices.remove(device);
1619
2247
  }
2248
+ /**
2249
+ * Removes all bridged endpoints from the specified plugin.
2250
+ *
2251
+ * @param {string} pluginName - The name of the plugin.
2252
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2253
+ */
1620
2254
  async removeAllBridgedEndpoints(pluginName) {
1621
2255
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
1622
2256
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
1623
2257
  await this.removeBridgedEndpoint(pluginName, device);
1624
2258
  }
1625
2259
  }
2260
+ /**
2261
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2262
+ *
2263
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2264
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2265
+ */
1626
2266
  sanitizeFabricInformations(fabricInfo) {
1627
2267
  return fabricInfo.map((info) => {
1628
2268
  return {
@@ -1636,6 +2276,12 @@ export class Matterbridge extends EventEmitter {
1636
2276
  };
1637
2277
  });
1638
2278
  }
2279
+ /**
2280
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2281
+ *
2282
+ * @param {SessionInformation[]} sessionInfo - The array of session information objects.
2283
+ * @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
2284
+ */
1639
2285
  sanitizeSessionInformation(sessionInfo) {
1640
2286
  return sessionInfo
1641
2287
  .filter((session) => session.isPeerActive)
@@ -1663,11 +2309,51 @@ export class Matterbridge extends EventEmitter {
1663
2309
  };
1664
2310
  });
1665
2311
  }
2312
+ /**
2313
+ * Sets the reachability of a matter server node and trigger ReachableChanged event.
2314
+ *
2315
+ * @param {ServerNode<ServerNode.RootEndpoint>} serverNode - The commissioning server to set the reachability for.
2316
+ * @param {boolean} reachable - The new reachability status.
2317
+ */
2318
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1666
2319
  setServerNodeReachability(serverNode, reachable) {
2320
+ /*
2321
+ const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
2322
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2323
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2324
+ */
1667
2325
  }
2326
+ /**
2327
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2328
+ * @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The matter aggregator to set the reachability for.
2329
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2330
+ */
2331
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1668
2332
  setAggregatorReachability(aggregatorNode, reachable) {
2333
+ /*
2334
+ const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
2335
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2336
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2337
+ matterAggregator.getBridgedDevices().forEach((device) => {
2338
+ this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
2339
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
2340
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2341
+ });
2342
+ */
1669
2343
  }
2344
+ /**
2345
+ * Sets the reachability of a device and trigger.
2346
+ *
2347
+ * @param {MatterbridgeEndpoint} device - The device to set the reachability for.
2348
+ * @param {boolean} reachable - The new reachability status of the device.
2349
+ */
2350
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1670
2351
  setDeviceReachability(device, reachable) {
2352
+ /*
2353
+ const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
2354
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2355
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2356
+ */
1671
2357
  }
1672
2358
  getVendorIdName = (vendorId) => {
1673
2359
  if (!vendorId)
@@ -1710,13 +2396,36 @@ export class Matterbridge extends EventEmitter {
1710
2396
  }
1711
2397
  return vendorName;
1712
2398
  };
2399
+ /**
2400
+ * Spawns a child process with the given command and arguments.
2401
+ * @param {string} command - The command to execute.
2402
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2403
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2404
+ */
1713
2405
  async spawnCommand(command, args = []) {
2406
+ /*
2407
+ npm > npm.cmd on windows
2408
+ cmd.exe ['dir'] on windows
2409
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2410
+ process.on('unhandledRejection', (reason, promise) => {
2411
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2412
+ });
2413
+
2414
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2415
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2416
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2417
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2418
+ */
1714
2419
  const cmdLine = command + ' ' + args.join(' ');
1715
2420
  if (process.platform === 'win32' && command === 'npm') {
2421
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
1716
2422
  const argstring = 'npm ' + args.join(' ');
1717
2423
  args.splice(0, args.length, '/c', argstring);
1718
2424
  command = 'cmd.exe';
1719
2425
  }
2426
+ // Decide when using sudo on linux
2427
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2428
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
1720
2429
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
1721
2430
  args.unshift(command);
1722
2431
  command = 'sudo';
@@ -1775,3 +2484,4 @@ export class Matterbridge extends EventEmitter {
1775
2484
  });
1776
2485
  }
1777
2486
  }
2487
+ //# sourceMappingURL=matterbridge.js.map