matterbridge 3.0.3-dev-20250520-e6e7257 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/cli.d.ts +29 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +37 -2
  4. package/dist/cli.js.map +1 -0
  5. package/dist/cluster/export.d.ts +2 -0
  6. package/dist/cluster/export.d.ts.map +1 -0
  7. package/dist/cluster/export.js +2 -0
  8. package/dist/cluster/export.js.map +1 -0
  9. package/dist/defaultConfigSchema.d.ts +27 -0
  10. package/dist/defaultConfigSchema.d.ts.map +1 -0
  11. package/dist/defaultConfigSchema.js +23 -0
  12. package/dist/defaultConfigSchema.js.map +1 -0
  13. package/dist/deviceManager.d.ts +114 -0
  14. package/dist/deviceManager.d.ts.map +1 -0
  15. package/dist/deviceManager.js +94 -1
  16. package/dist/deviceManager.js.map +1 -0
  17. package/dist/frontend.d.ts +241 -0
  18. package/dist/frontend.d.ts.map +1 -0
  19. package/dist/frontend.js +333 -15
  20. package/dist/frontend.js.map +1 -0
  21. package/dist/helpers.d.ts +47 -0
  22. package/dist/helpers.d.ts.map +1 -0
  23. package/dist/helpers.js +50 -0
  24. package/dist/helpers.js.map +1 -0
  25. package/dist/index.d.ts +35 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +27 -1
  28. package/dist/index.js.map +1 -0
  29. package/dist/logger/export.d.ts +2 -0
  30. package/dist/logger/export.d.ts.map +1 -0
  31. package/dist/logger/export.js +1 -0
  32. package/dist/logger/export.js.map +1 -0
  33. package/dist/matter/behaviors.d.ts +2 -0
  34. package/dist/matter/behaviors.d.ts.map +1 -0
  35. package/dist/matter/behaviors.js +2 -0
  36. package/dist/matter/behaviors.js.map +1 -0
  37. package/dist/matter/clusters.d.ts +2 -0
  38. package/dist/matter/clusters.d.ts.map +1 -0
  39. package/dist/matter/clusters.js +2 -0
  40. package/dist/matter/clusters.js.map +1 -0
  41. package/dist/matter/devices.d.ts +2 -0
  42. package/dist/matter/devices.d.ts.map +1 -0
  43. package/dist/matter/devices.js +2 -0
  44. package/dist/matter/devices.js.map +1 -0
  45. package/dist/matter/endpoints.d.ts +2 -0
  46. package/dist/matter/endpoints.d.ts.map +1 -0
  47. package/dist/matter/endpoints.js +2 -0
  48. package/dist/matter/endpoints.js.map +1 -0
  49. package/dist/matter/export.d.ts +5 -0
  50. package/dist/matter/export.d.ts.map +1 -0
  51. package/dist/matter/export.js +2 -0
  52. package/dist/matter/export.js.map +1 -0
  53. package/dist/matter/types.d.ts +3 -0
  54. package/dist/matter/types.d.ts.map +1 -0
  55. package/dist/matter/types.js +2 -0
  56. package/dist/matter/types.js.map +1 -0
  57. package/dist/matterbridge.d.ts +445 -0
  58. package/dist/matterbridge.d.ts.map +1 -0
  59. package/dist/matterbridge.js +747 -47
  60. package/dist/matterbridge.js.map +1 -0
  61. package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
  62. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  63. package/dist/matterbridgeAccessoryPlatform.js +34 -0
  64. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  65. package/dist/matterbridgeBehaviors.d.ts +1201 -0
  66. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  67. package/dist/matterbridgeBehaviors.js +60 -4
  68. package/dist/matterbridgeBehaviors.js.map +1 -0
  69. package/dist/matterbridgeDeviceTypes.d.ts +629 -0
  70. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  71. package/dist/matterbridgeDeviceTypes.js +563 -15
  72. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  73. package/dist/matterbridgeDynamicPlatform.d.ts +40 -0
  74. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  75. package/dist/matterbridgeDynamicPlatform.js +34 -0
  76. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  77. package/dist/matterbridgeEndpoint.d.ts +967 -0
  78. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  79. package/dist/matterbridgeEndpoint.js +807 -11
  80. package/dist/matterbridgeEndpoint.js.map +1 -0
  81. package/dist/matterbridgeEndpointHelpers.d.ts +2728 -0
  82. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  83. package/dist/matterbridgeEndpointHelpers.js +149 -10
  84. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  85. package/dist/matterbridgePlatform.d.ts +294 -0
  86. package/dist/matterbridgePlatform.d.ts.map +1 -0
  87. package/dist/matterbridgePlatform.js +225 -7
  88. package/dist/matterbridgePlatform.js.map +1 -0
  89. package/dist/matterbridgeTypes.d.ts +188 -0
  90. package/dist/matterbridgeTypes.d.ts.map +1 -0
  91. package/dist/matterbridgeTypes.js +24 -0
  92. package/dist/matterbridgeTypes.js.map +1 -0
  93. package/dist/pluginManager.d.ts +273 -0
  94. package/dist/pluginManager.d.ts.map +1 -0
  95. package/dist/pluginManager.js +264 -3
  96. package/dist/pluginManager.js.map +1 -0
  97. package/dist/roboticVacuumCleaner.d.ts +82 -0
  98. package/dist/roboticVacuumCleaner.d.ts.map +1 -0
  99. package/dist/roboticVacuumCleaner.js +78 -3
  100. package/dist/roboticVacuumCleaner.js.map +1 -0
  101. package/dist/shelly.d.ts +153 -0
  102. package/dist/shelly.d.ts.map +1 -0
  103. package/dist/shelly.js +155 -7
  104. package/dist/shelly.js.map +1 -0
  105. package/dist/storage/export.d.ts +2 -0
  106. package/dist/storage/export.d.ts.map +1 -0
  107. package/dist/storage/export.js +1 -0
  108. package/dist/storage/export.js.map +1 -0
  109. package/dist/update.d.ts +58 -0
  110. package/dist/update.d.ts.map +1 -0
  111. package/dist/update.js +53 -0
  112. package/dist/update.js.map +1 -0
  113. package/dist/utils/colorUtils.d.ts +61 -0
  114. package/dist/utils/colorUtils.d.ts.map +1 -0
  115. package/dist/utils/colorUtils.js +205 -2
  116. package/dist/utils/colorUtils.js.map +1 -0
  117. package/dist/utils/commandLine.d.ts +58 -0
  118. package/dist/utils/commandLine.d.ts.map +1 -0
  119. package/dist/utils/commandLine.js +53 -0
  120. package/dist/utils/commandLine.js.map +1 -0
  121. package/dist/utils/copyDirectory.d.ts +32 -0
  122. package/dist/utils/copyDirectory.d.ts.map +1 -0
  123. package/dist/utils/copyDirectory.js +37 -1
  124. package/dist/utils/copyDirectory.js.map +1 -0
  125. package/dist/utils/createZip.d.ts +38 -0
  126. package/dist/utils/createZip.d.ts.map +1 -0
  127. package/dist/utils/createZip.js +42 -2
  128. package/dist/utils/createZip.js.map +1 -0
  129. package/dist/utils/deepCopy.d.ts +31 -0
  130. package/dist/utils/deepCopy.d.ts.map +1 -0
  131. package/dist/utils/deepCopy.js +38 -0
  132. package/dist/utils/deepCopy.js.map +1 -0
  133. package/dist/utils/deepEqual.d.ts +53 -0
  134. package/dist/utils/deepEqual.d.ts.map +1 -0
  135. package/dist/utils/deepEqual.js +71 -1
  136. package/dist/utils/deepEqual.js.map +1 -0
  137. package/dist/utils/export.d.ts +11 -0
  138. package/dist/utils/export.d.ts.map +1 -0
  139. package/dist/utils/export.js +1 -0
  140. package/dist/utils/export.js.map +1 -0
  141. package/dist/utils/hex.d.ts +48 -0
  142. package/dist/utils/hex.d.ts.map +1 -0
  143. package/dist/utils/hex.js +57 -0
  144. package/dist/utils/hex.js.map +1 -0
  145. package/dist/utils/isvalid.d.ts +102 -0
  146. package/dist/utils/isvalid.d.ts.map +1 -0
  147. package/dist/utils/isvalid.js +100 -0
  148. package/dist/utils/isvalid.js.map +1 -0
  149. package/dist/utils/network.d.ts +69 -0
  150. package/dist/utils/network.d.ts.map +1 -0
  151. package/dist/utils/network.js +76 -5
  152. package/dist/utils/network.js.map +1 -0
  153. package/dist/utils/wait.d.ts +52 -0
  154. package/dist/utils/wait.d.ts.map +1 -0
  155. package/dist/utils/wait.js +58 -9
  156. package/dist/utils/wait.js.map +1 -0
  157. package/dist/waterHeater.d.ts +75 -0
  158. package/dist/waterHeater.d.ts.map +1 -0
  159. package/dist/waterHeater.js +52 -0
  160. package/dist/waterHeater.js.map +1 -0
  161. package/npm-shrinkwrap.json +2 -2
  162. package/package.json +2 -1
