matterbridge 2.1.3-dev.1 → 2.1.3

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