matterbridge 2.2.0-dev.8 → 2.2.0

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 (147) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/cli.d.ts +29 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +37 -2
  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 +24 -6
  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 +172 -0
  19. package/dist/frontend.d.ts.map +1 -0
  20. package/dist/frontend.js +270 -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 -1
  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 +411 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +719 -47
  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 +835 -0
  75. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  76. package/dist/matterbridgeEndpoint.js +692 -6
  77. package/dist/matterbridgeEndpoint.js.map +1 -0
  78. package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  80. package/dist/matterbridgeEndpointHelpers.js +118 -9
  81. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  82. package/dist/matterbridgePlatform.d.ts +181 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +140 -7
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +174 -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 +229 -3
  93. package/dist/pluginManager.js.map +1 -0
  94. package/dist/shelly.d.ts +77 -0
  95. package/dist/shelly.d.ts.map +1 -0
  96. package/dist/shelly.js +121 -6
  97. package/dist/shelly.js.map +1 -0
  98. package/dist/storage/export.d.ts +2 -0
  99. package/dist/storage/export.d.ts.map +1 -0
  100. package/dist/storage/export.js +1 -0
  101. package/dist/storage/export.js.map +1 -0
  102. package/dist/update.d.ts +32 -0
  103. package/dist/update.d.ts.map +1 -0
  104. package/dist/update.js +45 -0
  105. package/dist/update.js.map +1 -0
  106. package/dist/utils/colorUtils.d.ts +61 -0
  107. package/dist/utils/colorUtils.d.ts.map +1 -0
  108. package/dist/utils/colorUtils.js +205 -2
  109. package/dist/utils/colorUtils.js.map +1 -0
  110. package/dist/utils/copyDirectory.d.ts +32 -0
  111. package/dist/utils/copyDirectory.d.ts.map +1 -0
  112. package/dist/utils/copyDirectory.js +37 -1
  113. package/dist/utils/copyDirectory.js.map +1 -0
  114. package/dist/utils/createZip.d.ts +38 -0
  115. package/dist/utils/createZip.d.ts.map +1 -0
  116. package/dist/utils/createZip.js +42 -2
  117. package/dist/utils/createZip.js.map +1 -0
  118. package/dist/utils/deepCopy.d.ts +31 -0
  119. package/dist/utils/deepCopy.d.ts.map +1 -0
  120. package/dist/utils/deepCopy.js +40 -0
  121. package/dist/utils/deepCopy.js.map +1 -0
  122. package/dist/utils/deepEqual.d.ts +53 -0
  123. package/dist/utils/deepEqual.d.ts.map +1 -0
  124. package/dist/utils/deepEqual.js +65 -1
  125. package/dist/utils/deepEqual.js.map +1 -0
  126. package/dist/utils/export.d.ts +10 -0
  127. package/dist/utils/export.d.ts.map +1 -0
  128. package/dist/utils/export.js +1 -0
  129. package/dist/utils/export.js.map +1 -0
  130. package/dist/utils/isvalid.d.ts +87 -0
  131. package/dist/utils/isvalid.d.ts.map +1 -0
  132. package/dist/utils/isvalid.js +86 -0
  133. package/dist/utils/isvalid.js.map +1 -0
  134. package/dist/utils/network.d.ts +70 -0
  135. package/dist/utils/network.d.ts.map +1 -0
  136. package/dist/utils/network.js +77 -5
  137. package/dist/utils/network.js.map +1 -0
  138. package/dist/utils/parameter.d.ts +44 -0
  139. package/dist/utils/parameter.d.ts.map +1 -0
  140. package/dist/utils/parameter.js +41 -0
  141. package/dist/utils/parameter.js.map +1 -0
  142. package/dist/utils/wait.d.ts +43 -0
  143. package/dist/utils/wait.d.ts.map +1 -0
  144. package/dist/utils/wait.js +48 -5
  145. package/dist/utils/wait.js.map +1 -0
  146. package/npm-shrinkwrap.json +2 -2
  147. package/package.json +2 -1
@@ -1,9 +1,35 @@
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 os from 'node:os';
2
25
  import path from 'node:path';
3
26
  import { promises as fs } from 'node:fs';
4
27
  import EventEmitter from 'node:events';
28
+ // AnsiLogger module
5
29
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
30
+ // NodeStorage module
6
31
  import { NodeStorageManager } from './storage/export.js';
32
+ // Matterbridge
7
33
  import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout } from './utils/export.js';
8
34
  import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
9
35
  import { PluginManager } from './pluginManager.js';
@@ -11,13 +37,18 @@ import { DeviceManager } from './deviceManager.js';
11
37
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
12
38
  import { bridge } from './matterbridgeDeviceTypes.js';
13
39
  import { Frontend } from './frontend.js';
40
+ // @matter
14
41
  import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
15
42
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
16
43
  import { AggregatorEndpoint } from '@matter/main/endpoints';
17
44
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
45
+ // Default colors
18
46
  const plg = '\u001B[38;5;33m';
19
47
  const dev = '\u001B[38;5;79m';
20
48
  const typ = '\u001B[38;5;207m';