@@ -1,10 +1,36 @@
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.3
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';
5
28
  import { inspect } from 'node:util';
29
+ // AnsiLogger module
6
30
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
31
+ // NodeStorage module
7
32
  import { NodeStorageManager } from './storage/export.js';
33
+ // Matterbridge
8
34
  import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber } from './utils/export.js';
9
35
  import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
10
36
  import { dev, plg, typ } from './matterbridgeTypes.js';
@@ -14,11 +40,15 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
14
40
  import { bridge } from './matterbridgeDeviceTypes.js';
15
41
  import { Frontend } from './frontend.js';
16
42
  import { addVirtualDevices } from './helpers.js';
43
+ // @matter
17
44
  import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
18
45
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
19
46
  import { AggregatorEndpoint } from '@matter/main/endpoints';
20
47
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
21
48
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
49
+ /**
50
+ * Represents the Matterbridge application.
51
+ */
22
52
  export class Matterbridge extends EventEmitter {
23
53
  systemInformation = {
24
54
  interfaceName: '',
@@ -66,7 +96,7 @@ export class Matterbridge extends EventEmitter {
66
96
  shellySysUpdate: false,
67
97
  shellyMainUpdate: false,
68
98
  profile: getParameter('profile'),
69
- loggerLevel: "info",
99
+ loggerLevel: "info" /* LogLevel.INFO */,
70
100
  fileLogger: false,
71
101
  matterLoggerLevel: MatterLogLevel.INFO,
72
102
  matterFileLogger: false,
@@ -105,9 +135,11 @@ export class Matterbridge extends EventEmitter {
105
135
  plugins;
106
136
  devices;
107
137
  frontend = new Frontend(this);
138
+ // Matterbridge storage
108
139
  nodeStorage;
109
140
  nodeContext;
110
141
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
142
+ // Cleanup
111
143
  hasCleanupStarted = false;
112
144
  initialized = false;
113
145
  execRunningCount = 0;
@@ -120,19 +152,22 @@ export class Matterbridge extends EventEmitter {
120
152
  sigtermHandler;
121
153
  exceptionHandler;
122
154
  rejectionHandler;
155
+ // Matter environment
123
156
  environment = Environment.default;
157
+ // Matter storage
124
158
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
125
159
  matterStorageService;
126
160
  matterStorageManager;
127
161
  matterbridgeContext;
128
162
  controllerContext;
129
- mdnsInterface;
130
- ipv4address;
131
- ipv6address;
132
- port;
133
- passcode;
134
- discriminator;
135
- certification;
163
+ // Matter parameters
164
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
165
+ ipv4address; // matter server node listeningAddressIpv4
166
+ ipv6address; // matter server node listeningAddressIpv6
167
+ port; // first server node port
168
+ passcode; // first server node passcode
169
+ discriminator; // first server node discriminator
170
+ certification; // device certification
136
171
  serverNode;
137
172
  aggregatorNode;
138
173
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
@@ -140,21 +175,50 @@ export class Matterbridge extends EventEmitter {
140
175
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
141
176
  aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
142
177
  static instance;
178
+ // We load asyncronously so is private
143
179
  constructor() {
144
180
  super();
145
181
  }
182
+ /**
183
+ * Emits an event of the specified type with the provided arguments.
184
+ *
185
+ * @template K - The type of the event.
186
+ * @param {K} eventName - The name of the event to emit.
187
+ * @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
188
+ * @returns {boolean} - Returns true if the event had listeners, false otherwise.
189
+ */
146
190
  emit(eventName, ...args) {
147
191
  return super.emit(eventName, ...args);
148
192
  }
193
+ /**
194
+ * Registers an event listener for the specified event type.
195
+ *
196
+ * @template K - The type of the event.
197
+ * @param {K} eventName - The name of the event to listen for.
198
+ * @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
199
+ * @returns {this} - Returns the instance of the Matterbridge class.
200
+ */
149
201
  on(eventName, listener) {
150
202
  return super.on(eventName, listener);
151
203
  }
204
+ /**
205
+ * Retrieves the list of Matterbridge devices.
206
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
207
+ */
152
208
  getDevices() {
153
209
  return this.devices.array();
154
210
  }
211
+ /**
212
+ * Retrieves the list of registered plugins.
213
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
214
+ */
155
215
  getPlugins() {
156
216
  return this.plugins.array();
157
217
  }
218
+ /**
219
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
220
+ * @param {LogLevel} logLevel The logger logLevel to set.
221
+ */
158
222
  async setLogLevel(logLevel) {
159
223
  if (this.log)
160
224
  this.log.logLevel = logLevel;
@@ -168,19 +232,31 @@ export class Matterbridge extends EventEmitter {
168
232
  for (const plugin of this.plugins) {
169
233
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
170
234
  continue;
171
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
172
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
173
- }
174
- let callbackLogLevel = "notice";
175
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
176
- callbackLogLevel = "info";
177
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
178
- callbackLogLevel = "debug";
235
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
236
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
237
+ }
238
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
239
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
240
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
241
+ callbackLogLevel = "info" /* LogLevel.INFO */;
242
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
243
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
179
244
  AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
180
245
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
181
246
  }
247
+ /** ***********************************************************************************************************************************/
248
+ /** loadInstance() and cleanup() methods */
249
+ /** ***********************************************************************************************************************************/
250
+ /**
251
+ * Loads an instance of the Matterbridge class.
252
+ * If an instance already exists, return that instance.
253
+ *
254
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
255
+ * @returns The loaded Matterbridge instance.
256
+ */
182
257
  static async loadInstance(initialize = false) {
183
258
  if (!Matterbridge.instance) {
259
+ // eslint-disable-next-line no-console
184
260
  if (hasParameter('debug'))
185
261
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
186
262
  Matterbridge.instance = new Matterbridge();
@@ -189,8 +265,14 @@ export class Matterbridge extends EventEmitter {
189
265
  }
190
266
  return Matterbridge.instance;
191
267
  }
268
+ /**
269
+ * Call cleanup().
270
+ * @deprecated This method is deprecated and is only used for jest tests.
271
+ *
272
+ */
192
273
  async destroyInstance() {
193
274
  this.log.info(`Destroy instance...`);
275
+ // Save server nodes to close
194
276
  const servers = [];
195
277
  if (this.bridgeMode === 'bridge') {
196
278
  if (this.serverNode)
@@ -202,72 +284,103 @@ export class Matterbridge extends EventEmitter {
202
284
  servers.push(plugin.serverNode);
203
285
  }
204
286
  }
287
+ // Cleanup
205
288
  await this.cleanup('destroying instance...', false);
289
+ // Close servers mdns service
206
290
  this.log.info(`Dispose ${servers.length} MdnsService...`);
207
291
  for (const server of servers) {
208
292
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
209
293
  this.log.info(`Closed ${server.id} MdnsService`);
210
294
  }
295
+ // Wait for the cleanup to finish
211
296
  await new Promise((resolve) => {
212
297
  setTimeout(resolve, 500);
213
298
  });
214
299
  }
300
+ /**
301
+ * Initializes the Matterbridge application.
302
+ *
303
+ * @remarks
304
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
305
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
306
+ * node version, registers signal handlers, initializes storage, and parses the command line.
307
+ *
308
+ * @returns A Promise that resolves when the initialization is complete.
309
+ */
215
310
  async initialize() {
311
+ // Emit the initialize_started event
216
312
  this.emit('initialize_started');
217
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
313
+ // Create the matterbridge logger
314
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
315
+ // Set the restart mode
218
316
  if (hasParameter('service'))
219
317
  this.restartMode = 'service';
220
318
  if (hasParameter('docker'))
221
319
  this.restartMode = 'docker';
320
+ // Set the matterbridge home directory
222
321
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
223
322
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
224
323
  await this.createDirectory(this.homeDirectory, 'Matterbridge Home Directory');
324
+ // Set the matterbridge directory
225
325
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
226
326
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
227
327
  await this.createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory');
228
328
  await this.createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory');
229
329
  await this.createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory');
330
+ // Set the matterbridge plugin directory
230
331
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
231
332
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
232
333
  await this.createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory');
334
+ // Set the matterbridge cert directory
233
335
  this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
234
336
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
235
337
  await this.createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory');
338
+ // Set the matterbridge root directory
236
339
  const { fileURLToPath } = await import('node:url');
237
340
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
238
341
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
239
342
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
343
+ // Setup the matter environment
240
344
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
241
345
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
242
346
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
243
347
  this.environment.vars.set('runtime.signals', false);
244
348
  this.environment.vars.set('runtime.exitcode', false);
349
+ // Register process handlers
245
350
  this.registerProcessHandlers();
351
+ // Initialize nodeStorage and nodeContext
246
352
  try {
247
353
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
248
354
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
249
355
  this.log.debug('Creating node storage context for matterbridge');
250
356
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
357
+ // TODO: Remove this code when node-persist-manager is updated
358
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
251
359
  const keys = (await this.nodeStorage?.storage.keys());
252
360
  for (const key of keys) {
253
361
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
362
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
363
  await this.nodeStorage?.storage.get(key);
255
364
  }
256
365
  const storages = await this.nodeStorage.getStorageNames();
257
366
  for (const storage of storages) {
258
367
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
259
368
  const nodeContext = await this.nodeStorage?.createStorage(storage);
369
+ // TODO: Remove this code when node-persist-manager is updated
370
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
260
371
  const keys = (await nodeContext?.storage.keys());
261
372
  keys.forEach(async (key) => {
262
373
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
263
374
  await nodeContext?.get(key);
264
375
  });
265
376
  }
377
+ // Creating a backup of the node storage since it is not corrupted
266
378
  this.log.debug('Creating node storage backup...');
267
379
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
268
380
  this.log.debug('Created node storage backup');
269
381
  }
270
382
  catch (error) {
383
+ // Restoring the backup of the node storage since it is corrupted
271
384
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
272
385
  if (hasParameter('norestore')) {
273
386
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -282,14 +395,19 @@ export class Matterbridge extends EventEmitter {
282
395
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
283
396
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
284
397
  }
398
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
285
399
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
400
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
286
401
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
402
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
287
403
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
404
+ // Certificate management
288
405
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
289
406
  try {
290
407
  await fs.access(pairingFilePath, fs.constants.R_OK);
291
408
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
292
409
  const pairingFileJson = JSON.parse(pairingFileContent);
410
+ // Set the vendorId, vendorName, productId and productName if they are present in the pairing file
293
411
  if (isValidNumber(pairingFileJson.vendorId))
294
412
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
295
413
  if (isValidString(pairingFileJson.vendorName, 3))
@@ -298,16 +416,20 @@ export class Matterbridge extends EventEmitter {
298
416
  this.aggregatorProductId = VendorId(pairingFileJson.productId);
299
417
  if (isValidString(pairingFileJson.productName, 3))
300
418
  this.aggregatorProductName = pairingFileJson.productName;
419
+ // Override the passcode and discriminator if they are present in the pairing file
301
420
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
302
421
  this.passcode = pairingFileJson.passcode;
303
422
  this.discriminator = pairingFileJson.discriminator;
304
423
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
305
424
  }
425
+ // Set the certification if it is present in the pairing file
306
426
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
307
427
  const hexStringToUint8Array = (hexString) => {
308
428
  const matches = hexString.match(/.{1,2}/g);
309
429
  return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
310
430
  };
431
+ // const hexString = Buffer.from('Test string', 'utf-8').toString('hex');
432
+ // console.log(hexString, Buffer.from(hexStringToUint8Array(hexString)).toString('utf-8'));
311
433
  this.certification = {
312
434
  privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
313
435
  certificate: hexStringToUint8Array(pairingFileJson.certificate),
@@ -320,41 +442,44 @@ export class Matterbridge extends EventEmitter {
320
442
  catch (error) {
321
443
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
322
444
  }
445
+ // Store the passcode, discriminator and port in the node context
323
446
  await this.nodeContext.set('matterport', this.port);
324
447
  await this.nodeContext.set('matterpasscode', this.passcode);
325
448
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
326
449
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
450
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
327
451
  if (hasParameter('logger')) {
328
452
  const level = getParameter('logger');
329
453
  if (level === 'debug') {
330
- this.log.logLevel = "debug";
454
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
331
455
  }
332
456
  else if (level === 'info') {
333
- this.log.logLevel = "info";
457
+ this.log.logLevel = "info" /* LogLevel.INFO */;
334
458
  }
335
459
  else if (level === 'notice') {
336
- this.log.logLevel = "notice";
460
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
337
461
  }
338
462
  else if (level === 'warn') {
339
- this.log.logLevel = "warn";
463
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
340
464
  }
341
465
  else if (level === 'error') {
342
- this.log.logLevel = "error";
466
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
343
467
  }
344
468
  else if (level === 'fatal') {
345
- this.log.logLevel = "fatal";
469
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
346
470
  }
347
471
  else {
348
472
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
349
- this.log.logLevel = "info";
473
+ this.log.logLevel = "info" /* LogLevel.INFO */;
350
474
  }
351
475
  }
352
476
  else {
353
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
477
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
354
478
  }
355
479
  this.frontend.logLevel = this.log.logLevel;
356
480
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
357
481
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
482
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
358
483
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
359
484
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
360
485
  this.matterbridgeInformation.fileLogger = true;
@@ -363,6 +488,7 @@ export class Matterbridge extends EventEmitter {
363
488
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
364
489
  if (this.profile !== undefined)
365
490
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
491
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
366
492
  if (hasParameter('matterlogger')) {
367
493
  const level = getParameter('matterlogger');
368
494
  if (level === 'debug') {
@@ -394,6 +520,7 @@ export class Matterbridge extends EventEmitter {
394
520
  Logger.format = MatterLogFormat.ANSI;
395
521
  Logger.setLogger('default', this.createMatterLogger());
396
522
  this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
523
+ // Create the file logger for matter.js (context: matterFileLog)
397
524
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
398
525
  this.matterbridgeInformation.matterFileLogger = true;
399
526
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -402,6 +529,7 @@ export class Matterbridge extends EventEmitter {
402
529
  });
403
530
  }
404
531
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
532
+ // Log network interfaces
405
533
  const networkInterfaces = os.networkInterfaces();
406
534
  const availableAddresses = Object.entries(networkInterfaces);
407
535
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -413,6 +541,7 @@ export class Matterbridge extends EventEmitter {
413
541
  });
414
542
  }
415
543
  }
544
+ // Set the interface to use for matter server node mdnsInterface
416
545
  if (hasParameter('mdnsinterface')) {
417
546
  this.mdnsInterface = getParameter('mdnsinterface');
418
547
  }
@@ -421,6 +550,7 @@ export class Matterbridge extends EventEmitter {
421
550
  if (this.mdnsInterface === '')
422
551
  this.mdnsInterface = undefined;
423
552
  }
553
+ // Validate mdnsInterface
424
554
  if (this.mdnsInterface) {
425
555
  if (!availableInterfaces.includes(this.mdnsInterface)) {
426
556
  this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -433,6 +563,7 @@ export class Matterbridge extends EventEmitter {
433
563
  }
434
564
  if (this.mdnsInterface)
435
565
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
566
+ // Set the listeningAddressIpv4 for the matter commissioning server
436
567
  if (hasParameter('ipv4address')) {
437
568
  this.ipv4address = getParameter('ipv4address');
438
569
  }
@@ -441,6 +572,7 @@ export class Matterbridge extends EventEmitter {
441
572
  if (this.ipv4address === '')
442
573
  this.ipv4address = undefined;
443
574
  }
575
+ // Validate ipv4address
444
576
  if (this.ipv4address) {
445
577
  let isValid = false;
446
578
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -456,6 +588,7 @@ export class Matterbridge extends EventEmitter {
456
588
  await this.nodeContext.remove('matteripv4address');
457
589
  }
458
590
  }
591
+ // Set the listeningAddressIpv6 for the matter commissioning server
459
592
  if (hasParameter('ipv6address')) {
460
593
  this.ipv6address = getParameter('ipv6address');
461
594
  }
@@ -464,6 +597,7 @@ export class Matterbridge extends EventEmitter {
464
597
  if (this.ipv6address === '')
465
598
  this.ipv6address = undefined;
466
599
  }
600
+ // Validate ipv6address
467
601
  if (this.ipv6address) {
468
602
  let isValid = false;
469
603
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -484,6 +618,7 @@ export class Matterbridge extends EventEmitter {
484
618
  await this.nodeContext.remove('matteripv6address');
485
619
  }
486
620
  }
621
+ // Initialize the virtual mode
487
622
  if (hasParameter('novirtual')) {
488
623
  this.matterbridgeInformation.virtualMode = 'disabled';
489
624
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -492,14 +627,19 @@ export class Matterbridge extends EventEmitter {
492
627
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
493
628
  }
494
629
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
630
+ // Initialize PluginManager
495
631
  this.plugins = new PluginManager(this);
496
632
  await this.plugins.loadFromStorage();
497
633
  this.plugins.logLevel = this.log.logLevel;
634
+ // Initialize DeviceManager
498
635
  this.devices = new DeviceManager(this, this.nodeContext);
499
636
  this.devices.logLevel = this.log.logLevel;
637
+ // Get the plugins from node storage and create the plugins node storage contexts
500
638
  for (const plugin of this.plugins) {
501
639
  const packageJson = await this.plugins.parse(plugin);
502
640
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
641
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
642
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
503
643
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
504
644
  try {
505
645
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -521,6 +661,7 @@ export class Matterbridge extends EventEmitter {
521
661
  await plugin.nodeContext.set('description', plugin.description);
522
662
  await plugin.nodeContext.set('author', plugin.author);
523
663
  }
664
+ // Log system info and create .matterbridge directory
524
665
  await this.logNodeAndSystemInfo();
525
666
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
526
667
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -528,6 +669,7 @@ export class Matterbridge extends EventEmitter {
528
669
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
529
670
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
530
671
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
672
+ // Check node version and throw error
531
673
  const minNodeVersion = 18;
532
674
  const nodeVersion = process.versions.node;
533
675
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -535,10 +677,17 @@ export class Matterbridge extends EventEmitter {
535
677
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
536
678
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
537
679
  }
680
+ // Parse command line
538
681
  await this.parseCommandLine();
682
+ // Emit the initialize_completed event
539
683
  this.emit('initialize_completed');
540
684
  this.initialized = true;
541
685
  }
686
+ /**
687
+ * Parses the command line arguments and performs the corresponding actions.
688
+ * @private
689
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
690
+ */
542
691
  async parseCommandLine() {
543
692
  if (hasParameter('help')) {
544
693
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -660,6 +809,7 @@ export class Matterbridge extends EventEmitter {
660
809
  this.shutdown = true;
661
810
  return;
662
811
  }
812
+ // Start the matter storage and create the matterbridge context
663
813
  try {
664
814
  await this.startMatterStorage();
665
815
  }
@@ -667,12 +817,14 @@ export class Matterbridge extends EventEmitter {
667
817
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
668
818
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
669
819
  }
820
+ // Clear the matterbridge context if the reset parameter is set
670
821
  if (hasParameter('reset') && getParameter('reset') === undefined) {
671
822
  this.initialized = true;
672
823
  await this.shutdownProcessAndReset();
673
824
  this.shutdown = true;
674
825
  return;
675
826
  }
827
+ // Clear matterbridge plugin context if the reset parameter is set
676
828
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
677
829
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
678
830
  const plugin = this.plugins.get(getParameter('reset'));
@@ -697,30 +849,37 @@ export class Matterbridge extends EventEmitter {
697
849
  this.shutdown = true;
698
850
  return;
699
851
  }
852
+ // Initialize frontend
700
853
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
701
854
  await this.frontend.start(getIntParameter('frontend'));
855
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
702
856
  this.checkUpdateTimeout = setTimeout(async () => {
703
857
  const { checkUpdates } = await import('./update.js');
704
858
  checkUpdates(this);
705
859
  }, 30 * 1000).unref();
860
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
706
861
  this.checkUpdateInterval = setInterval(async () => {
707
862
  const { checkUpdates } = await import('./update.js');
708
863
  checkUpdates(this);
709
864
  }, 12 * 60 * 60 * 1000).unref();
865
+ // Start the matterbridge in mode test
710
866
  if (hasParameter('test')) {
711
867
  this.bridgeMode = 'bridge';
712
868
  MatterbridgeEndpoint.bridgeMode = 'bridge';
713
869
  return;
714
870
  }
871
+ // Start the matterbridge in mode controller
715
872
  if (hasParameter('controller')) {
716
873
  this.bridgeMode = 'controller';
717
874
  await this.startController();
718
875
  return;
719
876
  }
877
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
720
878
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
721
879
  this.log.info('Setting default matterbridge start mode to bridge');
722
880
  await this.nodeContext?.set('bridgeMode', 'bridge');
723
881
  }
882
+ // Start matterbridge in bridge mode
724
883
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
725
884
  this.bridgeMode = 'bridge';
726
885
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -728,6 +887,7 @@ export class Matterbridge extends EventEmitter {
728
887
  await this.startBridge();
729
888
  return;
730
889
  }
890
+ // Start matterbridge in childbridge mode
731
891
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
732
892
  this.bridgeMode = 'childbridge';
733
893
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -736,10 +896,20 @@ export class Matterbridge extends EventEmitter {
736
896
  return;
737
897
  }
738
898
  }
899
+ /**
900
+ * Asynchronously loads and starts the registered plugins.
901
+ *
902
+ * This method is responsible for initializing and staarting all enabled plugins.
903
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
904
+ *
905
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
906
+ */
739
907
  async startPlugins() {
908
+ // Check, load and start the plugins
740
909
  for (const plugin of this.plugins) {
741
910
  plugin.configJson = await this.plugins.loadConfig(plugin);
742
911
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
912
+ // Check if the plugin is available
743
913
  if (!(await this.plugins.resolve(plugin.path))) {
744
914
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
745
915
  plugin.enabled = false;
@@ -759,10 +929,14 @@ export class Matterbridge extends EventEmitter {
759
929
  plugin.addedDevices = undefined;
760
930
  plugin.qrPairingCode = undefined;
761
931
  plugin.manualPairingCode = undefined;
762
- this.plugins.load(plugin, true, 'Matterbridge is starting');
932
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
763
933
  }
764
934
  this.frontend.wssSendRefreshRequired('plugins');
765
935
  }
936
+ /**
937
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
938
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
939
+ */
766
940
  registerProcessHandlers() {
767
941
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
768
942
  process.removeAllListeners('uncaughtException');
@@ -789,6 +963,9 @@ export class Matterbridge extends EventEmitter {
789
963
  };
790
964
  process.on('SIGTERM', this.sigtermHandler);
791
965
  }
966
+ /**
967
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
968
+ */
792
969
  deregisterProcessHandlers() {
793
970
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
794
971
  if (this.exceptionHandler)
@@ -805,12 +982,17 @@ export class Matterbridge extends EventEmitter {
805
982
  process.off('SIGTERM', this.sigtermHandler);
806
983
  this.sigtermHandler = undefined;
807
984
  }
985
+ /**
986
+ * Logs the node and system information.
987
+ */
808
988
  async logNodeAndSystemInfo() {
989
+ // IP address information
809
990
  const networkInterfaces = os.networkInterfaces();
810
991
  this.systemInformation.interfaceName = '';
811
992
  this.systemInformation.ipv4Address = '';
812
993
  this.systemInformation.ipv6Address = '';
813
994
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
995
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
814
996
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
815
997
  continue;
816
998
  if (!interfaceDetails) {
@@ -836,19 +1018,22 @@ export class Matterbridge extends EventEmitter {
836
1018
  break;
837
1019
  }
838
1020
  }
1021
+ // Node information
839
1022
  this.systemInformation.nodeVersion = process.versions.node;
840
1023
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
841
1024
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
842
1025
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1026
+ // Host system information
843
1027
  this.systemInformation.hostname = os.hostname();
844
1028
  this.systemInformation.user = os.userInfo().username;
845
- this.systemInformation.osType = os.type();
846
- this.systemInformation.osRelease = os.release();
847
- this.systemInformation.osPlatform = os.platform();
848
- this.systemInformation.osArch = os.arch();
849
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
850
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
851
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
1029
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1030
+ this.systemInformation.osRelease = os.release(); // Kernel version
1031
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1032
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
1033
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1034
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1035
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
1036
+ // Log the system information
852
1037
  this.log.debug('Host System Information:');
853
1038
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
854
1039
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -864,14 +1049,17 @@ export class Matterbridge extends EventEmitter {
864
1049
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
865
1050
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
866
1051
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1052
+ // Log directories
867
1053
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
868
1054
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
869
1055
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
870
1056
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
871
1057
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1058
+ // Global node_modules directory
872
1059
  if (this.nodeContext)
873
1060
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
874
1061
  if (this.globalModulesDirectory === '') {
1062
+ // First run of Matterbridge so the node storage is empty
875
1063
  try {
876
1064
  this.execRunningCount++;
877
1065
  this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory = await getGlobalNodeModules();
@@ -885,53 +1073,84 @@ export class Matterbridge extends EventEmitter {
885
1073
  }
886
1074
  else
887
1075
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1076
+ /* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
1077
+ else {
1078
+ this.getGlobalNodeModules()
1079
+ .then(async (globalModulesDirectory) => {
1080
+ this.globalModulesDirectory = globalModulesDirectory;
1081
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
1082
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1083
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
1084
+ })
1085
+ .catch((error) => {
1086
+ this.log.error(`Error getting global node_modules directory: ${error}`);
1087
+ });
1088
+ }*/
1089
+ // Matterbridge version
888
1090
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
889
1091
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
890
1092
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
891
1093
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1094
+ // Matterbridge latest version (will be set in the checkUpdate function)
892
1095
  if (this.nodeContext)
893
1096
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
894
1097
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1098
+ // Matterbridge dev version (will be set in the checkUpdate function)
895
1099
  if (this.nodeContext)
896
1100
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
897
1101
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1102
+ // Current working directory
898
1103
  const currentDir = process.cwd();
899
1104
  this.log.debug(`Current Working Directory: ${currentDir}`);
1105
+ // Command line arguments (excluding 'node' and the script name)
900
1106
  const cmdArgs = process.argv.slice(2).join(' ');
901
1107
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
902
1108
  }
1109
+ /**
1110
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1111
+ *
1112
+ * @returns {Function} The MatterLogger function.
1113
+ */
903
1114
  createMatterLogger() {
904
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1115
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
905
1116
  return (level, formattedLog) => {
906
1117
  const logger = formattedLog.slice(44, 44 + 20).trim();
907
1118
  const message = formattedLog.slice(65);
908
1119
  matterLogger.logName = logger;
909
1120
  switch (level) {
910
1121
  case MatterLogLevel.DEBUG:
911
- matterLogger.log("debug", message);
1122
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
912
1123
  break;
913
1124
  case MatterLogLevel.INFO:
914
- matterLogger.log("info", message);
1125
+ matterLogger.log("info" /* LogLevel.INFO */, message);
915
1126
  break;
916
1127
  case MatterLogLevel.NOTICE:
917
- matterLogger.log("notice", message);
1128
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
918
1129
  break;
919
1130
  case MatterLogLevel.WARN:
920
- matterLogger.log("warn", message);
1131
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
921
1132
  break;
922
1133
  case MatterLogLevel.ERROR:
923
- matterLogger.log("error", message);
1134
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
924
1135
  break;
925
1136
  case MatterLogLevel.FATAL:
926
- matterLogger.log("fatal", message);
1137
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
927
1138
  break;
928
1139
  default:
929
- matterLogger.log("debug", message);
1140
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
930
1141
  break;
931
1142
  }
932
1143
  };
933
1144
  }
1145
+ /**
1146
+ * Creates a Matter File Logger.
1147
+ *
1148
+ * @param {string} filePath - The path to the log file.
1149
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1150
+ * @returns {Function} - A function that logs formatted messages to the log file.
1151
+ */
934
1152
  async createMatterFileLogger(filePath, unlink = false) {
1153
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
935
1154
  let fileSize = 0;
936
1155
  if (unlink) {
937
1156
  try {
@@ -980,12 +1199,21 @@ export class Matterbridge extends EventEmitter {
980
1199
  }
981
1200
  };
982
1201
  }
1202
+ /**
1203
+ * Restarts the process by exiting the current instance and loading a new instance.
1204
+ */
983
1205
  async restartProcess() {
984
1206
  await this.cleanup('restarting...', true);
985
1207
  }
1208
+ /**
1209
+ * Shut down the process by exiting the current process.
1210
+ */
986
1211
  async shutdownProcess() {
987
1212
  await this.cleanup('shutting down...', false);
988
1213
  }
1214
+ /**
1215
+ * Update matterbridge and and shut down the process.
1216
+ */
989
1217
  async updateProcess() {
990
1218
  this.log.info('Updating matterbridge...');
991
1219
  try {
@@ -998,52 +1226,73 @@ export class Matterbridge extends EventEmitter {
998
1226
  this.frontend.wssSendRestartRequired();
999
1227
  await this.cleanup('updating...', false);
1000
1228
  }
1229
+ /**
1230
+ * Unregister all devices and shut down the process.
1231
+ */
1001
1232
  async unregisterAndShutdownProcess() {
1002
1233
  this.log.info('Unregistering all devices and shutting down...');
1003
1234
  for (const plugin of this.plugins) {
1004
1235
  await this.removeAllBridgedEndpoints(plugin.name, 250);
1005
1236
  }
1006
1237
  this.log.debug('Waiting for the MessageExchange to finish...');
1007
- await new Promise((resolve) => setTimeout(resolve, 1000));
1238
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
1008
1239
  this.log.debug('Cleaning up and shutting down...');
1009
1240
  await this.cleanup('unregistered all devices and shutting down...', false);
1010
1241
  }
1242
+ /**
1243
+ * Reset commissioning and shut down the process.
1244
+ */
1011
1245
  async shutdownProcessAndReset() {
1012
1246
  await this.cleanup('shutting down with reset...', false);
1013
1247
  }
1248
+ /**
1249
+ * Factory reset and shut down the process.
1250
+ */
1014
1251
  async shutdownProcessAndFactoryReset() {
1015
1252
  await this.cleanup('shutting down with factory reset...', false);
1016
1253
  }
1254
+ /**
1255
+ * Cleans up the Matterbridge instance.
1256
+ * @param {string} message - The cleanup message.
1257
+ * @param {boolean} [restart=false] - Indicates whether to restart the instance after cleanup. Default is `false`.
1258
+ * @returns A promise that resolves when the cleanup is completed.
1259
+ */
1017
1260
  async cleanup(message, restart = false) {
1018
1261
  if (this.initialized && !this.hasCleanupStarted) {
1019
1262
  this.emit('cleanup_started');
1020
1263
  this.hasCleanupStarted = true;
1021
1264
  this.log.info(message);
1265
+ // Clear the start matter interval
1022
1266
  if (this.startMatterInterval) {
1023
1267
  clearInterval(this.startMatterInterval);
1024
1268
  this.startMatterInterval = undefined;
1025
1269
  this.log.debug('Start matter interval cleared');
1026
1270
  }
1271
+ // Clear the check update timeout
1027
1272
  if (this.checkUpdateTimeout) {
1028
1273
  clearInterval(this.checkUpdateTimeout);
1029
1274
  this.checkUpdateTimeout = undefined;
1030
1275
  this.log.debug('Check update timeout cleared');
1031
1276
  }
1277
+ // Clear the check update interval
1032
1278
  if (this.checkUpdateInterval) {
1033
1279
  clearInterval(this.checkUpdateInterval);
1034
1280
  this.checkUpdateInterval = undefined;
1035
1281
  this.log.debug('Check update interval cleared');
1036
1282
  }
1283
+ // Clear the configure timeout
1037
1284
  if (this.configureTimeout) {
1038
1285
  clearTimeout(this.configureTimeout);
1039
1286
  this.configureTimeout = undefined;
1040
1287
  this.log.debug('Matterbridge configure timeout cleared');
1041
1288
  }
1289
+ // Clear the reachability timeout
1042
1290
  if (this.reachabilityTimeout) {
1043
1291
  clearTimeout(this.reachabilityTimeout);
1044
1292
  this.reachabilityTimeout = undefined;
1045
1293
  this.log.debug('Matterbridge reachability timeout cleared');
1046
1294
  }
1295
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
1047
1296
  for (const plugin of this.plugins) {
1048
1297
  if (!plugin.enabled || plugin.error)
1049
1298
  continue;
@@ -1054,9 +1303,10 @@ export class Matterbridge extends EventEmitter {
1054
1303
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1055
1304
  }
1056
1305
  }
1306
+ // Stop matter server nodes
1057
1307
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1058
1308
  this.log.debug('Waiting for the MessageExchange to finish...');
1059
- await new Promise((resolve) => setTimeout(resolve, 1000));
1309
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
1060
1310
  if (this.bridgeMode === 'bridge') {
1061
1311
  if (this.serverNode) {
1062
1312
  await this.stopServerNode(this.serverNode);
@@ -1072,6 +1322,7 @@ export class Matterbridge extends EventEmitter {
1072
1322
  }
1073
1323
  }
1074
1324
  this.log.notice('Stopped matter server nodes');
1325
+ // Matter commisioning reset
1075
1326
  if (message === 'shutting down with reset...') {
1076
1327
  this.log.info('Resetting Matterbridge commissioning information...');
1077
1328
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1081,18 +1332,36 @@ export class Matterbridge extends EventEmitter {
1081
1332
  await this.matterbridgeContext?.clearAll();
1082
1333
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1083
1334
  }
1335
+ // Stop matter storage
1084
1336
  await this.stopMatterStorage();
1337
+ // Stop the frontend
1085
1338
  await this.frontend.stop();
1339
+ // Remove the matterfilelogger
1086
1340
  try {
1087
1341
  Logger.removeLogger('matterfilelogger');
1088
1342
  }
1089
1343
  catch (error) {
1090
1344
  this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
1091
1345
  }
1346
+ // Close the matterbridge node storage and context
1092
1347
  if (this.nodeStorage && this.nodeContext) {
1348
+ /*
1349
+ TODO: Implement serialization of registered devices in edge mode
1350
+ this.log.info('Saving registered devices...');
1351
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1352
+ this.devices.forEach(async (device) => {
1353
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1354
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1355
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1356
+ });
1357
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1358
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1359
+ */
1360
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1093
1361
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1094
1362
  await this.nodeContext.close();
1095
1363
  this.nodeContext = undefined;
1364
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1096
1365
  for (const plugin of this.plugins) {
1097
1366
  if (plugin.nodeContext) {
1098
1367
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1109,8 +1378,10 @@ export class Matterbridge extends EventEmitter {
1109
1378
  }
1110
1379
  this.plugins.clear();
1111
1380
  this.devices.clear();
1381
+ // Factory reset
1112
1382
  if (message === 'shutting down with factory reset...') {
1113
1383
  try {
1384
+ // Delete matter storage directory with its subdirectories and backup
1114
1385
  const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
1115
1386
  this.log.info(`Removing matter storage directory: ${dir}`);
1116
1387
  await fs.rm(dir, { recursive: true });
@@ -1124,6 +1395,7 @@ export class Matterbridge extends EventEmitter {
1124
1395
  }
1125
1396
  }
1126
1397
  try {
1398
+ // Delete matterbridge storage directory with its subdirectories and backup
1127
1399
  const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
1128
1400
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1129
1401
  await fs.rm(dir, { recursive: true });
@@ -1138,12 +1410,13 @@ export class Matterbridge extends EventEmitter {
1138
1410
  }
1139
1411
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1140
1412
  }
1413
+ // Deregisters the process handlers
1141
1414
  this.deregisterProcessHandlers();
1142
1415
  if (restart) {
1143
1416
  if (message === 'updating...') {
1144
1417
  this.log.info('Cleanup completed. Updating...');
1145
1418
  Matterbridge.instance = undefined;
1146
- this.emit('update');
1419
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1147
1420
  }
1148
1421
  else if (message === 'restarting...') {
1149
1422
  this.log.info('Cleanup completed. Restarting...');
@@ -1164,6 +1437,14 @@ export class Matterbridge extends EventEmitter {
1164
1437
  this.log.debug('Cleanup already started...');
1165
1438
  }
1166
1439
  }
1440
+ /**
1441
+ * Creates and configures the server node for an accessory plugin for a given device.
1442
+ *
1443
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1444
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1445
+ * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1446
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1447
+ */
1167
1448
  async createAccessoryPlugin(plugin, device, start = false) {
1168
1449
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1169
1450
  plugin.locked = true;
@@ -1177,6 +1458,13 @@ export class Matterbridge extends EventEmitter {
1177
1458
  await this.startServerNode(plugin.serverNode);
1178
1459
  }
1179
1460
  }
1461
+ /**
1462
+ * Creates and configures the server node for a dynamic plugin.
1463
+ *
1464
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1465
+ * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1466
+ * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1467
+ */
1180
1468
  async createDynamicPlugin(plugin, start = false) {
1181
1469
  if (!plugin.locked) {
1182
1470
  plugin.locked = true;
@@ -1189,7 +1477,13 @@ export class Matterbridge extends EventEmitter {
1189
1477
  await this.startServerNode(plugin.serverNode);
1190
1478
  }
1191
1479
  }
1480
+ /**
1481
+ * Starts the Matterbridge in bridge mode.
1482
+ * @private
1483
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1484
+ */
1192
1485
  async startBridge() {
1486
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1193
1487
  if (!this.matterStorageManager)
1194
1488
  throw new Error('No storage manager initialized');
1195
1489
  if (!this.matterbridgeContext)
@@ -1228,7 +1522,9 @@ export class Matterbridge extends EventEmitter {
1228
1522
  clearInterval(this.startMatterInterval);
1229
1523
  this.startMatterInterval = undefined;
1230
1524
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1525
+ // Start the Matter server node
1231
1526
  this.startServerNode(this.serverNode);
1527
+ // Configure the plugins
1232
1528
  this.configureTimeout = setTimeout(async () => {
1233
1529
  for (const plugin of this.plugins) {
1234
1530
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1246,6 +1542,7 @@ export class Matterbridge extends EventEmitter {
1246
1542
  }
1247
1543
  this.frontend.wssSendRefreshRequired('plugins');
1248
1544
  }, 30 * 1000);
1545
+ // Setting reachability to true
1249
1546
  this.reachabilityTimeout = setTimeout(() => {
1250
1547
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1251
1548
  if (this.aggregatorNode)
@@ -1254,6 +1551,11 @@ export class Matterbridge extends EventEmitter {
1254
1551
  }, 60 * 1000);
1255
1552
  }, 1000);
1256
1553
  }
1554
+ /**
1555
+ * Starts the Matterbridge in childbridge mode.
1556
+ * @private
1557
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1558
+ */
1257
1559
  async startChildbridge() {
1258
1560
  if (!this.matterStorageManager)
1259
1561
  throw new Error('No storage manager initialized');
@@ -1291,6 +1593,7 @@ export class Matterbridge extends EventEmitter {
1291
1593
  clearInterval(this.startMatterInterval);
1292
1594
  this.startMatterInterval = undefined;
1293
1595
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1596
+ // Configure the plugins
1294
1597
  this.configureTimeout = setTimeout(async () => {
1295
1598
  for (const plugin of this.plugins) {
1296
1599
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1327,7 +1630,9 @@ export class Matterbridge extends EventEmitter {
1327
1630
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1328
1631
  continue;
1329
1632
  }
1633
+ // Start the Matter server node
1330
1634
  this.startServerNode(plugin.serverNode);
1635
+ // Setting reachability to true
1331
1636
  plugin.reachabilityTimeout = setTimeout(() => {
1332
1637
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggregator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
1333
1638
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1337,6 +1642,11 @@ export class Matterbridge extends EventEmitter {
1337
1642
  }
1338
1643
  }, 1000);
1339
1644
  }
1645
+ /**
1646
+ * Starts the Matterbridge controller.
1647
+ * @private
1648
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1649
+ */
1340
1650
  async startController() {
1341
1651
  if (!this.matterStorageManager) {
1342
1652
  this.log.error('No storage manager initialized');
@@ -1351,8 +1661,207 @@ export class Matterbridge extends EventEmitter {
1351
1661
  return;
1352
1662
  }
1353
1663
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1664
+ /*
1665
+ this.matterServer = await this.createMatterServer(this.storageManager);
1666
+ this.log.info('Creating matter commissioning controller');
1667
+ this.commissioningController = new CommissioningController({
1668
+ autoConnect: false,
1669
+ });
1670
+ this.log.info('Adding matter commissioning controller to matter server');
1671
+ await this.matterServer.addCommissioningController(this.commissioningController);
1672
+
1673
+ this.log.info('Starting matter server');
1674
+ await this.matterServer.start();
1675
+ this.log.info('Matter server started');
1676
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1677
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1678
+ regulatoryCountryCode: 'XX',
1679
+ };
1680
+ const commissioningController = new CommissioningController({
1681
+ environment: {
1682
+ environment,
1683
+ id: uniqueId,
1684
+ },
1685
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1686
+ adminFabricLabel,
1687
+ });
1688
+
1689
+ if (hasParameter('pairingcode')) {
1690
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1691
+ const pairingCode = getParameter('pairingcode');
1692
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1693
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1694
+
1695
+ let longDiscriminator, setupPin, shortDiscriminator;
1696
+ if (pairingCode !== undefined) {
1697
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1698
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1699
+ longDiscriminator = undefined;
1700
+ setupPin = pairingCodeCodec.passcode;
1701
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1702
+ } else {
1703
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1704
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1705
+ setupPin = this.controllerContext.get('pin', 20202021);
1706
+ }
1707
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1708
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1709
+ }
1710
+
1711
+ const options = {
1712
+ commissioning: commissioningOptions,
1713
+ discovery: {
1714
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1715
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1716
+ },
1717
+ passcode: setupPin,
1718
+ } as NodeCommissioningOptions;
1719
+ this.log.info('Commissioning with options:', options);
1720
+ const nodeId = await this.commissioningController.commissionNode(options);
1721
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1722
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1723
+ } // (hasParameter('pairingcode'))
1724
+
1725
+ if (hasParameter('unpairall')) {
1726
+ this.log.info('***Commissioning controller unpairing all nodes...');
1727
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1728
+ for (const nodeId of nodeIds) {
1729
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1730
+ await this.commissioningController.removeNode(nodeId);
1731
+ }
1732
+ return;
1733
+ }
1734
+
1735
+ if (hasParameter('discover')) {
1736
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1737
+ // console.log(discover);
1738
+ }
1739
+
1740
+ if (!this.commissioningController.isCommissioned()) {
1741
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1742
+ return;
1743
+ }
1744
+
1745
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1746
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1747
+ for (const nodeId of nodeIds) {
1748
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1749
+
1750
+ const node = await this.commissioningController.connectNode(nodeId, {
1751
+ autoSubscribe: false,
1752
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1753
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1754
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1755
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1756
+ stateInformationCallback: (peerNodeId, info) => {
1757
+ switch (info) {
1758
+ case NodeStateInformation.Connected:
1759
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1760
+ break;
1761
+ case NodeStateInformation.Disconnected:
1762
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1763
+ break;
1764
+ case NodeStateInformation.Reconnecting:
1765
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1766
+ break;
1767
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1768
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1769
+ break;
1770
+ case NodeStateInformation.StructureChanged:
1771
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1772
+ break;
1773
+ case NodeStateInformation.Decommissioned:
1774
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1775
+ break;
1776
+ default:
1777
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1778
+ break;
1779
+ }
1780
+ },
1781
+ });
1782
+
1783
+ node.logStructure();
1784
+
1785
+ // Get the interaction client
1786
+ this.log.info('Getting the interaction client');
1787
+ const interactionClient = await node.getInteractionClient();
1788
+ let cluster;
1789
+ let attributes;
1790
+
1791
+ // Log BasicInformationCluster
1792
+ cluster = BasicInformationCluster;
1793
+ attributes = await interactionClient.getMultipleAttributes({
1794
+ attributes: [{ clusterId: cluster.id }],
1795
+ });
1796
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1797
+ attributes.forEach((attribute) => {
1798
+ this.log.info(
1799
+ `- 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}`,
1800
+ );
1801
+ });
1802
+
1803
+ // Log PowerSourceCluster
1804
+ cluster = PowerSourceCluster;
1805
+ attributes = await interactionClient.getMultipleAttributes({
1806
+ attributes: [{ clusterId: cluster.id }],
1807
+ });
1808
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1809
+ attributes.forEach((attribute) => {
1810
+ this.log.info(
1811
+ `- 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}`,
1812
+ );
1813
+ });
1814
+
1815
+ // Log ThreadNetworkDiagnostics
1816
+ cluster = ThreadNetworkDiagnosticsCluster;
1817
+ attributes = await interactionClient.getMultipleAttributes({
1818
+ attributes: [{ clusterId: cluster.id }],
1819
+ });
1820
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1821
+ attributes.forEach((attribute) => {
1822
+ this.log.info(
1823
+ `- 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}`,
1824
+ );
1825
+ });
1826
+
1827
+ // Log SwitchCluster
1828
+ cluster = SwitchCluster;
1829
+ attributes = await interactionClient.getMultipleAttributes({
1830
+ attributes: [{ clusterId: cluster.id }],
1831
+ });
1832
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1833
+ attributes.forEach((attribute) => {
1834
+ this.log.info(
1835
+ `- 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}`,
1836
+ );
1837
+ });
1838
+
1839
+ this.log.info('Subscribing to all attributes and events');
1840
+ await node.subscribeAllAttributesAndEvents({
1841
+ ignoreInitialTriggers: false,
1842
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1843
+ this.log.info(
1844
+ `***${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}`,
1845
+ ),
1846
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1847
+ this.log.info(
1848
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1849
+ );
1850
+ },
1851
+ });
1852
+ this.log.info('Subscribed to all attributes and events');
1853
+ }
1854
+ */
1354
1855
  }
1856
+ /** ***********************************************************************************************************************************/
1857
+ /** Matter.js methods */
1858
+ /** ***********************************************************************************************************************************/
1859
+ /**
1860
+ * Starts the matter storage process with name Matterbridge.
1861
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1862
+ */
1355
1863
  async startMatterStorage() {
1864
+ // Setup Matter storage
1356
1865
  this.log.info(`Starting matter node storage...`);
1357
1866
  this.matterStorageService = this.environment.get(StorageService);
1358
1867
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1361,13 +1870,25 @@ export class Matterbridge extends EventEmitter {
1361
1870
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
1362
1871
  this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
1363
1872
  this.log.info('Matter node storage started');
1873
+ // Backup matter storage since it is created/opened correctly
1364
1874
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1365
1875
  }
1876
+ /**
1877
+ * Makes a backup copy of the specified matter storage directory.
1878
+ *
1879
+ * @param storageName - The name of the storage directory to be backed up.
1880
+ * @param backupName - The name of the backup directory to be created.
1881
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1882
+ */
1366
1883
  async backupMatterStorage(storageName, backupName) {
1367
1884
  this.log.info('Creating matter node storage backup...');
1368
1885
  await copyDirectory(storageName, backupName);
1369
1886
  this.log.info('Created matter node storage backup');
1370
1887
  }
1888
+ /**
1889
+ * Stops the matter storage.
1890
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1891
+ */
1371
1892
  async stopMatterStorage() {
1372
1893
  this.log.info('Closing matter node storage...');
1373
1894
  await this.matterStorageManager?.close();
@@ -1376,6 +1897,19 @@ export class Matterbridge extends EventEmitter {
1376
1897
  this.matterbridgeContext = undefined;
1377
1898
  this.log.info('Matter node storage closed');
1378
1899
  }
1900
+ /**
1901
+ * Creates a server node storage context.
1902
+ *
1903
+ * @param {string} pluginName - The name of the plugin.
1904
+ * @param {string} deviceName - The name of the device.
1905
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1906
+ * @param {number} vendorId - The vendor ID.
1907
+ * @param {string} vendorName - The vendor name.
1908
+ * @param {number} productId - The product ID.
1909
+ * @param {string} productName - The product name.
1910
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1911
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1912
+ */
1379
1913
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1380
1914
  const { randomBytes } = await import('node:crypto');
1381
1915
  if (!this.matterStorageService)
@@ -1409,6 +1943,15 @@ export class Matterbridge extends EventEmitter {
1409
1943
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1410
1944
  return storageContext;
1411
1945
  }
1946
+ /**
1947
+ * Creates a server node.
1948
+ *
1949
+ * @param {StorageContext} storageContext - The storage context for the server node.
1950
+ * @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
1951
+ * @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
1952
+ * @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
1953
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1954
+ */
1412
1955
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1413
1956
  const storeId = await storageContext.get('storeId');
1414
1957
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1418,24 +1961,37 @@ export class Matterbridge extends EventEmitter {
1418
1961
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1419
1962
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1420
1963
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1964
+ /**
1965
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1966
+ */
1421
1967
  const serverNode = await ServerNode.create({
1968
+ // Required: Give the Node a unique ID which is used to store the state of this node
1422
1969
  id: storeId,
1970
+ // Provide Network relevant configuration like the port
1971
+ // Optional when operating only one device on a host, Default port is 5540
1423
1972
  network: {
1424
1973
  listeningAddressIpv4: this.ipv4address,
1425
1974
  listeningAddressIpv6: this.ipv6address,
1426
1975
  port,
1427
1976
  },
1977
+ // Provide the certificate for the device
1428
1978
  operationalCredentials: {
1429
1979
  certification: this.certification,
1430
1980
  },
1981
+ // Provide Commissioning relevant settings
1982
+ // Optional for development/testing purposes
1431
1983
  commissioning: {
1432
1984
  passcode,
1433
1985
  discriminator,
1434
1986
  },
1987
+ // Provide Node announcement settings
1988
+ // Optional: If Ommitted some development defaults are used
1435
1989
  productDescription: {
1436
1990
  name: await storageContext.get('deviceName'),
1437
1991
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1438
1992
  },
1993
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1994
+ // Optional: If Omitted some development defaults are used
1439
1995
  basicInformation: {
1440
1996
  vendorId: VendorId(await storageContext.get('vendorId')),
1441
1997
  vendorName: await storageContext.get('vendorName'),
@@ -1453,12 +2009,13 @@ export class Matterbridge extends EventEmitter {
1453
2009
  },
1454
2010
  });
1455
2011
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
2012
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1456
2013
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1457
2014
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1458
2015
  if (this.bridgeMode === 'bridge') {
1459
2016
  this.matterbridgeFabricInformations = sanitizedFabrics;
1460
2017
  if (resetSessions)
1461
- this.matterbridgeSessionInformations = undefined;
2018
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1462
2019
  this.matterbridgePaired = true;
1463
2020
  }
1464
2021
  if (this.bridgeMode === 'childbridge') {
@@ -1466,13 +2023,19 @@ export class Matterbridge extends EventEmitter {
1466
2023
  if (plugin) {
1467
2024
  plugin.fabricInformations = sanitizedFabrics;
1468
2025
  if (resetSessions)
1469
- plugin.sessionInformations = undefined;
2026
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1470
2027
  plugin.paired = true;
1471
2028
  }
1472
2029
  }
1473
2030
  };
2031
+ /**
2032
+ * This event is triggered when the device is initially commissioned successfully.
2033
+ * This means: It is added to the first fabric.
2034
+ */
1474
2035
  serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
2036
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1475
2037
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
2038
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1476
2039
  serverNode.lifecycle.online.on(async () => {
1477
2040
  this.log.notice(`Server node for ${storeId} is online`);
1478
2041
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1541,6 +2104,7 @@ export class Matterbridge extends EventEmitter {
1541
2104
  this.frontend.wssSendRefreshRequired('settings');
1542
2105
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1543
2106
  });
2107
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1544
2108
  serverNode.lifecycle.offline.on(() => {
1545
2109
  this.log.notice(`Server node for ${storeId} is offline`);
1546
2110
  if (this.bridgeMode === 'bridge') {
@@ -1564,6 +2128,10 @@ export class Matterbridge extends EventEmitter {
1564
2128
  this.frontend.wssSendRefreshRequired('settings');
1565
2129
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1566
2130
  });
2131
+ /**
2132
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2133
+ * information is needed.
2134
+ */
1567
2135
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1568
2136
  let action = '';
1569
2137
  switch (fabricAction) {
@@ -1597,16 +2165,24 @@ export class Matterbridge extends EventEmitter {
1597
2165
  }
1598
2166
  }
1599
2167
  };
2168
+ /**
2169
+ * This event is triggered when an operative new session was opened by a Controller.
2170
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2171
+ */
1600
2172
  serverNode.events.sessions.opened.on((session) => {
1601
2173
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1602
2174
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1603
2175
  this.frontend.wssSendRefreshRequired('sessions');
1604
2176
  });
2177
+ /**
2178
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2179
+ */
1605
2180
  serverNode.events.sessions.closed.on((session) => {
1606
2181
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1607
2182
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1608
2183
  this.frontend.wssSendRefreshRequired('sessions');
1609
2184
  });
2185
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1610
2186
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1611
2187
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1612
2188
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1615,24 +2191,42 @@ export class Matterbridge extends EventEmitter {
1615
2191
  this.log.info(`Created server node for ${storeId}`);
1616
2192
  return serverNode;
1617
2193
  }
2194
+ /**
2195
+ * Starts the specified server node.
2196
+ *
2197
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2198
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2199
+ */
1618
2200
  async startServerNode(matterServerNode) {
1619
2201
  if (!matterServerNode)
1620
2202
  return;
1621
2203
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1622
2204
  await matterServerNode.start();
1623
2205
  }
2206
+ /**
2207
+ * Stops the specified server node.
2208
+ *
2209
+ * @param {ServerNode} matterServerNode - The server node to stop.
2210
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2211
+ */
1624
2212
  async stopServerNode(matterServerNode) {
1625
2213
  if (!matterServerNode)
1626
2214
  return;
1627
2215
  this.log.notice(`Closing ${matterServerNode.id} server node`);
1628
2216
  try {
1629
- await withTimeout(matterServerNode.close(), 30000);
2217
+ await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
1630
2218
  this.log.info(`Closed ${matterServerNode.id} server node`);
1631
2219
  }
1632
2220
  catch (error) {
1633
2221
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1634
2222
  }
1635
2223
  }
2224
+ /**
2225
+ * Advertises the specified server node.
2226
+ *
2227
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2228
+ * @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.
2229
+ */
1636
2230
  async advertiseServerNode(matterServerNode) {
1637
2231
  if (matterServerNode) {
1638
2232
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1641,25 +2235,46 @@ export class Matterbridge extends EventEmitter {
1641
2235
  return { qrPairingCode, manualPairingCode };
1642
2236
  }
1643
2237
  }
2238
+ /**
2239
+ * Stop advertise the specified server node.
2240
+ *
2241
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2242
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2243
+ */
1644
2244
  async stopAdvertiseServerNode(matterServerNode) {
1645
2245
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1646
2246
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1647
2247
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1648
2248
  }
1649
2249
  }
2250
+ /**
2251
+ * Creates an aggregator node with the specified storage context.
2252
+ *
2253
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2254
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2255
+ */
1650
2256
  async createAggregatorNode(storageContext) {
1651
2257
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1652
2258
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1653
2259
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1654
2260
  return aggregatorNode;
1655
2261
  }
2262
+ /**
2263
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2264
+ *
2265
+ * @param {string} pluginName - The name of the plugin.
2266
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2267
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2268
+ */
1656
2269
  async addBridgedEndpoint(pluginName, device) {
2270
+ // Check if the plugin is registered
1657
2271
  const plugin = this.plugins.get(pluginName);
1658
2272
  if (!plugin) {
1659
2273
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1660
2274
  return;
1661
2275
  }
1662
2276
  if (this.bridgeMode === 'bridge') {
2277
+ // Register and add the device to the matterbridge aggregator node
1663
2278
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1664
2279
  if (!this.aggregatorNode) {
1665
2280
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1676,6 +2291,7 @@ export class Matterbridge extends EventEmitter {
1676
2291
  }
1677
2292
  }
1678
2293
  else if (this.bridgeMode === 'childbridge') {
2294
+ // Register and add the device to the plugin server node
1679
2295
  if (plugin.type === 'AccessoryPlatform') {
1680
2296
  try {
1681
2297
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1692,10 +2308,12 @@ export class Matterbridge extends EventEmitter {
1692
2308
  return;
1693
2309
  }
1694
2310
  }
2311
+ // Register and add the device to the plugin aggregator node
1695
2312
  if (plugin.type === 'DynamicPlatform') {
1696
2313
  try {
1697
2314
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1698
2315
  await this.createDynamicPlugin(plugin);
2316
+ // Fast plugins can add another device before the server node is created
1699
2317
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1700
2318
  if (!plugin.aggregatorNode) {
1701
2319
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1715,17 +2333,28 @@ export class Matterbridge extends EventEmitter {
1715
2333
  plugin.registeredDevices++;
1716
2334
  if (plugin.addedDevices !== undefined)
1717
2335
  plugin.addedDevices++;
2336
+ // Add the device to the DeviceManager
1718
2337
  this.devices.set(device);
2338
+ // Subscribe to the reachable$Changed event
1719
2339
  await this.subscribeAttributeChanged(plugin, device);
1720
2340
  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}`);
1721
2341
  }
2342
+ /**
2343
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2344
+ *
2345
+ * @param {string} pluginName - The name of the plugin.
2346
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2347
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2348
+ */
1722
2349
  async removeBridgedEndpoint(pluginName, device) {
1723
2350
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2351
+ // Check if the plugin is registered
1724
2352
  const plugin = this.plugins.get(pluginName);
1725
2353
  if (!plugin) {
1726
2354
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1727
2355
  return;
1728
2356
  }
2357
+ // Register and add the device to the matterbridge aggregator node
1729
2358
  if (this.bridgeMode === 'bridge') {
1730
2359
  if (!this.aggregatorNode) {
1731
2360
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1740,6 +2369,7 @@ export class Matterbridge extends EventEmitter {
1740
2369
  }
1741
2370
  else if (this.bridgeMode === 'childbridge') {
1742
2371
  if (plugin.type === 'AccessoryPlatform') {
2372
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1743
2373
  }
1744
2374
  else if (plugin.type === 'DynamicPlatform') {
1745
2375
  if (!plugin.aggregatorNode) {
@@ -1754,8 +2384,21 @@ export class Matterbridge extends EventEmitter {
1754
2384
  if (plugin.addedDevices !== undefined)
1755
2385
  plugin.addedDevices--;
1756
2386
  }
2387
+ // Remove the device from the DeviceManager
1757
2388
  this.devices.remove(device);
1758
2389
  }
2390
+ /**
2391
+ * Removes all bridged endpoints from the specified plugin.
2392
+ *
2393
+ * @param {string} pluginName - The name of the plugin.
2394
+ * @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2395
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2396
+ *
2397
+ * @remarks
2398
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2399
+ * It also applies a delay between each removal if specified.
2400
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2401
+ */
1759
2402
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1760
2403
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1761
2404
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1766,6 +2409,15 @@ export class Matterbridge extends EventEmitter {
1766
2409
  if (delay > 0)
1767
2410
  await new Promise((resolve) => setTimeout(resolve, 2000));
1768
2411
  }
2412
+ /**
2413
+ * Subscribes to the attribute change event for the given device and plugin.
2414
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2415
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2416
+ *
2417
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2418
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2419
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2420
+ */
1769
2421
  async subscribeAttributeChanged(plugin, device) {
1770
2422
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
1771
2423
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
@@ -1781,6 +2433,12 @@ export class Matterbridge extends EventEmitter {
1781
2433
  });
1782
2434
  }
1783
2435
  }
2436
+ /**
2437
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2438
+ *
2439
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2440
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2441
+ */
1784
2442
  sanitizeFabricInformations(fabricInfo) {
1785
2443
  return fabricInfo.map((info) => {
1786
2444
  return {
@@ -1794,6 +2452,12 @@ export class Matterbridge extends EventEmitter {
1794
2452
  };
1795
2453
  });
1796
2454
  }
2455
+ /**
2456
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2457
+ *
2458
+ * @param {SessionInformation[]} sessionInfo - The array of session information objects.
2459
+ * @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
2460
+ */
1797
2461
  sanitizeSessionInformation(sessionInfo) {
1798
2462
  return sessionInfo
1799
2463
  .filter((session) => session.isPeerActive)
@@ -1821,7 +2485,20 @@ export class Matterbridge extends EventEmitter {
1821
2485
  };
1822
2486
  });
1823
2487
  }
2488
+ /**
2489
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2490
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2491
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2492
+ */
2493
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1824
2494
  async setAggregatorReachability(aggregatorNode, reachable) {
2495
+ /*
2496
+ for (const child of aggregatorNode.parts) {
2497
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2498
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2499
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2500
+ }
2501
+ */
1825
2502
  }
1826
2503
  getVendorIdName = (vendorId) => {
1827
2504
  if (!vendorId)
@@ -1864,14 +2541,29 @@ export class Matterbridge extends EventEmitter {
1864
2541
  }
1865
2542
  return vendorName;
1866
2543
  };
2544
+ /**
2545
+ * Spawns a child process with the given command and arguments.
2546
+ * @param {string} command - The command to execute.
2547
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2548
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2549
+ */
1867
2550
  async spawnCommand(command, args = []) {
1868
2551
  const { spawn } = await import('node:child_process');
2552
+ /*
2553
+ npm > npm.cmd on windows
2554
+ cmd.exe ['dir'] on windows
2555
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2556
+ */
1869
2557
  const cmdLine = command + ' ' + args.join(' ');
1870
2558
  if (process.platform === 'win32' && command === 'npm') {
2559
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
1871
2560
  const argstring = 'npm ' + args.join(' ');
1872
2561
  args.splice(0, args.length, '/c', argstring);
1873
2562
  command = 'cmd.exe';
1874
2563
  }
2564
+ // Decide when using sudo on linux
2565
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2566
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
1875
2567
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
1876
2568
  args.unshift(command);
1877
2569
  command = 'sudo';
@@ -1929,6 +2621,13 @@ export class Matterbridge extends EventEmitter {
1929
2621
  }
1930
2622
  });
1931
2623
  }
2624
+ /**
2625
+ * Creates a directory at the specified path if it doesn't already exist.
2626
+ *
2627
+ * @param {string} path - The path to the directory to create.
2628
+ * @param {string} name - The name of the directory.
2629
+ * @returns {Promise<void>} A promise that resolves when the directory has been created or already exists.
2630
+ */
1932
2631
  async createDirectory(path, name) {
1933
2632
  try {
1934
2633
  await fs.access(path);
@@ -1950,3 +2649,4 @@ export class Matterbridge extends EventEmitter {
1950
2649
  }
1951
2650
  }
1952
2651
  }
2652
+ //# sourceMappingURL=matterbridge.js.map