matterbridge 2.1.1-dev.2 → 2.1.1

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 (111) hide show
  1. package/CHANGELOG.md +3 -2
  2. package/dist/cli.d.ts +25 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +26 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cluster/export.d.ts +2 -0
  7. package/dist/cluster/export.d.ts.map +1 -0
  8. package/dist/cluster/export.js +2 -0
  9. package/dist/cluster/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts +27 -0
  11. package/dist/defaultConfigSchema.d.ts.map +1 -0
  12. package/dist/defaultConfigSchema.js +23 -0
  13. package/dist/defaultConfigSchema.js.map +1 -0
  14. package/dist/deviceManager.d.ts +46 -0
  15. package/dist/deviceManager.d.ts.map +1 -0
  16. package/dist/deviceManager.js +26 -1
  17. package/dist/deviceManager.js.map +1 -0
  18. package/dist/frontend.d.ts +109 -0
  19. package/dist/frontend.d.ts.map +1 -0
  20. package/dist/frontend.js +227 -22
  21. package/dist/frontend.js.map +1 -0
  22. package/dist/index.d.ts +35 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +28 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/logger/export.d.ts +2 -0
  27. package/dist/logger/export.d.ts.map +1 -0
  28. package/dist/logger/export.js +1 -0
  29. package/dist/logger/export.js.map +1 -0
  30. package/dist/matter/behaviors.d.ts +2 -0
  31. package/dist/matter/behaviors.d.ts.map +1 -0
  32. package/dist/matter/behaviors.js +2 -0
  33. package/dist/matter/behaviors.js.map +1 -0
  34. package/dist/matter/clusters.d.ts +2 -0
  35. package/dist/matter/clusters.d.ts.map +1 -0
  36. package/dist/matter/clusters.js +2 -0
  37. package/dist/matter/clusters.js.map +1 -0
  38. package/dist/matter/devices.d.ts +2 -0
  39. package/dist/matter/devices.d.ts.map +1 -0
  40. package/dist/matter/devices.js +2 -0
  41. package/dist/matter/devices.js.map +1 -0
  42. package/dist/matter/endpoints.d.ts +2 -0
  43. package/dist/matter/endpoints.d.ts.map +1 -0
  44. package/dist/matter/endpoints.js +2 -0
  45. package/dist/matter/endpoints.js.map +1 -0
  46. package/dist/matter/export.d.ts +4 -0
  47. package/dist/matter/export.d.ts.map +1 -0
  48. package/dist/matter/export.js +2 -0
  49. package/dist/matter/export.js.map +1 -0
  50. package/dist/matter/types.d.ts +3 -0
  51. package/dist/matter/types.d.ts.map +1 -0
  52. package/dist/matter/types.js +2 -0
  53. package/dist/matter/types.js.map +1 -0
  54. package/dist/matterbridge.d.ts +409 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +754 -40
  57. package/dist/matterbridge.js.map +1 -0
  58. package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
  59. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  60. package/dist/matterbridgeAccessoryPlatform.js +33 -0
  61. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  62. package/dist/matterbridgeBehaviors.d.ts +1056 -0
  63. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  64. package/dist/matterbridgeBehaviors.js +32 -1
  65. package/dist/matterbridgeBehaviors.js.map +1 -0
  66. package/dist/matterbridgeDeviceTypes.d.ts +177 -0
  67. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  68. package/dist/matterbridgeDeviceTypes.js +112 -11
  69. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  70. package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
  71. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  72. package/dist/matterbridgeDynamicPlatform.js +33 -0
  73. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  74. package/dist/matterbridgeEndpoint.d.ts +834 -0
  75. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  76. package/dist/matterbridgeEndpoint.js +690 -6
  77. package/dist/matterbridgeEndpoint.js.map +1 -0
  78. package/dist/matterbridgeEndpointHelpers.d.ts +2262 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  80. package/dist/matterbridgeEndpointHelpers.js +96 -0
  81. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  82. package/dist/matterbridgePlatform.d.ts +164 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +123 -3
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +165 -0
  87. package/dist/matterbridgeTypes.d.ts.map +1 -0
  88. package/dist/matterbridgeTypes.js +24 -0
  89. package/dist/matterbridgeTypes.js.map +1 -0
  90. package/dist/pluginManager.d.ts +238 -0
  91. package/dist/pluginManager.d.ts.map +1 -0
  92. package/dist/pluginManager.js +240 -3
  93. package/dist/pluginManager.js.map +1 -0
  94. package/dist/storage/export.d.ts +2 -0
  95. package/dist/storage/export.d.ts.map +1 -0
  96. package/dist/storage/export.js +1 -0
  97. package/dist/storage/export.js.map +1 -0
  98. package/dist/utils/colorUtils.d.ts +61 -0
  99. package/dist/utils/colorUtils.d.ts.map +1 -0
  100. package/dist/utils/colorUtils.js +205 -2
  101. package/dist/utils/colorUtils.js.map +1 -0
  102. package/dist/utils/export.d.ts +3 -0
  103. package/dist/utils/export.d.ts.map +1 -0
  104. package/dist/utils/export.js +1 -0
  105. package/dist/utils/export.js.map +1 -0
  106. package/dist/utils/utils.d.ts +221 -0
  107. package/dist/utils/utils.d.ts.map +1 -0
  108. package/dist/utils/utils.js +251 -7
  109. package/dist/utils/utils.js.map +1 -0
  110. package/npm-shrinkwrap.json +2 -2
  111. 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, 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: '',
