matterbridge 2.1.2-dev.2 → 2.1.2

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