49
+ /**
50
+ * Represents the Matterbridge application.
51
+ */
21
52
  export class Matterbridge extends EventEmitter {
22
53
  systemInformation = {
23
54
  interfaceName: '',
@@ -61,7 +92,7 @@ export class Matterbridge extends EventEmitter {
61
92
  shellySysUpdate: false,
62
93
  shellyMainUpdate: false,
63
94
  profile: getParameter('profile'),
64
- loggerLevel: "info",
95
+ loggerLevel: "info" /* LogLevel.INFO */,
65
96
  fileLogger: false,
66
97
  matterLoggerLevel: MatterLogLevel.INFO,
67
98
  matterFileLogger: false,
@@ -97,9 +128,11 @@ export class Matterbridge extends EventEmitter {
97
128
  plugins;
98
129
  devices;
99
130
  frontend = new Frontend(this);
131
+ // Matterbridge storage
100
132
  nodeStorage;
101
133
  nodeContext;
102
134
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
135
+ // Cleanup
103
136
  hasCleanupStarted = false;
104
137
  initialized = false;
105
138
  execRunningCount = 0;
@@ -112,38 +145,70 @@ export class Matterbridge extends EventEmitter {
112
145
  sigtermHandler;
113
146
  exceptionHandler;
114
147
  rejectionHandler;
148
+ // Matter environment
115
149
  environment = Environment.default;
150
+ // Matter storage
116
151
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
117
152
  matterStorageService;
118
153
  matterStorageManager;
119
154
  matterbridgeContext;
120
155
  mattercontrollerContext;
121
- mdnsInterface;
122
- ipv4address;
123
- ipv6address;
124
- port;
125
- passcode;
126
- discriminator;
156
+ // Matter parameters
157
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
158
+ ipv4address; // matter server node listeningAddressIpv4
159
+ ipv6address; // matter server node listeningAddressIpv6
160
+ port; // first server node port
161
+ passcode; // first server node passcode
162
+ discriminator; // first server node discriminator
127
163
  serverNode;
128
164
  aggregatorNode;
129
165
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
130
166
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
131
167
  static instance;
168
+ // We load asyncronously so is private
132
169
  constructor() {
133
170
  super();
134
171
  }
172
+ /**
173
+ * Emits an event of the specified type with the provided arguments.
174
+ *
175
+ * @template K - The type of the event.
176
+ * @param {K} eventName - The name of the event to emit.
177
+ * @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
178
+ * @returns {boolean} - Returns true if the event had listeners, false otherwise.
179
+ */
135
180
  emit(eventName, ...args) {
136
181
  return super.emit(eventName, ...args);
137
182
  }
183
+ /**
184
+ * Registers an event listener for the specified event type.
185
+ *
186
+ * @template K - The type of the event.
187
+ * @param {K} eventName - The name of the event to listen for.
188
+ * @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
189
+ * @returns {this} - Returns the instance of the Matterbridge class.
190
+ */
138
191
  on(eventName, listener) {
139
192
  return super.on(eventName, listener);
140
193
  }
194
+ /**
195
+ * Retrieves the list of Matterbridge devices.
196
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
197
+ */
141
198
  getDevices() {
142
199
  return this.devices.array();
143
200
  }
201
+ /**
202
+ * Retrieves the list of registered plugins.
203
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
204
+ */
144
205
  getPlugins() {
145
206
  return this.plugins.array();
146
207
  }
208
+ /**
209
+ * Set the logger logLevel for the Matterbridge classes.
210
+ * @param {LogLevel} logLevel The logger logLevel to set.
211
+ */
147
212
  async setLogLevel(logLevel) {
148
213
  if (this.log)
149
214
  this.log.logLevel = logLevel;
@@ -157,19 +222,31 @@ export class Matterbridge extends EventEmitter {
157
222
  for (const plugin of this.plugins) {
158
223
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
159
224
  continue;
160
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
161
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
162
- }
163
- let callbackLogLevel = "notice";
164
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
165
- callbackLogLevel = "info";
166
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
167
- callbackLogLevel = "debug";
225
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
226
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
227
+ }
228
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
229
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
230
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
231
+ callbackLogLevel = "info" /* LogLevel.INFO */;
232
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
233
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
168
234
  AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
169
235
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
170
236
  }
237
+ /** ***********************************************************************************************************************************/
238
+ /** loadInstance() and cleanup() methods */
239
+ /** ***********************************************************************************************************************************/
240
+ /**
241
+ * Loads an instance of the Matterbridge class.
242
+ * If an instance already exists, return that instance.
243
+ *
244
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
245
+ * @returns The loaded Matterbridge instance.
246
+ */
171
247
  static async loadInstance(initialize = false) {
172
248
  if (!Matterbridge.instance) {
249
+ // eslint-disable-next-line no-console
173
250
  if (hasParameter('debug'))
174
251
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
175
252
  Matterbridge.instance = new Matterbridge();
@@ -178,8 +255,14 @@ export class Matterbridge extends EventEmitter {
178
255
  }
179
256
  return Matterbridge.instance;
180
257
  }
258
+ /**
259
+ * Call cleanup().
260
+ * @deprecated This method is deprecated and is only used for jest tests.
261
+ *
262
+ */
181
263
  async destroyInstance() {
182
264
  this.log.info(`Destroy instance...`);
265
+ // Save server nodes to close
183
266
  const servers = [];
184
267
  if (this.bridgeMode === 'bridge') {
185
268
  if (this.serverNode)
@@ -191,55 +274,81 @@ export class Matterbridge extends EventEmitter {
191
274
  servers.push(plugin.serverNode);
192
275
  }
193
276
  }
277
+ // Cleanup
194
278
  await this.cleanup('destroying instance...', false);
279
+ // Close servers mdns service
195
280
  this.log.info(`Dispose ${servers.length} MdnsService...`);
196
281
  for (const server of servers) {
197
282
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
198
283
  this.log.info(`Closed ${server.id} MdnsService`);
199
284
  }
285
+ // Wait for the cleanup to finish
200
286
  await new Promise((resolve) => {
201
287
  setTimeout(resolve, 1000);
202
288
  });
203
289
  }
290
+ /**
291
+ * Initializes the Matterbridge application.
292
+ *
293
+ * @remarks
294
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
295
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
296
+ * node version, registers signal handlers, initializes storage, and parses the command line.
297
+ *
298
+ * @returns A Promise that resolves when the initialization is complete.
299
+ */
204
300
  async initialize() {
301
+ // Set the restart mode
205
302
  if (hasParameter('service'))
206
303
  this.restartMode = 'service';
207
304
  if (hasParameter('docker'))
208
305
  this.restartMode = 'docker';
306
+ // Set the matterbridge directory
209
307
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
210
308
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
309
+ // Setup the matter environment
211
310
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
212
311
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
213
312
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
214
313
  this.environment.vars.set('runtime.signals', false);
215
314
  this.environment.vars.set('runtime.exitcode', false);
216
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
315
+ // Create the matterbridge logger
316
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
317
+ // Register process handlers
217
318
  this.registerProcessHandlers();
319
+ // Initialize nodeStorage and nodeContext
218
320
  try {
219
321
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
220
322
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
221
323
  this.log.debug('Creating node storage context for matterbridge');
222
324
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
325
+ // TODO: Remove this code when node-persist-manager is updated
326
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
223
327
  const keys = (await this.nodeStorage?.storage.keys());
224
328
  for (const key of keys) {
225
329
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
330
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
226
331
  await this.nodeStorage?.storage.get(key);
227
332
  }
228
333
  const storages = await this.nodeStorage.getStorageNames();
229
334
  for (const storage of storages) {
230
335
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
231
336
  const nodeContext = await this.nodeStorage?.createStorage(storage);
337
+ // TODO: Remove this code when node-persist-manager is updated
338
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
232
339
  const keys = (await nodeContext?.storage.keys());
233
340
  keys.forEach(async (key) => {
234
341
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
235
342
  await nodeContext?.get(key);
236
343
  });
237
344
  }
345
+ // Creating a backup of the node storage since it is not corrupted
238
346
  this.log.debug('Creating node storage backup...');
239
347
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
240
348
  this.log.debug('Created node storage backup');
241
349
  }
242
350
  catch (error) {
351
+ // Restoring the backup of the node storage since it is corrupted
243
352
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
244
353
  if (hasParameter('norestore')) {
245
354
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -254,41 +363,46 @@ export class Matterbridge extends EventEmitter {
254
363
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
255
364
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
256
365
  }
366
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
257
367
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
368
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
258
369
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
370
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
259
371
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
260
372
  this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
373
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
261
374
  if (hasParameter('logger')) {
262
375
  const level = getParameter('logger');
263
376
  if (level === 'debug') {
264
- this.log.logLevel = "debug";
377
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
265
378
  }
266
379
  else if (level === 'info') {
267
- this.log.logLevel = "info";
380
+ this.log.logLevel = "info" /* LogLevel.INFO */;
268
381
  }
269
382
  else if (level === 'notice') {
270
- this.log.logLevel = "notice";
383
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
271
384
  }
272
385
  else if (level === 'warn') {
273
- this.log.logLevel = "warn";
386
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
274
387
  }
275
388
  else if (level === 'error') {
276
- this.log.logLevel = "error";
389
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
277
390
  }
278
391
  else if (level === 'fatal') {
279
- this.log.logLevel = "fatal";
392
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
280
393
  }
281
394
  else {
282
395
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
283
- this.log.logLevel = "info";
396
+ this.log.logLevel = "info" /* LogLevel.INFO */;
284
397
  }
285
398
  }
286
399
  else {
287
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
400
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
288
401
  }
289
402
  this.frontend.logLevel = this.log.logLevel;
290
403
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
291
404
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
405
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
292
406
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
293
407
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
294
408
  this.matterbridgeInformation.fileLogger = true;
@@ -297,6 +411,7 @@ export class Matterbridge extends EventEmitter {
297
411
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
298
412
  if (this.profile !== undefined)
299
413
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
414
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
300
415
  if (hasParameter('matterlogger')) {
301
416
  const level = getParameter('matterlogger');
302
417
  if (level === 'debug') {
@@ -328,6 +443,7 @@ export class Matterbridge extends EventEmitter {
328
443
  Logger.format = MatterLogFormat.ANSI;
329
444
  Logger.setLogger('default', this.createMatterLogger());
330
445
  this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
446
+ // Create the file logger for matter.js (context: matterFileLog)
331
447
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
332
448
  this.matterbridgeInformation.matterFileLogger = true;
333
449
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -336,6 +452,7 @@ export class Matterbridge extends EventEmitter {
336
452
  });
337
453
  }
338
454
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
455
+ // Set the interface to use for matter server node mdnsInterface
339
456
  if (hasParameter('mdnsinterface')) {
340
457
  this.mdnsInterface = getParameter('mdnsinterface');
341
458
  }
@@ -344,6 +461,7 @@ export class Matterbridge extends EventEmitter {
344
461
  if (this.mdnsInterface === '')
345
462
  this.mdnsInterface = undefined;
346
463
  }
464
+ // Validate mdnsInterface
347
465
  if (this.mdnsInterface) {
348
466
  const networkInterfaces = os.networkInterfaces();
349
467
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -357,6 +475,7 @@ export class Matterbridge extends EventEmitter {
357
475
  }
358
476
  if (this.mdnsInterface)
359
477
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
478
+ // Set the listeningAddressIpv4 for the matter commissioning server
360
479
  if (hasParameter('ipv4address')) {
361
480
  this.ipv4address = getParameter('ipv4address');
362
481
  }
@@ -365,6 +484,7 @@ export class Matterbridge extends EventEmitter {
365
484
  if (this.ipv4address === '')
366
485
  this.ipv4address = undefined;
367
486
  }
487
+ // Set the listeningAddressIpv6 for the matter commissioning server
368
488
  if (hasParameter('ipv6address')) {
369
489
  this.ipv6address = getParameter('ipv6address');
370
490
  }
@@ -373,14 +493,19 @@ export class Matterbridge extends EventEmitter {
373
493
  if (this.ipv6address === '')
374
494
  this.ipv6address = undefined;
375
495
  }
496
+ // Initialize PluginManager
376
497
  this.plugins = new PluginManager(this);
377
498
  await this.plugins.loadFromStorage();
378
499
  this.plugins.logLevel = this.log.logLevel;
500
+ // Initialize DeviceManager
379
501
  this.devices = new DeviceManager(this, this.nodeContext);
380
502
  this.devices.logLevel = this.log.logLevel;
503
+ // Get the plugins from node storage and create the plugins node storage contexts
381
504
  for (const plugin of this.plugins) {
382
505
  const packageJson = await this.plugins.parse(plugin);
383
506
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
507
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
508
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
384
509
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
385
510
  try {
386
511
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -402,6 +527,7 @@ export class Matterbridge extends EventEmitter {
402
527
  await plugin.nodeContext.set('description', plugin.description);
403
528
  await plugin.nodeContext.set('author', plugin.author);
404
529
  }
530
+ // Log system info and create .matterbridge directory
405
531
  await this.logNodeAndSystemInfo();
406
532
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
407
533
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -409,6 +535,7 @@ export class Matterbridge extends EventEmitter {
409
535
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
410
536
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
411
537
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
538
+ // Check node version and throw error
412
539
  const minNodeVersion = 18;
413
540
  const nodeVersion = process.versions.node;
414
541
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -416,9 +543,15 @@ export class Matterbridge extends EventEmitter {
416
543
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
417
544
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
418
545
  }
546
+ // Parse command line
419
547
  await this.parseCommandLine();
420
548
  this.initialized = true;
421
549
  }
550
+ /**
551
+ * Parses the command line arguments and performs the corresponding actions.
552
+ * @private
553
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
554
+ */
422
555
  async parseCommandLine() {
423
556
  if (hasParameter('help')) {
424
557
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -530,6 +663,7 @@ export class Matterbridge extends EventEmitter {
530
663
  this.shutdown = true;
531
664
  return;
532
665
  }
666
+ // Start the matter storage and create the matterbridge context
533
667
  try {
534
668
  await this.startMatterStorage();
535
669
  }
@@ -537,12 +671,14 @@ export class Matterbridge extends EventEmitter {
537
671
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
538
672
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
539
673
  }
674
+ // Clear the matterbridge context if the reset parameter is set
540
675
  if (hasParameter('reset') && getParameter('reset') === undefined) {
541
676
  this.initialized = true;
542
677
  await this.shutdownProcessAndReset();
543
678
  this.shutdown = true;
544
679
  return;
545
680
  }
681
+ // Clear matterbridge plugin context if the reset parameter is set
546
682
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
547
683
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
548
684
  const plugin = this.plugins.get(getParameter('reset'));
@@ -567,30 +703,37 @@ export class Matterbridge extends EventEmitter {
567
703
  this.shutdown = true;
568
704
  return;
569
705
  }
706
+ // Initialize frontend
570
707
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
571
708
  await this.frontend.start(getIntParameter('frontend'));
709
+ // Check in 30 seconds the latest versions
572
710
  this.checkUpdateTimeout = setTimeout(async () => {
573
711
  const { checkUpdates } = await import('./update.js');
574
712
  checkUpdates(this);
575
713
  }, 30 * 1000).unref();
714
+ // Check each 24 hours the latest versions
576
715
  this.checkUpdateInterval = setInterval(async () => {
577
716
  const { checkUpdates } = await import('./update.js');
578
717
  checkUpdates(this);
579
718
  }, 24 * 60 * 60 * 1000).unref();
719
+ // Start the matterbridge in mode test
580
720
  if (hasParameter('test')) {
581
721
  this.bridgeMode = 'bridge';
582
722
  MatterbridgeEndpoint.bridgeMode = 'bridge';
583
723
  return;
584
724
  }
725
+ // Start the matterbridge in mode controller
585
726
  if (hasParameter('controller')) {
586
727
  this.bridgeMode = 'controller';
587
728
  await this.startController();
588
729
  return;
589
730
  }
731
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
590
732
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
591
733
  this.log.info('Setting default matterbridge start mode to bridge');
592
734
  await this.nodeContext?.set('bridgeMode', 'bridge');
593
735
  }
736
+ // Start matterbridge in bridge mode
594
737
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
595
738
  this.bridgeMode = 'bridge';
596
739
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -598,6 +741,7 @@ export class Matterbridge extends EventEmitter {
598
741
  await this.startBridge();
599
742
  return;
600
743
  }
744
+ // Start matterbridge in childbridge mode
601
745
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
602
746
  this.bridgeMode = 'childbridge';
603
747
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -606,10 +750,20 @@ export class Matterbridge extends EventEmitter {
606
750
  return;
607
751
  }
608
752
  }
753
+ /**
754
+ * Asynchronously loads and starts the registered plugins.
755
+ *
756
+ * This method is responsible for initializing and staarting all enabled plugins.
757
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
758
+ *
759
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
760
+ */
609
761
  async startPlugins() {
762
+ // Check, load and start the plugins
610
763
  for (const plugin of this.plugins) {
611
764
  plugin.configJson = await this.plugins.loadConfig(plugin);
612
765
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
766
+ // Check if the plugin is available
613
767
  if (!(await this.plugins.resolve(plugin.path))) {
614
768
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
615
769
  plugin.enabled = false;
@@ -629,20 +783,26 @@ export class Matterbridge extends EventEmitter {
629
783
  plugin.addedDevices = undefined;
630
784
  plugin.qrPairingCode = undefined;
631
785
  plugin.manualPairingCode = undefined;
632
- this.plugins.load(plugin, true, 'Matterbridge is starting');
786
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
633
787
  }
634
788
  this.frontend.wssSendRefreshRequired();
635
789
  }
790
+ /**
791
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
792
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
793
+ */
636
794
  registerProcessHandlers() {
637
795
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
638
796
  process.removeAllListeners('uncaughtException');
639
797
  process.removeAllListeners('unhandledRejection');
640
798
  this.exceptionHandler = async (error) => {
641
799
  this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
800
+ // await this.cleanup('Unhandled Exception detected, cleaning up...');
642
801
  };
643
802
  process.on('uncaughtException', this.exceptionHandler);
644
803
  this.rejectionHandler = async (reason, promise) => {
645
804
  this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
805
+ // await this.cleanup('Unhandled Rejection detected, cleaning up...');
646
806
  };
647
807
  process.on('unhandledRejection', this.rejectionHandler);
648
808
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -655,6 +815,9 @@ export class Matterbridge extends EventEmitter {
655
815
  };
656
816
  process.on('SIGTERM', this.sigtermHandler);
657
817
  }
818
+ /**
819
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
820
+ */
658
821
  deregisterProcesslHandlers() {
659
822
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
660
823
  if (this.exceptionHandler)
@@ -671,12 +834,17 @@ export class Matterbridge extends EventEmitter {
671
834
  process.off('SIGTERM', this.sigtermHandler);
672
835
  this.sigtermHandler = undefined;
673
836
  }
837
+ /**
838
+ * Logs the node and system information.
839
+ */
674
840
  async logNodeAndSystemInfo() {
841
+ // IP address information
675
842
  const networkInterfaces = os.networkInterfaces();
676
843
  this.systemInformation.interfaceName = '';
677
844
  this.systemInformation.ipv4Address = '';
678
845
  this.systemInformation.ipv6Address = '';
679
846
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
847
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
680
848
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
681
849
  continue;
682
850
  if (!interfaceDetails) {
@@ -702,19 +870,22 @@ export class Matterbridge extends EventEmitter {
702
870
  break;
703
871
  }
704
872
  }
873
+ // Node information
705
874
  this.systemInformation.nodeVersion = process.versions.node;
706
875
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
707
876
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
708
877
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
878
+ // Host system information
709
879
  this.systemInformation.hostname = os.hostname();
710
880
  this.systemInformation.user = os.userInfo().username;
711
- this.systemInformation.osType = os.type();
712
- this.systemInformation.osRelease = os.release();
713
- this.systemInformation.osPlatform = os.platform();
714
- this.systemInformation.osArch = os.arch();
715
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
716
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
717
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
881
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
882
+ this.systemInformation.osRelease = os.release(); // Kernel version
883
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
884
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
885
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
886
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
887
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
888
+ // Log the system information
718
889
  this.log.debug('Host System Information:');
719
890
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
720
891
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -730,16 +901,20 @@ export class Matterbridge extends EventEmitter {
730
901
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
731
902
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
732
903
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
904
+ // Home directory
733
905
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
734
906
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
735
907
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
908
+ // Package root directory
736
909
  const { fileURLToPath } = await import('node:url');
737
910
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
738
911
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
739
912
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
740
913
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
914
+ // Global node_modules directory
741
915
  if (this.nodeContext)
742
916
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
917
+ // First run of Matterbridge so the node storage is empty
743
918
  if (this.globalModulesDirectory === '') {
744
919
  try {
745
920
  this.execRunningCount++;
@@ -755,6 +930,20 @@ export class Matterbridge extends EventEmitter {
755
930
  }
756
931
  else
757
932
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
933
+ /* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
934
+ else {
935
+ this.getGlobalNodeModules()
936
+ .then(async (globalModulesDirectory) => {
937
+ this.globalModulesDirectory = globalModulesDirectory;
938
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
939
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
940
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
941
+ })
942
+ .catch((error) => {
943
+ this.log.error(`Error getting global node_modules directory: ${error}`);
944
+ });
945
+ }*/
946
+ // Create the data directory .matterbridge in the home directory
758
947
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
759
948
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
760
949
  try {
@@ -778,6 +967,7 @@ export class Matterbridge extends EventEmitter {
778
967
  }
779
968
  }
780
969
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
970
+ // Create the plugin directory Matterbridge in the home directory
781
971
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
782
972
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
783
973
  try {
@@ -801,50 +991,68 @@ export class Matterbridge extends EventEmitter {
801
991
  }
802
992
  }
803
993
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
994
+ // Matterbridge version
804
995
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
805
996
  this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
806
997
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
807
998
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
999
+ // Matterbridge latest version
808
1000
  if (this.nodeContext)
809
1001
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
810
1002
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1003
+ // this.getMatterbridgeLatestVersion();
1004
+ // Current working directory
811
1005
  const currentDir = process.cwd();
812
1006
  this.log.debug(`Current Working Directory: ${currentDir}`);
1007
+ // Command line arguments (excluding 'node' and the script name)
813
1008
  const cmdArgs = process.argv.slice(2).join(' ');
814
1009
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
815
1010
  }
1011
+ /**
1012
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1013
+ *
1014
+ * @returns {Function} The MatterLogger function.
1015
+ */
816
1016
  createMatterLogger() {
817
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1017
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
818
1018
  return (_level, formattedLog) => {
819
1019
  const logger = formattedLog.slice(44, 44 + 20).trim();
820
1020
  const message = formattedLog.slice(65);
821
1021
  matterLogger.logName = logger;
822
1022
  switch (_level) {
823
1023
  case MatterLogLevel.DEBUG:
824
- matterLogger.log("debug", message);
1024
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
825
1025
  break;
826
1026
  case MatterLogLevel.INFO:
827
- matterLogger.log("info", message);
1027
+ matterLogger.log("info" /* LogLevel.INFO */, message);
828
1028
  break;
829
1029
  case MatterLogLevel.NOTICE:
830
- matterLogger.log("notice", message);
1030
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
831
1031
  break;
832
1032
  case MatterLogLevel.WARN:
833
- matterLogger.log("warn", message);
1033
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
834
1034
  break;
835
1035
  case MatterLogLevel.ERROR:
836
- matterLogger.log("error", message);
1036
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
837
1037
  break;
838
1038
  case MatterLogLevel.FATAL:
839
- matterLogger.log("fatal", message);
1039
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
840
1040
  break;
841
1041
  default:
842
- matterLogger.log("debug", message);
1042
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
843
1043
  break;
844
1044
  }
845
1045
  };
846
1046
  }
1047
+ /**
1048
+ * Creates a Matter File Logger.
1049
+ *
1050
+ * @param {string} filePath - The path to the log file.
1051
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1052
+ * @returns {Function} - A function that logs formatted messages to the log file.
1053
+ */
847
1054
  async createMatterFileLogger(filePath, unlink = false) {
1055
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
848
1056
  let fileSize = 0;
849
1057
  if (unlink) {
850
1058
  try {
@@ -893,12 +1101,21 @@ export class Matterbridge extends EventEmitter {
893
1101
  }
894
1102
  };
895
1103
  }
1104
+ /**
1105
+ * Restarts the process by exiting the current instance and loading a new instance.
1106
+ */
896
1107
  async restartProcess() {
897
1108
  await this.cleanup('restarting...', true);
898
1109
  }
1110
+ /**
1111
+ * Shut down the process by exiting the current process.
1112
+ */
899
1113
  async shutdownProcess() {
900
1114
  await this.cleanup('shutting down...', false);
901
1115
  }
1116
+ /**
1117
+ * Update matterbridge and and shut down the process.
1118
+ */
902
1119
  async updateProcess() {
903
1120
  this.log.info('Updating matterbridge...');
904
1121
  try {
@@ -911,51 +1128,72 @@ export class Matterbridge extends EventEmitter {
911
1128
  this.frontend.wssSendRestartRequired();
912
1129
  await this.cleanup('updating...', false);
913
1130
  }
1131
+ /**
1132
+ * Unregister all devices and shut down the process.
1133
+ */
914
1134
  async unregisterAndShutdownProcess() {
915
1135
  this.log.info('Unregistering all devices and shutting down...');
916
1136
  for (const plugin of this.plugins) {
917
1137
  await this.removeAllBridgedEndpoints(plugin.name, 250);
918
1138
  }
919
1139
  this.log.debug('Waiting for the MessageExchange to finish...');
920
- await new Promise((resolve) => setTimeout(resolve, 1000));
1140
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
921
1141
  this.log.debug('Cleaning up and shutting down...');
922
1142
  await this.cleanup('unregistered all devices and shutting down...', false);
923
1143
  }
1144
+ /**
1145
+ * Reset commissioning and shut down the process.
1146
+ */
924
1147
  async shutdownProcessAndReset() {
925
1148
  await this.cleanup('shutting down with reset...', false);
926
1149
  }
1150
+ /**
1151
+ * Factory reset and shut down the process.
1152
+ */
927
1153
  async shutdownProcessAndFactoryReset() {
928
1154
  await this.cleanup('shutting down with factory reset...', false);
929
1155
  }
1156
+ /**
1157
+ * Cleans up the Matterbridge instance.
1158
+ * @param message - The cleanup message.
1159
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1160
+ * @returns A promise that resolves when the cleanup is completed.
1161
+ */
930
1162
  async cleanup(message, restart = false) {
931
1163
  if (this.initialized && !this.hasCleanupStarted) {
932
1164
  this.hasCleanupStarted = true;
933
1165
  this.log.info(message);
1166
+ // Clear the start matter interval
934
1167
  if (this.startMatterInterval) {
935
1168
  clearInterval(this.startMatterInterval);
936
1169
  this.startMatterInterval = undefined;
937
1170
  this.log.debug('Start matter interval cleared');
938
1171
  }
1172
+ // Clear the check update timeout
939
1173
  if (this.checkUpdateTimeout) {
940
1174
  clearInterval(this.checkUpdateTimeout);
941
1175
  this.checkUpdateTimeout = undefined;
942
1176
  this.log.debug('Check update timeout cleared');
943
1177
  }
1178
+ // Clear the check update interval
944
1179
  if (this.checkUpdateInterval) {
945
1180
  clearInterval(this.checkUpdateInterval);
946
1181
  this.checkUpdateInterval = undefined;
947
1182
  this.log.debug('Check update interval cleared');
948
1183
  }
1184
+ // Clear the configure timeout
949
1185
  if (this.configureTimeout) {
950
1186
  clearTimeout(this.configureTimeout);
951
1187
  this.configureTimeout = undefined;
952
1188
  this.log.debug('Matterbridge configure timeout cleared');
953
1189
  }
1190
+ // Clear the reachability timeout
954
1191
  if (this.reachabilityTimeout) {
955
1192
  clearTimeout(this.reachabilityTimeout);
956
1193
  this.reachabilityTimeout = undefined;
957
1194
  this.log.debug('Matterbridge reachability timeout cleared');
958
1195
  }
1196
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
959
1197
  for (const plugin of this.plugins) {
960
1198
  if (!plugin.enabled || plugin.error)
961
1199
  continue;
@@ -966,9 +1204,10 @@ export class Matterbridge extends EventEmitter {
966
1204
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
967
1205
  }
968
1206
  }
1207
+ // Stopping matter server nodes
969
1208
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
970
1209
  this.log.debug('Waiting for the MessageExchange to finish...');
971
- await new Promise((resolve) => setTimeout(resolve, 1000));
1210
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
972
1211
  if (this.bridgeMode === 'bridge') {
973
1212
  if (this.serverNode) {
974
1213
  await this.stopServerNode(this.serverNode);
@@ -984,6 +1223,7 @@ export class Matterbridge extends EventEmitter {
984
1223
  }
985
1224
  }
986
1225
  this.log.notice('Stopped matter server nodes');
1226
+ // Matter commisioning reset
987
1227
  if (message === 'shutting down with reset...') {
988
1228
  this.log.info('Resetting Matterbridge commissioning information...');
989
1229
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -993,17 +1233,37 @@ export class Matterbridge extends EventEmitter {
993
1233
  await this.matterbridgeContext?.clearAll();
994
1234
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
995
1235
  }
1236
+ // Stop matter storage
996
1237
  await this.stopMatterStorage();
1238
+ // Stop the frontend
997
1239
  await this.frontend.stop();
1240
+ // Remove the matterfilelogger
998
1241
  try {
999
1242
  Logger.removeLogger('matterfilelogger');
1243
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1000
1244
  }
1001
1245
  catch (error) {
1246
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1002
1247
  }
1248
+ // Serialize registeredDevices
1003
1249
  if (this.nodeStorage && this.nodeContext) {
1250
+ /*
1251
+ TODO: Implement serialization of registered devices in edge mode
1252
+ this.log.info('Saving registered devices...');
1253
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1254
+ this.devices.forEach(async (device) => {
1255
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1256
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1257
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1258
+ });
1259
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1260
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1261
+ */
1262
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1004
1263
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1005
1264
  await this.nodeContext.close();
1006
1265
  this.nodeContext = undefined;
1266
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1007
1267
  for (const plugin of this.plugins) {
1008
1268
  if (plugin.nodeContext) {
1009
1269
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1020,8 +1280,10 @@ export class Matterbridge extends EventEmitter {
1020
1280
  }
1021
1281
  this.plugins.clear();
1022
1282
  this.devices.clear();
1283
+ // Factory reset
1023
1284
  if (message === 'shutting down with factory reset...') {
1024
1285
  try {
1286
+ // Delete old matter storage file and backup
1025
1287
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1026
1288
  this.log.info(`Unlinking old matter storage file: ${file}`);
1027
1289
  await fs.unlink(file);
@@ -1035,6 +1297,7 @@ export class Matterbridge extends EventEmitter {
1035
1297
  }
1036
1298
  }
1037
1299
  try {
1300
+ // Delete matter node storage directory with its subdirectories and backup
1038
1301
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1039
1302
  this.log.info(`Removing matter node storage directory: ${dir}`);
1040
1303
  await fs.rm(dir, { recursive: true });
@@ -1048,6 +1311,7 @@ export class Matterbridge extends EventEmitter {
1048
1311
  }
1049
1312
  }
1050
1313
  try {
1314
+ // Delete node storage directory with its subdirectories and backup
1051
1315
  const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1052
1316
  this.log.info(`Removing storage directory: ${dir}`);
1053
1317
  await fs.rm(dir, { recursive: true });
@@ -1062,12 +1326,13 @@ export class Matterbridge extends EventEmitter {
1062
1326
  }
1063
1327
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1064
1328
  }
1329
+ // Deregisters the process handlers
1065
1330
  this.deregisterProcesslHandlers();
1066
1331
  if (restart) {
1067
1332
  if (message === 'updating...') {
1068
1333
  this.log.info('Cleanup completed. Updating...');
1069
1334
  Matterbridge.instance = undefined;
1070
- this.emit('update');
1335
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1071
1336
  }
1072
1337
  else if (message === 'restarting...') {
1073
1338
  this.log.info('Cleanup completed. Restarting...');
@@ -1087,6 +1352,14 @@ export class Matterbridge extends EventEmitter {
1087
1352
  this.log.debug('Cleanup already started...');
1088
1353
  }
1089
1354
  }
1355
+ /**
1356
+ * Creates and configures the server node for an accessory plugin for a given device.
1357
+ *
1358
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1359
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1360
+ * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1361
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1362
+ */
1090
1363
  async createAccessoryPlugin(plugin, device, start = false) {
1091
1364
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1092
1365
  plugin.locked = true;
@@ -1099,6 +1372,13 @@ export class Matterbridge extends EventEmitter {
1099
1372
  await this.startServerNode(plugin.serverNode);
1100
1373
  }
1101
1374
  }
1375
+ /**
1376
+ * Creates and configures the server node for a dynamic plugin.
1377
+ *
1378
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1379
+ * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1380
+ * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1381
+ */
1102
1382
  async createDynamicPlugin(plugin, start = false) {
1103
1383
  if (!plugin.locked) {
1104
1384
  plugin.locked = true;
@@ -1110,7 +1390,13 @@ export class Matterbridge extends EventEmitter {
1110
1390
  await this.startServerNode(plugin.serverNode);
1111
1391
  }
1112
1392
  }
1393
+ /**
1394
+ * Starts the Matterbridge in bridge mode.
1395
+ * @private
1396
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1397
+ */
1113
1398
  async startBridge() {
1399
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1114
1400
  if (!this.matterStorageManager)
1115
1401
  throw new Error('No storage manager initialized');
1116
1402
  if (!this.matterbridgeContext)
@@ -1147,7 +1433,9 @@ export class Matterbridge extends EventEmitter {
1147
1433
  clearInterval(this.startMatterInterval);
1148
1434
  this.startMatterInterval = undefined;
1149
1435
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1436
+ // Start the Matter server node
1150
1437
  this.startServerNode(this.serverNode);
1438
+ // Configure the plugins
1151
1439
  this.configureTimeout = setTimeout(async () => {
1152
1440
  for (const plugin of this.plugins) {
1153
1441
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1162,6 +1450,7 @@ export class Matterbridge extends EventEmitter {
1162
1450
  }
1163
1451
  this.frontend.wssSendRefreshRequired();
1164
1452
  }, 30 * 1000);
1453
+ // Setting reachability to true
1165
1454
  this.reachabilityTimeout = setTimeout(() => {
1166
1455
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1167
1456
  if (this.aggregatorNode)
@@ -1170,6 +1459,11 @@ export class Matterbridge extends EventEmitter {
1170
1459
  }, 60 * 1000);
1171
1460
  }, 1000);
1172
1461
  }
1462
+ /**
1463
+ * Starts the Matterbridge in childbridge mode.
1464
+ * @private
1465
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1466
+ */
1173
1467
  async startChildbridge() {
1174
1468
  if (!this.matterStorageManager)
1175
1469
  throw new Error('No storage manager initialized');
@@ -1213,12 +1507,13 @@ export class Matterbridge extends EventEmitter {
1213
1507
  clearInterval(this.startMatterInterval);
1214
1508
  this.startMatterInterval = undefined;
1215
1509
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1510
+ // Configure the plugins
1216
1511
  this.configureTimeout = setTimeout(async () => {
1217
1512
  for (const plugin of this.plugins) {
1218
1513
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1219
1514
  continue;
1220
1515
  try {
1221
- await this.plugins.configure(plugin);
1516
+ await this.plugins.configure(plugin); // TODO No await do it in parallel
1222
1517
  }
1223
1518
  catch (error) {
1224
1519
  plugin.error = true;
@@ -1246,7 +1541,9 @@ export class Matterbridge extends EventEmitter {
1246
1541
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1247
1542
  continue;
1248
1543
  }
1544
+ // Start the Matter server node
1249
1545
  this.startServerNode(plugin.serverNode);
1546
+ // Setting reachability to true
1250
1547
  plugin.reachabilityTimeout = setTimeout(() => {
1251
1548
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggragator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
1252
1549
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1256,9 +1553,219 @@ export class Matterbridge extends EventEmitter {
1256
1553
  }
1257
1554
  }, 1000);
1258
1555
  }
1556
+ /**
1557
+ * Starts the Matterbridge controller.
1558
+ * @private
1559
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1560
+ */
1259
1561
  async startController() {
1562
+ /*
1563
+ if (!this.storageManager) {
1564
+ this.log.error('No storage manager initialized');
1565
+ await this.cleanup('No storage manager initialized');
1566
+ return;
1567
+ }
1568
+ this.log.info('Creating context: mattercontrollerContext');
1569
+ this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
1570
+ if (!this.mattercontrollerContext) {
1571
+ this.log.error('No storage context mattercontrollerContext initialized');
1572
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1573
+ return;
1574
+ }
1575
+
1576
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1577
+ this.matterServer = await this.createMatterServer(this.storageManager);
1578
+ this.log.info('Creating matter commissioning controller');
1579
+ this.commissioningController = new CommissioningController({
1580
+ autoConnect: false,
1581
+ });
1582
+ this.log.info('Adding matter commissioning controller to matter server');
1583
+ await this.matterServer.addCommissioningController(this.commissioningController);
1584
+
1585
+ this.log.info('Starting matter server');
1586
+ await this.matterServer.start();
1587
+ this.log.info('Matter server started');
1588
+
1589
+ if (hasParameter('pairingcode')) {
1590
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1591
+ const pairingCode = getParameter('pairingcode');
1592
+ const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
1593
+ const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
1594
+
1595
+ let longDiscriminator, setupPin, shortDiscriminator;
1596
+ if (pairingCode !== undefined) {
1597
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1598
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1599
+ longDiscriminator = undefined;
1600
+ setupPin = pairingCodeCodec.passcode;
1601
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1602
+ } else {
1603
+ longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1604
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1605
+ setupPin = this.mattercontrollerContext.get('pin', 20202021);
1606
+ }
1607
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1608
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1609
+ }
1610
+
1611
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1612
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1613
+ regulatoryCountryCode: 'XX',
1614
+ };
1615
+ const options = {
1616
+ commissioning: commissioningOptions,
1617
+ discovery: {
1618
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1619
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1620
+ },
1621
+ passcode: setupPin,
1622
+ } as NodeCommissioningOptions;
1623
+ this.log.info('Commissioning with options:', options);
1624
+ const nodeId = await this.commissioningController.commissionNode(options);
1625
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1626
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1627
+ } // (hasParameter('pairingcode'))
1628
+
1629
+ if (hasParameter('unpairall')) {
1630
+ this.log.info('***Commissioning controller unpairing all nodes...');
1631
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1632
+ for (const nodeId of nodeIds) {
1633
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1634
+ await this.commissioningController.removeNode(nodeId);
1635
+ }
1636
+ return;
1637
+ }
1638
+
1639
+ if (hasParameter('discover')) {
1640
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1641
+ // console.log(discover);
1642
+ }
1643
+
1644
+ if (!this.commissioningController.isCommissioned()) {
1645
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1646
+ return;
1647
+ }
1648
+
1649
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1650
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1651
+ for (const nodeId of nodeIds) {
1652
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1653
+
1654
+ const node = await this.commissioningController.connectNode(nodeId, {
1655
+ autoSubscribe: false,
1656
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1657
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1658
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1659
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1660
+ stateInformationCallback: (peerNodeId, info) => {
1661
+ switch (info) {
1662
+ case NodeStateInformation.Connected:
1663
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1664
+ break;
1665
+ case NodeStateInformation.Disconnected:
1666
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1667
+ break;
1668
+ case NodeStateInformation.Reconnecting:
1669
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1670
+ break;
1671
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1672
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1673
+ break;
1674
+ case NodeStateInformation.StructureChanged:
1675
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1676
+ break;
1677
+ case NodeStateInformation.Decommissioned:
1678
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1679
+ break;
1680
+ default:
1681
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1682
+ break;
1683
+ }
1684
+ },
1685
+ });
1686
+
1687
+ node.logStructure();
1688
+
1689
+ // Get the interaction client
1690
+ this.log.info('Getting the interaction client');
1691
+ const interactionClient = await node.getInteractionClient();
1692
+ let cluster;
1693
+ let attributes;
1694
+
1695
+ // Log BasicInformationCluster
1696
+ cluster = BasicInformationCluster;
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 PowerSourceCluster
1708
+ cluster = PowerSourceCluster;
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 ThreadNetworkDiagnostics
1720
+ cluster = ThreadNetworkDiagnosticsCluster;
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
+ // Log SwitchCluster
1732
+ cluster = SwitchCluster;
1733
+ attributes = await interactionClient.getMultipleAttributes({
1734
+ attributes: [{ clusterId: cluster.id }],
1735
+ });
1736
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1737
+ attributes.forEach((attribute) => {
1738
+ this.log.info(
1739
+ `- 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}`,
1740
+ );
1741
+ });
1742
+
1743
+ this.log.info('Subscribing to all attributes and events');
1744
+ await node.subscribeAllAttributesAndEvents({
1745
+ ignoreInitialTriggers: false,
1746
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1747
+ this.log.info(
1748
+ `***${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}`,
1749
+ ),
1750
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1751
+ this.log.info(
1752
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1753
+ );
1754
+ },
1755
+ });
1756
+ this.log.info('Subscribed to all attributes and events');
1757
+ }
1758
+ */
1260
1759
  }
1760
+ /** ***********************************************************************************************************************************/
1761
+ /** Matter.js methods */
1762
+ /** ***********************************************************************************************************************************/
1763
+ /**
1764
+ * Starts the matter storage process with name Matterbridge.
1765
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1766
+ */
1261
1767
  async startMatterStorage() {
1768
+ // Setup Matter storage
1262
1769
  this.log.info(`Starting matter node storage...`);
1263
1770
  this.matterStorageService = this.environment.get(StorageService);
1264
1771
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1266,13 +1773,25 @@ export class Matterbridge extends EventEmitter {
1266
1773
  this.log.info('Matter node storage manager "Matterbridge" created');
1267
1774
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
1268
1775
  this.log.info('Matter node storage started');
1776
+ // Backup matter storage since it is created/opened correctly
1269
1777
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1270
1778
  }
1779
+ /**
1780
+ * Makes a backup copy of the specified matter storage directory.
1781
+ *
1782
+ * @param storageName - The name of the storage directory to be backed up.
1783
+ * @param backupName - The name of the backup directory to be created.
1784
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1785
+ */
1271
1786
  async backupMatterStorage(storageName, backupName) {
1272
1787
  this.log.info('Creating matter node storage backup...');
1273
1788
  await copyDirectory(storageName, backupName);
1274
1789
  this.log.info('Created matter node storage backup');
1275
1790
  }
1791
+ /**
1792
+ * Stops the matter storage.
1793
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1794
+ */
1276
1795
  async stopMatterStorage() {
1277
1796
  this.log.info('Closing matter node storage...');
1278
1797
  this.matterStorageManager?.close();
@@ -1281,6 +1800,19 @@ export class Matterbridge extends EventEmitter {
1281
1800
  this.matterbridgeContext = undefined;
1282
1801
  this.log.info('Matter node storage closed');
1283
1802
  }
1803
+ /**
1804
+ * Creates a server node storage context.
1805
+ *
1806
+ * @param {string} pluginName - The name of the plugin.
1807
+ * @param {string} deviceName - The name of the device.
1808
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1809
+ * @param {number} vendorId - The vendor ID.
1810
+ * @param {string} vendorName - The vendor name.
1811
+ * @param {number} productId - The product ID.
1812
+ * @param {string} productName - The product name.
1813
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1814
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1815
+ */
1284
1816
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1285
1817
  const { randomBytes } = await import('node:crypto');
1286
1818
  if (!this.matterStorageService)
@@ -1314,6 +1846,15 @@ export class Matterbridge extends EventEmitter {
1314
1846
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1315
1847
  return storageContext;
1316
1848
  }
1849
+ /**
1850
+ * Creates a server node.
1851
+ *
1852
+ * @param {StorageContext} storageContext - The storage context for the server node.
1853
+ * @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
1854
+ * @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
1855
+ * @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
1856
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1857
+ */
1317
1858
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1318
1859
  const storeId = await storageContext.get('storeId');
1319
1860
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1323,21 +1864,33 @@ export class Matterbridge extends EventEmitter {
1323
1864
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1324
1865
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1325
1866
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1867
+ /**
1868
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1869
+ */
1326
1870
  const serverNode = await ServerNode.create({
1871
+ // Required: Give the Node a unique ID which is used to store the state of this node
1327
1872
  id: storeId,
1873
+ // Provide Network relevant configuration like the port
1874
+ // Optional when operating only one device on a host, Default port is 5540
1328
1875
  network: {
1329
1876
  listeningAddressIpv4: this.ipv4address,
1330
1877
  listeningAddressIpv6: this.ipv6address,
1331
1878
  port,
1332
1879
  },
1880
+ // Provide Commissioning relevant settings
1881
+ // Optional for development/testing purposes
1333
1882
  commissioning: {
1334
1883
  passcode,
1335
1884
  discriminator,
1336
1885
  },
1886
+ // Provide Node announcement settings
1887
+ // Optional: If Ommitted some development defaults are used
1337
1888
  productDescription: {
1338
1889
  name: await storageContext.get('deviceName'),
1339
1890
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1340
1891
  },
1892
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1893
+ // Optional: If Omitted some development defaults are used
1341
1894
  basicInformation: {
1342
1895
  vendorId: VendorId(await storageContext.get('vendorId')),
1343
1896
  vendorName: await storageContext.get('vendorName'),
@@ -1354,12 +1907,13 @@ export class Matterbridge extends EventEmitter {
1354
1907
  },
1355
1908
  });
1356
1909
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
1910
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1357
1911
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1358
1912
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1359
1913
  if (this.bridgeMode === 'bridge') {
1360
1914
  this.matterbridgeFabricInformations = sanitizedFabrics;
1361
1915
  if (resetSessions)
1362
- this.matterbridgeSessionInformations = undefined;
1916
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1363
1917
  this.matterbridgePaired = true;
1364
1918
  }
1365
1919
  if (this.bridgeMode === 'childbridge') {
@@ -1367,13 +1921,19 @@ export class Matterbridge extends EventEmitter {
1367
1921
  if (plugin) {
1368
1922
  plugin.fabricInformations = sanitizedFabrics;
1369
1923
  if (resetSessions)
1370
- plugin.sessionInformations = undefined;
1924
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1371
1925
  plugin.paired = true;
1372
1926
  }
1373
1927
  }
1374
1928
  };
1929
+ /**
1930
+ * This event is triggered when the device is initially commissioned successfully.
1931
+ * This means: It is added to the first fabric.
1932
+ */
1375
1933
  serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
1934
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1376
1935
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1936
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1377
1937
  serverNode.lifecycle.online.on(async () => {
1378
1938
  this.log.notice(`Server node for ${storeId} is online`);
1379
1939
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1420,6 +1980,7 @@ export class Matterbridge extends EventEmitter {
1420
1980
  this.frontend.wssSendRefreshRequired();
1421
1981
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`);
1422
1982
  });
1983
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1423
1984
  serverNode.lifecycle.offline.on(() => {
1424
1985
  this.log.notice(`Server node for ${storeId} is offline`);
1425
1986
  if (this.bridgeMode === 'bridge') {
@@ -1441,6 +2002,10 @@ export class Matterbridge extends EventEmitter {
1441
2002
  }
1442
2003
  this.frontend.wssSendRefreshRequired();
1443
2004
  });
2005
+ /**
2006
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2007
+ * information is needed.
2008
+ */
1444
2009
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1445
2010
  let action = '';
1446
2011
  switch (fabricAction) {
@@ -1474,16 +2039,24 @@ export class Matterbridge extends EventEmitter {
1474
2039
  }
1475
2040
  }
1476
2041
  };
2042
+ /**
2043
+ * This event is triggered when an operative new session was opened by a Controller.
2044
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2045
+ */
1477
2046
  serverNode.events.sessions.opened.on((session) => {
1478
2047
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1479
2048
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1480
2049
  this.frontend.wssSendRefreshRequired();
1481
2050
  });
2051
+ /**
2052
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2053
+ */
1482
2054
  serverNode.events.sessions.closed.on((session) => {
1483
2055
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1484
2056
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1485
2057
  this.frontend.wssSendRefreshRequired();
1486
2058
  });
2059
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1487
2060
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1488
2061
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1489
2062
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1492,24 +2065,42 @@ export class Matterbridge extends EventEmitter {
1492
2065
  this.log.info(`Created server node for ${storeId}`);
1493
2066
  return serverNode;
1494
2067
  }
2068
+ /**
2069
+ * Starts the specified server node.
2070
+ *
2071
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2072
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2073
+ */
1495
2074
  async startServerNode(matterServerNode) {
1496
2075
  if (!matterServerNode)
1497
2076
  return;
1498
2077
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1499
2078
  await matterServerNode.start();
1500
2079
  }
2080
+ /**
2081
+ * Stops the specified server node.
2082
+ *
2083
+ * @param {ServerNode} matterServerNode - The server node to stop.
2084
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2085
+ */
1501
2086
  async stopServerNode(matterServerNode) {
1502
2087
  if (!matterServerNode)
1503
2088
  return;
1504
2089
  this.log.notice(`Closing ${matterServerNode.id} server node`);
1505
2090
  try {
1506
- await withTimeout(matterServerNode.close(), 30000);
2091
+ await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
1507
2092
  this.log.info(`Closed ${matterServerNode.id} server node`);
1508
2093
  }
1509
2094
  catch (error) {
1510
2095
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1511
2096
  }
1512
2097
  }
2098
+ /**
2099
+ * Advertises the specified server node.
2100
+ *
2101
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2102
+ * @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.
2103
+ */
1513
2104
  async advertiseServerNode(matterServerNode) {
1514
2105
  if (matterServerNode) {
1515
2106
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1518,23 +2109,44 @@ export class Matterbridge extends EventEmitter {
1518
2109
  return { qrPairingCode, manualPairingCode };
1519
2110
  }
1520
2111
  }
2112
+ /**
2113
+ * Stop advertise the specified server node.
2114
+ *
2115
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2116
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2117
+ */
1521
2118
  async stopAdvertiseServerNode(matterServerNode) {
1522
2119
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1523
2120
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1524
2121
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1525
2122
  }
1526
2123
  }
2124
+ /**
2125
+ * Creates an aggregator node with the specified storage context.
2126
+ *
2127
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2128
+ * @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2129
+ */
1527
2130
  async createAggregatorNode(storageContext) {
1528
2131
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
1529
2132
  const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1530
2133
  return aggregatorNode;
1531
2134
  }
2135
+ /**
2136
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2137
+ *
2138
+ * @param {string} pluginName - The name of the plugin.
2139
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2140
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2141
+ */
1532
2142
  async addBridgedEndpoint(pluginName, device) {
2143
+ // Check if the plugin is registered
1533
2144
  const plugin = this.plugins.get(pluginName);
1534
2145
  if (!plugin) {
1535
2146
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1536
2147
  return;
1537
2148
  }
2149
+ // Register and add the device to the matterbridge aggregator node
1538
2150
  if (this.bridgeMode === 'bridge') {
1539
2151
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1540
2152
  if (!this.aggregatorNode)
@@ -1557,16 +2169,26 @@ export class Matterbridge extends EventEmitter {
1557
2169
  plugin.registeredDevices++;
1558
2170
  if (plugin.addedDevices !== undefined)
1559
2171
  plugin.addedDevices++;
2172
+ // Add the device to the DeviceManager
1560
2173
  this.devices.set(device);
1561
2174
  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}`);
1562
2175
  }
2176
+ /**
2177
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2178
+ *
2179
+ * @param {string} pluginName - The name of the plugin.
2180
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2181
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2182
+ */
1563
2183
  async removeBridgedEndpoint(pluginName, device) {
1564
2184
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2185
+ // Check if the plugin is registered
1565
2186
  const plugin = this.plugins.get(pluginName);
1566
2187
  if (!plugin) {
1567
2188
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1568
2189
  return;
1569
2190
  }
2191
+ // Register and add the device to the matterbridge aggregator node
1570
2192
  if (this.bridgeMode === 'bridge') {
1571
2193
  if (!this.aggregatorNode) {
1572
2194
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1581,6 +2203,7 @@ export class Matterbridge extends EventEmitter {
1581
2203
  }
1582
2204
  else if (this.bridgeMode === 'childbridge') {
1583
2205
  if (plugin.type === 'AccessoryPlatform') {
2206
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1584
2207
  }
1585
2208
  else if (plugin.type === 'DynamicPlatform') {
1586
2209
  if (!plugin.aggregatorNode) {
@@ -1595,8 +2218,16 @@ export class Matterbridge extends EventEmitter {
1595
2218
  if (plugin.addedDevices !== undefined)
1596
2219
  plugin.addedDevices--;
1597
2220
  }
2221
+ // Remove the device from the DeviceManager
1598
2222
  this.devices.remove(device);
1599
2223
  }
2224
+ /**
2225
+ * Removes all bridged endpoints from the specified plugin.
2226
+ *
2227
+ * @param {string} pluginName - The name of the plugin.
2228
+ * @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2229
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2230
+ */
1600
2231
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1601
2232
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
1602
2233
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1605,6 +2236,12 @@ export class Matterbridge extends EventEmitter {
1605
2236
  await new Promise((resolve) => setTimeout(resolve, delay));
1606
2237
  }
1607
2238
  }
2239
+ /**
2240
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2241
+ *
2242
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2243
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2244
+ */
1608
2245
  sanitizeFabricInformations(fabricInfo) {
1609
2246
  return fabricInfo.map((info) => {
1610
2247
  return {
@@ -1618,6 +2255,12 @@ export class Matterbridge extends EventEmitter {
1618
2255
  };
1619
2256
  });
1620
2257
  }
2258
+ /**
2259
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2260
+ *
2261
+ * @param {SessionInformation[]} sessionInfo - The array of session information objects.
2262
+ * @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
2263
+ */
1621
2264
  sanitizeSessionInformation(sessionInfo) {
1622
2265
  return sessionInfo
1623
2266
  .filter((session) => session.isPeerActive)
@@ -1645,6 +2288,11 @@ export class Matterbridge extends EventEmitter {
1645
2288
  };
1646
2289
  });
1647
2290
  }
2291
+ /**
2292
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2293
+ * @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2294
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2295
+ */
1648
2296
  async setAggregatorReachability(aggregatorNode, reachable) {
1649
2297
  for (const child of aggregatorNode.parts) {
1650
2298
  this.log.debug(`Setting reachability of ${child?.deviceName} to ${reachable}`);
@@ -1690,14 +2338,37 @@ export class Matterbridge extends EventEmitter {
1690
2338
  }
1691
2339
  return vendorName;
1692
2340
  };
2341
+ /**
2342
+ * Spawns a child process with the given command and arguments.
2343
+ * @param {string} command - The command to execute.
2344
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2345
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2346
+ */
1693
2347
  async spawnCommand(command, args = []) {
1694
2348
  const { spawn } = await import('node:child_process');
2349
+ /*
2350
+ npm > npm.cmd on windows
2351
+ cmd.exe ['dir'] on windows
2352
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2353
+ process.on('unhandledRejection', (reason, promise) => {
2354
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2355
+ });
2356
+
2357
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2358
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2359
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2360
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2361
+ */
1695
2362
  const cmdLine = command + ' ' + args.join(' ');
1696
2363
  if (process.platform === 'win32' && command === 'npm') {
2364
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
1697
2365
  const argstring = 'npm ' + args.join(' ');
1698
2366
  args.splice(0, args.length, '/c', argstring);
1699
2367
  command = 'cmd.exe';
1700
2368
  }
2369
+ // Decide when using sudo on linux
2370
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2371
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
1701
2372
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
1702
2373
  args.unshift(command);
1703
2374
  command = 'sudo';
@@ -1756,3 +2427,4 @@ export class Matterbridge extends EventEmitter {
1756
2427
  });
1757
2428
  }
1758
2429
  }
2430
+ //# sourceMappingURL=matterbridge.js.map