@@ -53,7 +83,7 @@ export class Matterbridge extends EventEmitter {
53
83
  restartMode: '',
54
84
  readOnly: hasParameter('readonly'),
55
85
  profile: getParameter('profile'),
56
- loggerLevel: "info",
86
+ loggerLevel: "info" /* LogLevel.INFO */,
57
87
  fileLogger: false,
58
88
  matterLoggerLevel: MatterLogLevel.INFO,
59
89
  matterFileLogger: false,
@@ -88,9 +118,11 @@ export class Matterbridge extends EventEmitter {
88
118
  plugins;
89
119
  devices;
90
120
  frontend = new Frontend(this);
121
+ // Matterbridge storage
91
122
  nodeStorage;
92
123
  nodeContext;
93
124
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
125
+ // Cleanup
94
126
  hasCleanupStarted = false;
95
127
  initialized = false;
96
128
  execRunningCount = 0;
@@ -102,34 +134,57 @@ export class Matterbridge extends EventEmitter {
102
134
  sigtermHandler;
103
135
  exceptionHandler;
104
136
  rejectionHandler;
137
+ // Matter environment
105
138
  environment = Environment.default;
139
+ // Matter storage
106
140
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
107
141
  matterStorageService;
108
142
  matterStorageManager;
109
143
  matterbridgeContext;
110
144
  mattercontrollerContext;
111
- mdnsInterface;
112
- ipv4address;
113
- ipv6address;
114
- port;
115
- passcode;
116
- discriminator;
145
+ // Matter parameters
146
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
147
+ ipv4address; // matter server node listeningAddressIpv4
148
+ ipv6address; // matter server node listeningAddressIpv6
149
+ port; // first server node port
150
+ passcode; // first server node passcode
151
+ discriminator; // first server node discriminator
117
152
  serverNode;
118
153
  aggregatorNode;
119
154
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
120
155
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
121
156
  static instance;
157
+ // We load asyncronously so is private
122
158
  constructor() {
123
159
  super();
124
160
  }
161
+ /**
162
+ * Retrieves the list of Matterbridge devices.
163
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
164
+ */
125
165
  getDevices() {
126
166
  return this.devices.array();
127
167
  }
168
+ /**
169
+ * Retrieves the list of registered plugins.
170
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
171
+ */
128
172
  getPlugins() {
129
173
  return this.plugins.array();
130
174
  }
175
+ /** ***********************************************************************************************************************************/
176
+ /** loadInstance() and cleanup() methods */
177
+ /** ***********************************************************************************************************************************/
178
+ /**
179
+ * Loads an instance of the Matterbridge class.
180
+ * If an instance already exists, return that instance.
181
+ *
182
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
183
+ * @returns The loaded Matterbridge instance.
184
+ */
131
185
  static async loadInstance(initialize = false) {
132
186
  if (!Matterbridge.instance) {
187
+ // eslint-disable-next-line no-console
133
188
  if (hasParameter('debug'))
134
189
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
135
190
  Matterbridge.instance = new Matterbridge();
@@ -138,48 +193,89 @@ export class Matterbridge extends EventEmitter {
138
193
  }
139
194
  return Matterbridge.instance;
140
195
  }
196
+ /**
197
+ * Call cleanup().
198
+ * @deprecated This method is deprecated and is only used for jest tests.
199
+ *
200
+ */
141
201
  async destroyInstance() {
142
202
  await this.cleanup('destroying instance...', false);
203
+ /*
204
+ await waiter(
205
+ 'destroying instance...',
206
+ () => {
207
+ return this.initialized === false && this.execRunningCount <= 0 ? true : false;
208
+ },
209
+ false,
210
+ 60000,
211
+ 100,
212
+ false,
213
+ );
214
+ await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
215
+ */
143
216
  }
217
+ /**
218
+ * Initializes the Matterbridge application.
219
+ *
220
+ * @remarks
221
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
222
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
223
+ * node version, registers signal handlers, initializes storage, and parses the command line.
224
+ *
225
+ * @returns A Promise that resolves when the initialization is complete.
226
+ */
144
227
  async initialize() {
228
+ // Set the restart mode
145
229
  if (hasParameter('service'))
146
230
  this.restartMode = 'service';
147
231
  if (hasParameter('docker'))
148
232
  this.restartMode = 'docker';
233
+ // Set the matterbridge directory
149
234
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
150
235
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
236
+ // Setup matter environment
151
237
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
152
238
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
153
239
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
154
240
  this.environment.vars.set('runtime.signals', false);
155
241
  this.environment.vars.set('runtime.exitcode', false);
156
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
242
+ // Create matterbridge logger
243
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
244
+ // Register process handlers
157
245
  this.registerProcessHandlers();
246
+ // Initialize nodeStorage and nodeContext
158
247
  try {
159
248
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
160
249
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
161
250
  this.log.debug('Creating node storage context for matterbridge');
162
251
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
252
+ // TODO: Remove this code when node-persist-manager is updated
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
254
  const keys = (await this.nodeStorage?.storage.keys());
164
255
  for (const key of keys) {
165
256
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
257
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
258
  await this.nodeStorage?.storage.get(key);
167
259
  }
168
260
  const storages = await this.nodeStorage.getStorageNames();
169
261
  for (const storage of storages) {
170
262
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
171
263
  const nodeContext = await this.nodeStorage?.createStorage(storage);
264
+ // TODO: Remove this code when node-persist-manager is updated
265
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
266
  const keys = (await nodeContext?.storage.keys());
173
267
  keys.forEach(async (key) => {
174
268
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
175
269
  await nodeContext?.get(key);
176
270
  });
177
271
  }
272
+ // Creating a backup of the node storage since it is not corrupted
178
273
  this.log.debug('Creating node storage backup...');
179
274
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
180
275
  this.log.debug('Created node storage backup');
181
276
  }
182
277
  catch (error) {
278
+ // Restoring the backup of the node storage since it is corrupted
183
279
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
184
280
  if (hasParameter('norestore')) {
185
281
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -194,45 +290,51 @@ export class Matterbridge extends EventEmitter {
194
290
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
195
291
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
196
292
  }
293
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
197
294
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
295
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
198
296
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
297
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
199
298
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
200
299
  this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
300
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
201
301
  if (hasParameter('logger')) {
202
302
  const level = getParameter('logger');
203
303
  if (level === 'debug') {
204
- this.log.logLevel = "debug";
304
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
205
305
  }
206
306
  else if (level === 'info') {
207
- this.log.logLevel = "info";
307
+ this.log.logLevel = "info" /* LogLevel.INFO */;
208
308
  }
209
309
  else if (level === 'notice') {
210
- this.log.logLevel = "notice";
310
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
211
311
  }
212
312
  else if (level === 'warn') {
213
- this.log.logLevel = "warn";
313
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
214
314
  }
215
315
  else if (level === 'error') {
216
- this.log.logLevel = "error";
316
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
217
317
  }
218
318
  else if (level === 'fatal') {
219
- this.log.logLevel = "fatal";
319
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
220
320
  }
221
321
  else {
222
322
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
223
- this.log.logLevel = "info";
323
+ this.log.logLevel = "info" /* LogLevel.INFO */;
224
324
  }
225
325
  }
226
326
  else {
227
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
327
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
228
328
  }
229
329
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
330
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
230
331
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
231
332
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
232
333
  this.matterbridgeInformation.fileLogger = true;
233
334
  }
234
335
  this.log.notice('Matterbridge is starting...');
235
336
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
337
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
236
338
  if (hasParameter('matterlogger')) {
237
339
  const level = getParameter('matterlogger');
238
340
  if (level === 'debug') {
@@ -263,6 +365,7 @@ export class Matterbridge extends EventEmitter {
263
365
  }
264
366
  Logger.format = MatterLogFormat.ANSI;
265
367
  Logger.setLogger('default', this.createMatterLogger());
368
+ // Create the file logger for matter.js (context: matterFileLog)
266
369
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
267
370
  this.matterbridgeInformation.matterFileLogger = true;
268
371
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -271,6 +374,7 @@ export class Matterbridge extends EventEmitter {
271
374
  });
272
375
  }
273
376
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
377
+ // Set the interface to use for matter server node mdnsInterface
274
378
  if (hasParameter('mdnsinterface')) {
275
379
  this.mdnsInterface = getParameter('mdnsinterface');
276
380
  }
@@ -279,6 +383,7 @@ export class Matterbridge extends EventEmitter {
279
383
  if (this.mdnsInterface === '')
280
384
  this.mdnsInterface = undefined;
281
385
  }
386
+ // Validate mdnsInterface
282
387
  if (this.mdnsInterface) {
283
388
  const networkInterfaces = os.networkInterfaces();
284
389
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -292,6 +397,7 @@ export class Matterbridge extends EventEmitter {
292
397
  }
293
398
  if (this.mdnsInterface)
294
399
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
400
+ // Set the listeningAddressIpv4 for the matter commissioning server
295
401
  if (hasParameter('ipv4address')) {
296
402
  this.ipv4address = getParameter('ipv4address');
297
403
  }
@@ -300,6 +406,7 @@ export class Matterbridge extends EventEmitter {
300
406
  if (this.ipv4address === '')
301
407
  this.ipv4address = undefined;
302
408
  }
409
+ // Set the listeningAddressIpv6 for the matter commissioning server
303
410
  if (hasParameter('ipv6address')) {
304
411
  this.ipv6address = getParameter('ipv6address');
305
412
  }
@@ -308,14 +415,19 @@ export class Matterbridge extends EventEmitter {
308
415
  if (this.ipv6address === '')
309
416
  this.ipv6address = undefined;
310
417
  }
418
+ // Initialize PluginManager
311
419
  this.plugins = new PluginManager(this);
312
420
  await this.plugins.loadFromStorage();
313
421
  this.plugins.logLevel = this.log.logLevel;
422
+ // Initialize DeviceManager
314
423
  this.devices = new DeviceManager(this, this.nodeContext);
315
424
  this.devices.logLevel = this.log.logLevel;
425
+ // Get the plugins from node storage and create the plugins node storage contexts
316
426
  for (const plugin of this.plugins) {
317
427
  const packageJson = await this.plugins.parse(plugin);
318
428
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
429
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
430
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
319
431
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
320
432
  try {
321
433
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -337,6 +449,7 @@ export class Matterbridge extends EventEmitter {
337
449
  await plugin.nodeContext.set('description', plugin.description);
338
450
  await plugin.nodeContext.set('author', plugin.author);
339
451
  }
452
+ // Log system info and create .matterbridge directory
340
453
  await this.logNodeAndSystemInfo();
341
454
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
342
455
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -344,6 +457,7 @@ export class Matterbridge extends EventEmitter {
344
457
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
345
458
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
346
459
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
460
+ // Check node version and throw error
347
461
  const minNodeVersion = 18;
348
462
  const nodeVersion = process.versions.node;
349
463
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -351,9 +465,15 @@ export class Matterbridge extends EventEmitter {
351
465
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
352
466
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
353
467
  }
468
+ // Parse command line
354
469
  await this.parseCommandLine();
355
470
  this.initialized = true;
356
471
  }
472
+ /**
473
+ * Parses the command line arguments and performs the corresponding actions.
474
+ * @private
475
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
476
+ */
357
477
  async parseCommandLine() {
358
478
  if (hasParameter('help')) {
359
479
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -463,6 +583,7 @@ export class Matterbridge extends EventEmitter {
463
583
  await this.shutdownProcessAndFactoryReset();
464
584
  return;
465
585
  }
586
+ // Start the matter storage and create the matterbridge context
466
587
  try {
467
588
  await this.startMatterStorage();
468
589
  }
@@ -470,10 +591,12 @@ export class Matterbridge extends EventEmitter {
470
591
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
471
592
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
472
593
  }
594
+ // Clear the matterbridge context if the reset parameter is set
473
595
  if (hasParameter('reset') && getParameter('reset') === undefined) {
474
596
  await this.shutdownProcessAndReset();
475
597
  return;
476
598
  }
599
+ // Clear matterbridge plugin context if the reset parameter is set
477
600
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
478
601
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
479
602
  const plugin = this.plugins.get(getParameter('reset'));
@@ -495,9 +618,11 @@ export class Matterbridge extends EventEmitter {
495
618
  this.emit('shutdown');
496
619
  return;
497
620
  }
621
+ // Initialize frontend
498
622
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
499
623
  await this.frontend.start(getIntParameter('frontend'));
500
624
  this.frontend.logLevel = this.log.logLevel;
625
+ // Check each 60 minutes the latest versions
501
626
  this.checkUpdateInterval = setInterval(() => {
502
627
  this.getMatterbridgeLatestVersion();
503
628
  for (const plugin of this.plugins) {
@@ -505,20 +630,24 @@ export class Matterbridge extends EventEmitter {
505
630
  }
506
631
  this.frontend.wssSendRefreshRequired();
507
632
  }, 60 * 60 * 1000);
633
+ // Start the matterbridge in mode test
508
634
  if (hasParameter('test')) {
509
635
  this.bridgeMode = 'bridge';
510
636
  MatterbridgeEndpoint.bridgeMode = 'bridge';
511
637
  return;
512
638
  }
639
+ // Start the matterbridge in mode controller
513
640
  if (hasParameter('controller')) {
514
641
  this.bridgeMode = 'controller';
515
642
  await this.startController();
516
643
  return;
517
644
  }
645
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
518
646
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
519
647
  this.log.info('Setting default matterbridge start mode to bridge');
520
648
  await this.nodeContext?.set('bridgeMode', 'bridge');
521
649
  }
650
+ // Start matterbridge in bridge mode
522
651
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
523
652
  this.bridgeMode = 'bridge';
524
653
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -526,6 +655,7 @@ export class Matterbridge extends EventEmitter {
526
655
  await this.startBridge();
527
656
  return;
528
657
  }
658
+ // Start matterbridge in childbridge mode
529
659
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
530
660
  this.bridgeMode = 'childbridge';
531
661
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -534,16 +664,28 @@ export class Matterbridge extends EventEmitter {
534
664
  return;
535
665
  }
536
666
  }
667
+ /**
668
+ * Asynchronously loads and starts the registered plugins.
669
+ *
670
+ * This method is responsible for initializing and staarting all enabled plugins.
671
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
672
+ *
673
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
674
+ */
537
675
  async startPlugins() {
676
+ // Check, load and start the plugins
538
677
  for (const plugin of this.plugins) {
539
678
  plugin.configJson = await this.plugins.loadConfig(plugin);
540
679
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
680
+ // Check if the plugin is available
541
681
  if (!(await this.plugins.resolve(plugin.path))) {
542
682
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
543
683
  plugin.enabled = false;
544
684
  plugin.error = true;
545
685
  continue;
546
686
  }
687
+ // Check if the plugin has a new version
688
+ // this.getPluginLatestVersion(plugin); // No await do it asyncronously
547
689
  if (!plugin.enabled) {
548
690
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
549
691
  continue;
@@ -557,20 +699,26 @@ export class Matterbridge extends EventEmitter {
557
699
  plugin.addedDevices = undefined;
558
700
  plugin.qrPairingCode = undefined;
559
701
  plugin.manualPairingCode = undefined;
560
- this.plugins.load(plugin, true, 'Matterbridge is starting');
702
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
561
703
  }
562
704
  this.frontend.wssSendRefreshRequired();
563
705
  }
706
+ /**
707
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
708
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
709
+ */
564
710
  registerProcessHandlers() {
565
711
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
566
712
  process.removeAllListeners('uncaughtException');
567
713
  process.removeAllListeners('unhandledRejection');
568
714
  this.exceptionHandler = async (error) => {
569
715
  this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
716
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
570
717
  };
571
718
  process.on('uncaughtException', this.exceptionHandler);
572
719
  this.rejectionHandler = async (reason, promise) => {
573
720
  this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
721
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
574
722
  };
575
723
  process.on('unhandledRejection', this.rejectionHandler);
576
724
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -583,6 +731,9 @@ export class Matterbridge extends EventEmitter {
583
731
  };
584
732
  process.on('SIGTERM', this.sigtermHandler);
585
733
  }
734
+ /**
735
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
736
+ */
586
737
  deregisterProcesslHandlers() {
587
738
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
588
739
  if (this.exceptionHandler)
@@ -599,12 +750,17 @@ export class Matterbridge extends EventEmitter {
599
750
  process.off('SIGTERM', this.sigtermHandler);
600
751
  this.sigtermHandler = undefined;
601
752
  }
753
+ /**
754
+ * Logs the node and system information.
755
+ */
602
756
  async logNodeAndSystemInfo() {
757
+ // IP address information
603
758
  const networkInterfaces = os.networkInterfaces();
604
759
  this.systemInformation.interfaceName = '';
605
760
  this.systemInformation.ipv4Address = '';
606
761
  this.systemInformation.ipv6Address = '';
607
762
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
763
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
608
764
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
609
765
  continue;
610
766
  if (!interfaceDetails) {
@@ -630,19 +786,22 @@ export class Matterbridge extends EventEmitter {
630
786
  break;
631
787
  }
632
788
  }
789
+ // Node information
633
790
  this.systemInformation.nodeVersion = process.versions.node;
634
791
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
635
792
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
636
793
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
794
+ // Host system information
637
795
  this.systemInformation.hostname = os.hostname();
638
796
  this.systemInformation.user = os.userInfo().username;
639
- this.systemInformation.osType = os.type();
640
- this.systemInformation.osRelease = os.release();
641
- this.systemInformation.osPlatform = os.platform();
642
- this.systemInformation.osArch = os.arch();
643
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
644
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
645
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
797
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
798
+ this.systemInformation.osRelease = os.release(); // Kernel version
799
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
800
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
801
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
802
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
803
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
804
+ // Log the system information
646
805
  this.log.debug('Host System Information:');
647
806
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
648
807
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -658,15 +817,19 @@ export class Matterbridge extends EventEmitter {
658
817
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
659
818
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
660
819
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
820
+ // Home directory
661
821
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
662
822
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
663
823
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
824
+ // Package root directory
664
825
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
665
826
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
666
827
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
667
828
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
829
+ // Global node_modules directory
668
830
  if (this.nodeContext)
669
831
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
832
+ // First run of Matterbridge so the node storage is empty
670
833
  if (this.globalModulesDirectory === '') {
671
834
  try {
672
835
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -680,6 +843,20 @@ export class Matterbridge extends EventEmitter {
680
843
  }
681
844
  else
682
845
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
846
+ /* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
847
+ else {
848
+ this.getGlobalNodeModules()
849
+ .then(async (globalModulesDirectory) => {
850
+ this.globalModulesDirectory = globalModulesDirectory;
851
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
852
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
853
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
854
+ })
855
+ .catch((error) => {
856
+ this.log.error(`Error getting global node_modules directory: ${error}`);
857
+ });
858
+ }*/
859
+ // Create the data directory .matterbridge in the home directory
683
860
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
684
861
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
685
862
  try {
@@ -703,6 +880,7 @@ export class Matterbridge extends EventEmitter {
703
880
  }
704
881
  }
705
882
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
883
+ // Create the plugin directory Matterbridge in the home directory
706
884
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
707
885
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
708
886
  try {
@@ -726,18 +904,28 @@ export class Matterbridge extends EventEmitter {
726
904
  }
727
905
  }
728
906
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
907
+ // Matterbridge version
729
908
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
730
909
  this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
731
910
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
732
911
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
912
+ // Matterbridge latest version
733
913
  if (this.nodeContext)
734
914
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
735
915
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
916
+ // this.getMatterbridgeLatestVersion();
917
+ // Current working directory
736
918
  const currentDir = process.cwd();
737
919
  this.log.debug(`Current Working Directory: ${currentDir}`);
920
+ // Command line arguments (excluding 'node' and the script name)
738
921
  const cmdArgs = process.argv.slice(2).join(' ');
739
922
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
740
923
  }
924
+ /**
925
+ * Retrieves the latest version of a package from the npm registry.
926
+ * @param packageName - The name of the package.
927
+ * @returns A Promise that resolves to the latest version of the package.
928
+ */
741
929
  async getLatestVersion(packageName) {
742
930
  return new Promise((resolve, reject) => {
743
931
  this.execRunningCount++;
@@ -752,6 +940,10 @@ export class Matterbridge extends EventEmitter {
752
940
  });
753
941
  });
754
942
  }
943
+ /**
944
+ * Retrieves the path to the global Node.js modules directory.
945
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
946
+ */
755
947
  async getGlobalNodeModules() {
756
948
  return new Promise((resolve, reject) => {
757
949
  this.execRunningCount++;
@@ -766,6 +958,11 @@ export class Matterbridge extends EventEmitter {
766
958
  });
767
959
  });
768
960
  }
961
+ /**
962
+ * Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
963
+ * @private
964
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
965
+ */
769
966
  async getMatterbridgeLatestVersion() {
770
967
  this.getLatestVersion('matterbridge')
771
968
  .then(async (matterbridgeLatestVersion) => {
@@ -782,8 +979,19 @@ export class Matterbridge extends EventEmitter {
782
979
  })
783
980
  .catch((error) => {
784
981
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
982
+ // error.stack && this.log.debug(error.stack);
785
983
  });
786
984
  }
985
+ /**
986
+ * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
987
+ * If the plugin's version is different from the latest version, logs a warning message.
988
+ * If the plugin's version is the same as the latest version, logs an info message.
989
+ * If there is an error retrieving the latest version, logs an error message.
990
+ *
991
+ * @private
992
+ * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
993
+ * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
994
+ */
787
995
  async getPluginLatestVersion(plugin) {
788
996
  this.getLatestVersion(plugin.name)
789
997
  .then(async (latestVersion) => {
@@ -795,40 +1003,54 @@ export class Matterbridge extends EventEmitter {
795
1003
  })
796
1004
  .catch((error) => {
797
1005
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
1006
+ // error.stack && this.log.debug(error.stack);
798
1007
  });
799
1008
  }
1009
+ /**
1010
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1011
+ *
1012
+ * @returns {Function} The MatterLogger function.
1013
+ */
800
1014
  createMatterLogger() {
801
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1015
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
802
1016
  return (_level, formattedLog) => {
803
1017
  const logger = formattedLog.slice(44, 44 + 20).trim();
804
1018
  const message = formattedLog.slice(65);
805
1019
  matterLogger.logName = logger;
806
1020
  switch (_level) {
807
1021
  case MatterLogLevel.DEBUG:
808
- matterLogger.log("debug", message);
1022
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
809
1023
  break;
810
1024
  case MatterLogLevel.INFO:
811
- matterLogger.log("info", message);
1025
+ matterLogger.log("info" /* LogLevel.INFO */, message);
812
1026
  break;
813
1027
  case MatterLogLevel.NOTICE:
814
- matterLogger.log("notice", message);
1028
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
815
1029
  break;
816
1030
  case MatterLogLevel.WARN:
817
- matterLogger.log("warn", message);
1031
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
818
1032
  break;
819
1033
  case MatterLogLevel.ERROR:
820
- matterLogger.log("error", message);
1034
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
821
1035
  break;
822
1036
  case MatterLogLevel.FATAL:
823
- matterLogger.log("fatal", message);
1037
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
824
1038
  break;
825
1039
  default:
826
- matterLogger.log("debug", message);
1040
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
827
1041
  break;
828
1042
  }
829
1043
  };
830
1044
  }
1045
+ /**
1046
+ * Creates a Matter File Logger.
1047
+ *
1048
+ * @param {string} filePath - The path to the log file.
1049
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1050
+ * @returns {Function} - A function that logs formatted messages to the log file.
1051
+ */
831
1052
  async createMatterFileLogger(filePath, unlink = false) {
1053
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
832
1054
  let fileSize = 0;
833
1055
  if (unlink) {
834
1056
  try {
@@ -877,12 +1099,21 @@ export class Matterbridge extends EventEmitter {
877
1099
  }
878
1100
  };
879
1101
  }
1102
+ /**
1103
+ * Restarts the process by exiting the current instance and loading a new instance.
1104
+ */
880
1105
  async restartProcess() {
881
1106
  await this.cleanup('restarting...', true);
882
1107
  }
1108
+ /**
1109
+ * Shut down the process by exiting the current process.
1110
+ */
883
1111
  async shutdownProcess() {
884
1112
  await this.cleanup('shutting down...', false);
885
1113
  }
1114
+ /**
1115
+ * Update matterbridge and and shut down the process.
1116
+ */
886
1117
  async updateProcess() {
887
1118
  this.log.info('Updating matterbridge...');
888
1119
  try {
@@ -895,6 +1126,9 @@ export class Matterbridge extends EventEmitter {
895
1126
  this.frontend.wssSendRestartRequired();
896
1127
  await this.cleanup('updating...', false);
897
1128
  }
1129
+ /**
1130
+ * Unregister all devices and shut down the process.
1131
+ */
898
1132
  async unregisterAndShutdownProcess() {
899
1133
  this.log.info('Unregistering all devices and shutting down...');
900
1134
  for (const plugin of this.plugins) {
@@ -902,6 +1136,9 @@ export class Matterbridge extends EventEmitter {
902
1136
  }
903
1137
  await this.cleanup('unregistered all devices and shutting down...', false);
904
1138
  }
1139
+ /**
1140
+ * Reset commissioning and shut down the process.
1141
+ */
905
1142
  async shutdownProcessAndReset() {
906
1143
  this.log.info('Resetting Matterbridge commissioning information...');
907
1144
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -913,8 +1150,12 @@ export class Matterbridge extends EventEmitter {
913
1150
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
914
1151
  await this.cleanup('shutting down with reset...', false);
915
1152
  }
1153
+ /**
1154
+ * Factory reset and shut down the process.
1155
+ */
916
1156
  async shutdownProcessAndFactoryReset() {
917
1157
  try {
1158
+ // Delete old matter storage file and backup
918
1159
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
919
1160
  this.log.info(`Unlinking old matter storage file: ${file}`);
920
1161
  await fs.unlink(file);
@@ -928,6 +1169,7 @@ export class Matterbridge extends EventEmitter {
928
1169
  }
929
1170
  }
930
1171
  try {
1172
+ // Delete matter node storage directory with its subdirectories and backup
931
1173
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
932
1174
  this.log.info(`Removing matter node storage directory: ${dir}`);
933
1175
  await fs.rm(dir, { recursive: true });
@@ -941,6 +1183,7 @@ export class Matterbridge extends EventEmitter {
941
1183
  }
942
1184
  }
943
1185
  try {
1186
+ // Delete node storage directory with its subdirectories and backup
944
1187
  const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
945
1188
  this.log.info(`Removing storage directory: ${dir}`);
946
1189
  await fs.rm(dir, { recursive: true });
@@ -960,30 +1203,41 @@ export class Matterbridge extends EventEmitter {
960
1203
  this.devices.clear();
961
1204
  await this.cleanup('shutting down with factory reset...', false);
962
1205
  }
1206
+ /**
1207
+ * Cleans up the Matterbridge instance.
1208
+ * @param message - The cleanup message.
1209
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1210
+ * @returns A promise that resolves when the cleanup is completed.
1211
+ */
963
1212
  async cleanup(message, restart = false) {
964
1213
  if (this.initialized && !this.hasCleanupStarted) {
965
1214
  this.hasCleanupStarted = true;
966
1215
  this.log.info(message);
1216
+ // Clear the start matter interval
967
1217
  if (this.startMatterInterval) {
968
1218
  clearInterval(this.startMatterInterval);
969
1219
  this.startMatterInterval = undefined;
970
1220
  this.log.debug('Start matter interval cleared');
971
1221
  }
1222
+ // Clear the check update interval
972
1223
  if (this.checkUpdateInterval) {
973
1224
  clearInterval(this.checkUpdateInterval);
974
1225
  this.checkUpdateInterval = undefined;
975
1226
  this.log.debug('Check update interval cleared');
976
1227
  }
1228
+ // Clear the configure timeout
977
1229
  if (this.configureTimeout) {
978
1230
  clearTimeout(this.configureTimeout);
979
1231
  this.configureTimeout = undefined;
980
1232
  this.log.debug('Matterbridge configure timeout cleared');
981
1233
  }
1234
+ // Clear the reachability timeout
982
1235
  if (this.reachabilityTimeout) {
983
1236
  clearTimeout(this.reachabilityTimeout);
984
1237
  this.reachabilityTimeout = undefined;
985
1238
  this.log.debug('Matterbridge reachability timeout cleared');
986
1239
  }
1240
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
987
1241
  for (const plugin of this.plugins) {
988
1242
  if (!plugin.enabled || plugin.error)
989
1243
  continue;
@@ -994,13 +1248,14 @@ export class Matterbridge extends EventEmitter {
994
1248
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
995
1249
  }
996
1250
  }
1251
+ // Stop the frontend
997
1252
  this.frontend.stop();
998
- this.log.info(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1253
+ // Stopping matter server nodes
1254
+ this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
999
1255
  if (this.bridgeMode === 'bridge') {
1000
1256
  if (this.serverNode) {
1001
1257
  await this.stopServerNode(this.serverNode);
1002
1258
  this.serverNode = undefined;
1003
- this.log.info(`Stopped matter server node for Matterbridge`);
1004
1259
  }
1005
1260
  }
1006
1261
  if (this.bridgeMode === 'childbridge') {
@@ -1008,21 +1263,39 @@ export class Matterbridge extends EventEmitter {
1008
1263
  if (plugin.serverNode) {
1009
1264
  await this.stopServerNode(plugin.serverNode);
1010
1265
  plugin.serverNode = undefined;
1011
- this.log.info(`Stopped matter server node for ${plugin.name}`);
1012
1266
  }
1013
1267
  }
1014
1268
  }
1015
- this.log.info('Stopped matter server nodes');
1269
+ this.log.notice('Stopped matter server nodes');
1270
+ // Stop matter storage
1016
1271
  await this.stopMatterStorage();
1272
+ // Remove the matterfilelogger
1017
1273
  try {
1018
1274
  Logger.removeLogger('matterfilelogger');
1275
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1019
1276
  }
1020
1277
  catch (error) {
1278
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1021
1279
  }
1280
+ // Serialize registeredDevices
1022
1281
  if (this.nodeStorage && this.nodeContext) {
1282
+ /*
1283
+ TODO: Implement serialization of registered devices in edge mode
1284
+ this.log.info('Saving registered devices...');
1285
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1286
+ this.devices.forEach(async (device) => {
1287
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1288
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1289
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1290
+ });
1291
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1292
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1293
+ */
1294
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1023
1295
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1024
1296
  await this.nodeContext.close();
1025
1297
  this.nodeContext = undefined;
1298
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1026
1299
  for (const plugin of this.plugins) {
1027
1300
  if (plugin.nodeContext) {
1028
1301
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1039,12 +1312,13 @@ export class Matterbridge extends EventEmitter {
1039
1312
  }
1040
1313
  this.plugins.clear();
1041
1314
  this.devices.clear();
1315
+ // Deregisters the process handlers
1042
1316
  this.deregisterProcesslHandlers();
1043
1317
  if (restart) {
1044
1318
  if (message === 'updating...') {
1045
1319
  this.log.info('Cleanup completed. Updating...');
1046
1320
  Matterbridge.instance = undefined;
1047
- this.emit('update');
1321
+ this.emit('update'); // Restart the process but the update has been done before
1048
1322
  }
1049
1323
  else if (message === 'restarting...') {
1050
1324
  this.log.info('Cleanup completed. Restarting...');
@@ -1061,6 +1335,14 @@ export class Matterbridge extends EventEmitter {
1061
1335
  this.initialized = false;
1062
1336
  }
1063
1337
  }
1338
+ /**
1339
+ * Creates and configures the server node for an accessory plugin for a given device.
1340
+ *
1341
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1342
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1343
+ * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1344
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1345
+ */
1064
1346
  async createAccessoryPlugin(plugin, device, start = false) {
1065
1347
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1066
1348
  plugin.locked = true;
@@ -1072,6 +1354,13 @@ export class Matterbridge extends EventEmitter {
1072
1354
  await this.startServerNode(plugin.serverNode);
1073
1355
  }
1074
1356
  }
1357
+ /**
1358
+ * Creates and configures the server node for a dynamic plugin.
1359
+ *
1360
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1361
+ * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1362
+ * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1363
+ */
1075
1364
  async createDynamicPlugin(plugin, start = false) {
1076
1365
  if (!plugin.locked) {
1077
1366
  plugin.locked = true;
@@ -1083,7 +1372,13 @@ export class Matterbridge extends EventEmitter {
1083
1372
  await this.startServerNode(plugin.serverNode);
1084
1373
  }
1085
1374
  }
1375
+ /**
1376
+ * Starts the Matterbridge in bridge mode.
1377
+ * @private
1378
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1379
+ */
1086
1380
  async startBridge() {
1381
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1087
1382
  if (!this.matterStorageManager)
1088
1383
  throw new Error('No storage manager initialized');
1089
1384
  if (!this.matterbridgeContext)
@@ -1120,7 +1415,9 @@ export class Matterbridge extends EventEmitter {
1120
1415
  clearInterval(this.startMatterInterval);
1121
1416
  this.startMatterInterval = undefined;
1122
1417
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1418
+ // Start the Matter server node
1123
1419
  this.startServerNode(this.serverNode);
1420
+ // Configure the plugins
1124
1421
  this.configureTimeout = setTimeout(async () => {
1125
1422
  for (const plugin of this.plugins) {
1126
1423
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1135,6 +1432,7 @@ export class Matterbridge extends EventEmitter {
1135
1432
  }
1136
1433
  this.frontend.wssSendRefreshRequired();
1137
1434
  }, 30 * 1000);
1435
+ // Setting reachability to true
1138
1436
  this.reachabilityTimeout = setTimeout(() => {
1139
1437
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1140
1438
  if (this.serverNode)
@@ -1145,7 +1443,14 @@ export class Matterbridge extends EventEmitter {
1145
1443
  }, 60 * 1000);
1146
1444
  }, 1000);
1147
1445
  }
1446
+ /**
1447
+ * Starts the Matterbridge in childbridge mode.
1448
+ * @private
1449
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1450
+ */
1148
1451
  async startChildbridge() {
1452
+ // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1453
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1149
1454
  if (!this.matterStorageManager)
1150
1455
  throw new Error('No storage manager initialized');
1151
1456
  for (const plugin of this.plugins) {
@@ -1192,12 +1497,13 @@ export class Matterbridge extends EventEmitter {
1192
1497
  clearInterval(this.startMatterInterval);
1193
1498
  this.startMatterInterval = undefined;
1194
1499
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1500
+ // Configure the plugins
1195
1501
  this.configureTimeout = setTimeout(async () => {
1196
1502
  for (const plugin of this.plugins) {
1197
1503
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1198
1504
  continue;
1199
1505
  try {
1200
- await this.plugins.configure(plugin);
1506
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1201
1507
  }
1202
1508
  catch (error) {
1203
1509
  plugin.error = true;
@@ -1225,7 +1531,9 @@ export class Matterbridge extends EventEmitter {
1225
1531
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1226
1532
  continue;
1227
1533
  }
1534
+ // Start the Matter server node
1228
1535
  this.startServerNode(plugin.serverNode);
1536
+ // Setting reachability to true
1229
1537
  plugin.reachabilityTimeout = setTimeout(() => {
1230
1538
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1231
1539
  if (plugin.serverNode)
@@ -1239,9 +1547,219 @@ export class Matterbridge extends EventEmitter {
1239
1547
  }
1240
1548
  }, 1000);
1241
1549
  }
1550
+ /**
1551
+ * Starts the Matterbridge controller.
1552
+ * @private
1553
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1554
+ */
1242
1555
  async startController() {
1556
+ /*
1557
+ if (!this.storageManager) {
1558
+ this.log.error('No storage manager initialized');
1559
+ await this.cleanup('No storage manager initialized');
1560
+ return;
1561
+ }
1562
+ this.log.info('Creating context: mattercontrollerContext');
1563
+ this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
1564
+ if (!this.mattercontrollerContext) {
1565
+ this.log.error('No storage context mattercontrollerContext initialized');
1566
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1567
+ return;
1568
+ }
1569
+
1570
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1571
+ this.matterServer = await this.createMatterServer(this.storageManager);
1572
+ this.log.info('Creating matter commissioning controller');
1573
+ this.commissioningController = new CommissioningController({
1574
+ autoConnect: false,
1575
+ });
1576
+ this.log.info('Adding matter commissioning controller to matter server');
1577
+ await this.matterServer.addCommissioningController(this.commissioningController);
1578
+
1579
+ this.log.info('Starting matter server');
1580
+ await this.matterServer.start();
1581
+ this.log.info('Matter server started');
1582
+
1583
+ if (hasParameter('pairingcode')) {
1584
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1585
+ const pairingCode = getParameter('pairingcode');
1586
+ const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
1587
+ const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
1588
+
1589
+ let longDiscriminator, setupPin, shortDiscriminator;
1590
+ if (pairingCode !== undefined) {
1591
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1592
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1593
+ longDiscriminator = undefined;
1594
+ setupPin = pairingCodeCodec.passcode;
1595
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1596
+ } else {
1597
+ longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1598
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1599
+ setupPin = this.mattercontrollerContext.get('pin', 20202021);
1600
+ }
1601
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1602
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1603
+ }
1604
+
1605
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1606
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1607
+ regulatoryCountryCode: 'XX',
1608
+ };
1609
+ const options = {
1610
+ commissioning: commissioningOptions,
1611
+ discovery: {
1612
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1613
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1614
+ },
1615
+ passcode: setupPin,
1616
+ } as NodeCommissioningOptions;
1617
+ this.log.info('Commissioning with options:', options);
1618
+ const nodeId = await this.commissioningController.commissionNode(options);
1619
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1620
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1621
+ } // (hasParameter('pairingcode'))
1622
+
1623
+ if (hasParameter('unpairall')) {
1624
+ this.log.info('***Commissioning controller unpairing all nodes...');
1625
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1626
+ for (const nodeId of nodeIds) {
1627
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1628
+ await this.commissioningController.removeNode(nodeId);
1629
+ }
1630
+ return;
1631
+ }
1632
+
1633
+ if (hasParameter('discover')) {
1634
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1635
+ // console.log(discover);
1636
+ }
1637
+
1638
+ if (!this.commissioningController.isCommissioned()) {
1639
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1640
+ return;
1641
+ }
1642
+
1643
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1644
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1645
+ for (const nodeId of nodeIds) {
1646
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1647
+
1648
+ const node = await this.commissioningController.connectNode(nodeId, {
1649
+ autoSubscribe: false,
1650
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1651
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1652
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1653
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1654
+ stateInformationCallback: (peerNodeId, info) => {
1655
+ switch (info) {
1656
+ case NodeStateInformation.Connected:
1657
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1658
+ break;
1659
+ case NodeStateInformation.Disconnected:
1660
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1661
+ break;
1662
+ case NodeStateInformation.Reconnecting:
1663
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1664
+ break;
1665
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1666
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1667
+ break;
1668
+ case NodeStateInformation.StructureChanged:
1669
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1670
+ break;
1671
+ case NodeStateInformation.Decommissioned:
1672
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1673
+ break;
1674
+ default:
1675
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1676
+ break;
1677
+ }
1678
+ },
1679
+ });
1680
+
1681
+ node.logStructure();
1682
+
1683
+ // Get the interaction client
1684
+ this.log.info('Getting the interaction client');
1685
+ const interactionClient = await node.getInteractionClient();
1686
+ let cluster;
1687
+ let attributes;
1688
+
1689
+ // Log BasicInformationCluster
1690
+ cluster = BasicInformationCluster;
1691
+ attributes = await interactionClient.getMultipleAttributes({
1692
+ attributes: [{ clusterId: cluster.id }],
1693
+ });
1694
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1695
+ attributes.forEach((attribute) => {
1696
+ this.log.info(
1697
+ `- 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}`,
1698
+ );
1699
+ });
1700
+
1701
+ // Log PowerSourceCluster
1702
+ cluster = PowerSourceCluster;
1703
+ attributes = await interactionClient.getMultipleAttributes({
1704
+ attributes: [{ clusterId: cluster.id }],
1705
+ });
1706
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1707
+ attributes.forEach((attribute) => {
1708
+ this.log.info(
1709
+ `- 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}`,
1710
+ );
1711
+ });
1712
+
1713
+ // Log ThreadNetworkDiagnostics
1714
+ cluster = ThreadNetworkDiagnosticsCluster;
1715
+ attributes = await interactionClient.getMultipleAttributes({
1716
+ attributes: [{ clusterId: cluster.id }],
1717
+ });
1718
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1719
+ attributes.forEach((attribute) => {
1720
+ this.log.info(
1721
+ `- 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}`,
1722
+ );
1723
+ });
1724
+
1725
+ // Log SwitchCluster
1726
+ cluster = SwitchCluster;
1727
+ attributes = await interactionClient.getMultipleAttributes({
1728
+ attributes: [{ clusterId: cluster.id }],
1729
+ });
1730
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1731
+ attributes.forEach((attribute) => {
1732
+ this.log.info(
1733
+ `- 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}`,
1734
+ );
1735
+ });
1736
+
1737
+ this.log.info('Subscribing to all attributes and events');
1738
+ await node.subscribeAllAttributesAndEvents({
1739
+ ignoreInitialTriggers: false,
1740
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1741
+ this.log.info(
1742
+ `***${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}`,
1743
+ ),
1744
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1745
+ this.log.info(
1746
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1747
+ );
1748
+ },
1749
+ });
1750
+ this.log.info('Subscribed to all attributes and events');
1751
+ }
1752
+ */
1243
1753
  }
1754
+ /** ***********************************************************************************************************************************/
1755
+ /** Matter.js methods */
1756
+ /** ***********************************************************************************************************************************/
1757
+ /**
1758
+ * Starts the matter storage process with name Matterbridge.
1759
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1760
+ */
1244
1761
  async startMatterStorage() {
1762
+ // Setup Matter storage
1245
1763
  this.log.info(`Starting matter node storage...`);
1246
1764
  this.matterStorageService = this.environment.get(StorageService);
1247
1765
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1249,13 +1767,25 @@ export class Matterbridge extends EventEmitter {
1249
1767
  this.log.info('Matter node storage manager "Matterbridge" created');
1250
1768
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
1251
1769
  this.log.info('Matter node storage started');
1770
+ // Backup matter storage since it is created/opened correctly
1252
1771
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1253
1772
  }
1773
+ /**
1774
+ * Makes a backup copy of the specified matter storage directory.
1775
+ *
1776
+ * @param storageName - The name of the storage directory to be backed up.
1777
+ * @param backupName - The name of the backup directory to be created.
1778
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1779
+ */
1254
1780
  async backupMatterStorage(storageName, backupName) {
1255
1781
  this.log.info('Creating matter node storage backup...');
1256
1782
  await copyDirectory(storageName, backupName);
1257
1783
  this.log.info('Created matter node storage backup');
1258
1784
  }
1785
+ /**
1786
+ * Stops the matter storage.
1787
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1788
+ */
1259
1789
  async stopMatterStorage() {
1260
1790
  this.log.info('Closing matter node storage...');
1261
1791
  this.matterStorageManager?.close();
@@ -1264,6 +1794,19 @@ export class Matterbridge extends EventEmitter {
1264
1794
  this.matterbridgeContext = undefined;
1265
1795
  this.log.info('Matter node storage closed');
1266
1796
  }
1797
+ /**
1798
+ * Creates a server node storage context.
1799
+ *
1800
+ * @param {string} pluginName - The name of the plugin.
1801
+ * @param {string} deviceName - The name of the device.
1802
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1803
+ * @param {number} vendorId - The vendor ID.
1804
+ * @param {string} vendorName - The vendor name.
1805
+ * @param {number} productId - The product ID.
1806
+ * @param {string} productName - The product name.
1807
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1808
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1809
+ */
1267
1810
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1268
1811
  if (!this.matterStorageService)
1269
1812
  throw new Error('No storage service initialized');
@@ -1296,6 +1839,15 @@ export class Matterbridge extends EventEmitter {
1296
1839
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1297
1840
  return storageContext;
1298
1841
  }
1842
+ /**
1843
+ * Creates a server node.
1844
+ *
1845
+ * @param {StorageContext} storageContext - The storage context for the server node.
1846
+ * @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
1847
+ * @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
1848
+ * @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
1849
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1850
+ */
1299
1851
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1300
1852
  const storeId = await storageContext.get('storeId');
1301
1853
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1305,21 +1857,33 @@ export class Matterbridge extends EventEmitter {
1305
1857
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1306
1858
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1307
1859
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1860
+ /**
1861
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1862
+ */
1308
1863
  const serverNode = await ServerNode.create({
1864
+ // Required: Give the Node a unique ID which is used to store the state of this node
1309
1865
  id: storeId,
1866
+ // Provide Network relevant configuration like the port
1867
+ // Optional when operating only one device on a host, Default port is 5540
1310
1868
  network: {
1311
1869
  listeningAddressIpv4: this.ipv4address,
1312
1870
  listeningAddressIpv6: this.ipv6address,
1313
1871
  port,
1314
1872
  },
1873
+ // Provide Commissioning relevant settings
1874
+ // Optional for development/testing purposes
1315
1875
  commissioning: {
1316
1876
  passcode,
1317
1877
  discriminator,
1318
1878
  },
1879
+ // Provide Node announcement settings
1880
+ // Optional: If Ommitted some development defaults are used
1319
1881
  productDescription: {
1320
1882
  name: await storageContext.get('deviceName'),
1321
1883
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1322
1884
  },
1885
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1886
+ // Optional: If Omitted some development defaults are used
1323
1887
  basicInformation: {
1324
1888
  vendorId: VendorId(await storageContext.get('vendorId')),
1325
1889
  vendorName: await storageContext.get('vendorName'),
@@ -1336,12 +1900,13 @@ export class Matterbridge extends EventEmitter {
1336
1900
  },
1337
1901
  });
1338
1902
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
1903
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1339
1904
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1340
1905
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1341
1906
  if (this.bridgeMode === 'bridge') {
1342
1907
  this.matterbridgeFabricInformations = sanitizedFabrics;
1343
1908
  if (resetSessions)
1344
- this.matterbridgeSessionInformations = undefined;
1909
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1345
1910
  this.matterbridgePaired = true;
1346
1911
  }
1347
1912
  if (this.bridgeMode === 'childbridge') {
@@ -1349,13 +1914,19 @@ export class Matterbridge extends EventEmitter {
1349
1914
  if (plugin) {
1350
1915
  plugin.fabricInformations = sanitizedFabrics;
1351
1916
  if (resetSessions)
1352
- plugin.sessionInformations = undefined;
1917
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1353
1918
  plugin.paired = true;
1354
1919
  }
1355
1920
  }
1356
1921
  };
1922
+ /**
1923
+ * This event is triggered when the device is initially commissioned successfully.
1924
+ * This means: It is added to the first fabric.
1925
+ */
1357
1926
  serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
1927
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1358
1928
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1929
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1359
1930
  serverNode.lifecycle.online.on(async () => {
1360
1931
  this.log.notice(`Server node for ${storeId} is online`);
1361
1932
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1401,6 +1972,7 @@ export class Matterbridge extends EventEmitter {
1401
1972
  }
1402
1973
  this.frontend.wssSendRefreshRequired();
1403
1974
  });
1975
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1404
1976
  serverNode.lifecycle.offline.on(() => {
1405
1977
  this.log.notice(`Server node for ${storeId} is offline`);
1406
1978
  if (this.bridgeMode === 'bridge') {
@@ -1422,6 +1994,10 @@ export class Matterbridge extends EventEmitter {
1422
1994
  }
1423
1995
  this.frontend.wssSendRefreshRequired();
1424
1996
  });
1997
+ /**
1998
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
1999
+ * information is needed.
2000
+ */
1425
2001
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1426
2002
  let action = '';
1427
2003
  switch (fabricAction) {
@@ -1455,16 +2031,24 @@ export class Matterbridge extends EventEmitter {
1455
2031
  }
1456
2032
  }
1457
2033
  };
2034
+ /**
2035
+ * This event is triggered when an operative new session was opened by a Controller.
2036
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2037
+ */
1458
2038
  serverNode.events.sessions.opened.on((session) => {
1459
2039
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1460
2040
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1461
2041
  this.frontend.wssSendRefreshRequired();
1462
2042
  });
2043
+ /**
2044
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2045
+ */
1463
2046
  serverNode.events.sessions.closed.on((session) => {
1464
2047
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1465
2048
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1466
2049
  this.frontend.wssSendRefreshRequired();
1467
2050
  });
2051
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1468
2052
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1469
2053
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1470
2054
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1473,19 +2057,39 @@ export class Matterbridge extends EventEmitter {
1473
2057
  this.log.info(`Created server node for ${storeId}`);
1474
2058
  return serverNode;
1475
2059
  }
2060
+ /**
2061
+ * Starts the specified server node.
2062
+ *
2063
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2064
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2065
+ */
1476
2066
  async startServerNode(matterServerNode) {
1477
2067
  if (!matterServerNode)
1478
2068
  return;
1479
2069
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1480
2070
  await matterServerNode.start();
1481
2071
  }
2072
+ /**
2073
+ * Stops the specified server node.
2074
+ *
2075
+ * @param {ServerNode} matterServerNode - The server node to stop.
2076
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2077
+ */
1482
2078
  async stopServerNode(matterServerNode) {
1483
2079
  if (!matterServerNode)
1484
2080
  return;
1485
2081
  this.log.notice(`Closing ${matterServerNode.id} server node`);
1486
2082
  await matterServerNode.close();
1487
2083
  this.log.info(`Closed ${matterServerNode.id} server node`);
2084
+ // await matterServerNode.env.get(MdnsService)[Symbol.asyncDispose]();
2085
+ // this.log.info(`Closed ${matterServerNode.id} MdnsService`);
1488
2086
  }
2087
+ /**
2088
+ * Advertises the specified server node if it is commissioned.
2089
+ *
2090
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2091
+ * @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.
2092
+ */
1489
2093
  async advertiseServerNode(matterServerNode) {
1490
2094
  if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
1491
2095
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1495,17 +2099,32 @@ export class Matterbridge extends EventEmitter {
1495
2099
  }
1496
2100
  return undefined;
1497
2101
  }
2102
+ /**
2103
+ * Creates an aggregator node with the specified storage context.
2104
+ *
2105
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2106
+ * @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2107
+ */
1498
2108
  async createAggregatorNode(storageContext) {
1499
2109
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
1500
2110
  const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1501
2111
  return aggregatorNode;
1502
2112
  }
2113
+ /**
2114
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2115
+ *
2116
+ * @param {string} pluginName - The name of the plugin.
2117
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2118
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2119
+ */
1503
2120
  async addBridgedEndpoint(pluginName, device) {
2121
+ // Check if the plugin is registered
1504
2122
  const plugin = this.plugins.get(pluginName);
1505
2123
  if (!plugin) {
1506
2124
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1507
2125
  return;
1508
2126
  }
2127
+ // Register and add the device to the matterbridge aggregator node
1509
2128
  if (this.bridgeMode === 'bridge') {
1510
2129
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1511
2130
  if (!this.aggregatorNode)
@@ -1528,16 +2147,26 @@ export class Matterbridge extends EventEmitter {
1528
2147
  plugin.registeredDevices++;
1529
2148
  if (plugin.addedDevices !== undefined)
1530
2149
  plugin.addedDevices++;
2150
+ // Add the device to the DeviceManager
1531
2151
  this.devices.set(device);
1532
2152
  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}`);
1533
2153
  }
2154
+ /**
2155
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2156
+ *
2157
+ * @param {string} pluginName - The name of the plugin.
2158
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2159
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2160
+ */
1534
2161
  async removeBridgedEndpoint(pluginName, device) {
1535
2162
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2163
+ // Check if the plugin is registered
1536
2164
  const plugin = this.plugins.get(pluginName);
1537
2165
  if (!plugin) {
1538
2166
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1539
2167
  return;
1540
2168
  }
2169
+ // Register and add the device to the matterbridge aggregator node
1541
2170
  if (this.bridgeMode === 'bridge') {
1542
2171
  if (!this.aggregatorNode) {
1543
2172
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1552,6 +2181,7 @@ export class Matterbridge extends EventEmitter {
1552
2181
  }
1553
2182
  else if (this.bridgeMode === 'childbridge') {
1554
2183
  if (plugin.type === 'AccessoryPlatform') {
2184
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1555
2185
  }
1556
2186
  else if (plugin.type === 'DynamicPlatform') {
1557
2187
  if (!plugin.aggregatorNode) {
@@ -1565,6 +2195,7 @@ export class Matterbridge extends EventEmitter {
1565
2195
  plugin.registeredDevices--;
1566
2196
  if (plugin.addedDevices !== undefined)
1567
2197
  plugin.addedDevices--;
2198
+ // Close the server node TODO check if this is correct
1568
2199
  if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
1569
2200
  if (plugin.serverNode) {
1570
2201
  await this.stopServerNode(plugin.serverNode);
@@ -1575,14 +2206,27 @@ export class Matterbridge extends EventEmitter {
1575
2206
  }
1576
2207
  }
1577
2208
  }
2209
+ // Remove the device from the DeviceManager
1578
2210
  this.devices.remove(device);
1579
2211
  }
2212
+ /**
2213
+ * Removes all bridged endpoints from the specified plugin.
2214
+ *
2215
+ * @param {string} pluginName - The name of the plugin.
2216
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2217
+ */
1580
2218
  async removeAllBridgedEndpoints(pluginName) {
1581
2219
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
1582
2220
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
1583
2221
  await this.removeBridgedEndpoint(pluginName, device);
1584
2222
  }
1585
2223
  }
2224
+ /**
2225
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2226
+ *
2227
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2228
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2229
+ */
1586
2230
  sanitizeFabricInformations(fabricInfo) {
1587
2231
  return fabricInfo.map((info) => {
1588
2232
  return {
@@ -1596,6 +2240,12 @@ export class Matterbridge extends EventEmitter {
1596
2240
  };
1597
2241
  });
1598
2242
  }
2243
+ /**
2244
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2245
+ *
2246
+ * @param {SessionInformation[]} sessionInfo - The array of session information objects.
2247
+ * @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
2248
+ */
1599
2249
  sanitizeSessionInformation(sessionInfo) {
1600
2250
  return sessionInfo
1601
2251
  .filter((session) => session.isPeerActive)
@@ -1623,11 +2273,51 @@ export class Matterbridge extends EventEmitter {
1623
2273
  };
1624
2274
  });
1625
2275
  }
2276
+ /**
2277
+ * Sets the reachability of a matter server node and trigger ReachableChanged event.
2278
+ *
2279
+ * @param {ServerNode<ServerNode.RootEndpoint>} serverNode - The commissioning server to set the reachability for.
2280
+ * @param {boolean} reachable - The new reachability status.
2281
+ */
2282
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1626
2283
  setServerNodeReachability(serverNode, reachable) {
2284
+ /*
2285
+ const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
2286
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2287
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2288
+ */
1627
2289
  }
2290
+ /**
2291
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2292
+ * @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The matter aggregator to set the reachability for.
2293
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2294
+ */
2295
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1628
2296
  setAggregatorReachability(aggregatorNode, reachable) {
2297
+ /*
2298
+ const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
2299
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2300
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2301
+ matterAggregator.getBridgedDevices().forEach((device) => {
2302
+ this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
2303
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
2304
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2305
+ });
2306
+ */
1629
2307
  }
2308
+ /**
2309
+ * Sets the reachability of a device and trigger.
2310
+ *
2311
+ * @param {MatterbridgeEndpoint} device - The device to set the reachability for.
2312
+ * @param {boolean} reachable - The new reachability status of the device.
2313
+ */
2314
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1630
2315
  setDeviceReachability(device, reachable) {
2316
+ /*
2317
+ const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
2318
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
2319
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2320
+ */
1631
2321
  }
1632
2322
  getVendorIdName = (vendorId) => {
1633
2323
  if (!vendorId)
@@ -1670,13 +2360,36 @@ export class Matterbridge extends EventEmitter {
1670
2360
  }
1671
2361
  return vendorName;
1672
2362
  };
2363
+ /**
2364
+ * Spawns a child process with the given command and arguments.
2365
+ * @param {string} command - The command to execute.
2366
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2367
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2368
+ */
1673
2369
  async spawnCommand(command, args = []) {
2370
+ /*
2371
+ npm > npm.cmd on windows
2372
+ cmd.exe ['dir'] on windows
2373
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2374
+ process.on('unhandledRejection', (reason, promise) => {
2375
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2376
+ });
2377
+
2378
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2379
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2380
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2381
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2382
+ */
1674
2383
  const cmdLine = command + ' ' + args.join(' ');
1675
2384
  if (process.platform === 'win32' && command === 'npm') {
2385
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
1676
2386
  const argstring = 'npm ' + args.join(' ');
1677
2387
  args.splice(0, args.length, '/c', argstring);
1678
2388
  command = 'cmd.exe';
1679
2389
  }
2390
+ // Decide when using sudo on linux
2391
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2392
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
1680
2393
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
1681
2394
  args.unshift(command);
1682
2395
  command = 'sudo';
@@ -1735,3 +2448,4 @@ export class Matterbridge extends EventEmitter {
1735
2448
  });
1736
2449
  }
1737
2450
  }
2451
+ //# sourceMappingURL=matterbridge.js.map