matterbridge 3.0.7-dev-20250618-fb768ee → 3.0.7

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 (188) hide show
  1. package/CHANGELOG.md +3 -2
  2. package/README-DEV.md +4 -4
  3. package/dist/cli.d.ts +29 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +62 -2
  6. package/dist/cli.js.map +1 -0
  7. package/dist/clusters/export.d.ts +2 -0
  8. package/dist/clusters/export.d.ts.map +1 -0
  9. package/dist/clusters/export.js +2 -0
  10. package/dist/clusters/export.js.map +1 -0
  11. package/dist/defaultConfigSchema.d.ts +27 -0
  12. package/dist/defaultConfigSchema.d.ts.map +1 -0
  13. package/dist/defaultConfigSchema.js +23 -0
  14. package/dist/defaultConfigSchema.js.map +1 -0
  15. package/dist/deviceManager.d.ts +114 -0
  16. package/dist/deviceManager.d.ts.map +1 -0
  17. package/dist/deviceManager.js +94 -1
  18. package/dist/deviceManager.js.map +1 -0
  19. package/dist/devices/export.d.ts +5 -0
  20. package/dist/devices/export.d.ts.map +1 -0
  21. package/dist/devices/export.js +2 -0
  22. package/dist/devices/export.js.map +1 -0
  23. package/dist/evse.d.ts +67 -0
  24. package/dist/evse.d.ts.map +1 -0
  25. package/dist/evse.js +65 -9
  26. package/dist/evse.js.map +1 -0
  27. package/dist/frontend.d.ts +256 -0
  28. package/dist/frontend.d.ts.map +1 -0
  29. package/dist/frontend.js +374 -16
  30. package/dist/frontend.js.map +1 -0
  31. package/dist/globalMatterbridge.d.ts +32 -0
  32. package/dist/globalMatterbridge.d.ts.map +1 -0
  33. package/dist/globalMatterbridge.js +20 -0
  34. package/dist/globalMatterbridge.js.map +1 -0
  35. package/dist/helpers.d.ts +47 -0
  36. package/dist/helpers.d.ts.map +1 -0
  37. package/dist/helpers.js +51 -0
  38. package/dist/helpers.js.map +1 -0
  39. package/dist/index.d.ts +37 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +28 -1
  42. package/dist/index.js.map +1 -0
  43. package/dist/laundryWasher.d.ts +243 -0
  44. package/dist/laundryWasher.d.ts.map +1 -0
  45. package/dist/laundryWasher.js +92 -7
  46. package/dist/laundryWasher.js.map +1 -0
  47. package/dist/logger/export.d.ts +2 -0
  48. package/dist/logger/export.d.ts.map +1 -0
  49. package/dist/logger/export.js +1 -0
  50. package/dist/logger/export.js.map +1 -0
  51. package/dist/matter/behaviors.d.ts +2 -0
  52. package/dist/matter/behaviors.d.ts.map +1 -0
  53. package/dist/matter/behaviors.js +2 -0
  54. package/dist/matter/behaviors.js.map +1 -0
  55. package/dist/matter/clusters.d.ts +2 -0
  56. package/dist/matter/clusters.d.ts.map +1 -0
  57. package/dist/matter/clusters.js +2 -0
  58. package/dist/matter/clusters.js.map +1 -0
  59. package/dist/matter/devices.d.ts +2 -0
  60. package/dist/matter/devices.d.ts.map +1 -0
  61. package/dist/matter/devices.js +2 -0
  62. package/dist/matter/devices.js.map +1 -0
  63. package/dist/matter/endpoints.d.ts +2 -0
  64. package/dist/matter/endpoints.d.ts.map +1 -0
  65. package/dist/matter/endpoints.js +2 -0
  66. package/dist/matter/endpoints.js.map +1 -0
  67. package/dist/matter/export.d.ts +5 -0
  68. package/dist/matter/export.d.ts.map +1 -0
  69. package/dist/matter/export.js +2 -0
  70. package/dist/matter/export.js.map +1 -0
  71. package/dist/matter/types.d.ts +3 -0
  72. package/dist/matter/types.d.ts.map +1 -0
  73. package/dist/matter/types.js +2 -0
  74. package/dist/matter/types.js.map +1 -0
  75. package/dist/matterbridge.d.ts +445 -0
  76. package/dist/matterbridge.d.ts.map +1 -0
  77. package/dist/matterbridge.js +748 -46
  78. package/dist/matterbridge.js.map +1 -0
  79. package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
  80. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  81. package/dist/matterbridgeAccessoryPlatform.js +34 -0
  82. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  83. package/dist/matterbridgeBehaviors.d.ts +1333 -0
  84. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  85. package/dist/matterbridgeBehaviors.js +54 -1
  86. package/dist/matterbridgeBehaviors.js.map +1 -0
  87. package/dist/matterbridgeDeviceTypes.d.ts +644 -0
  88. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  89. package/dist/matterbridgeDeviceTypes.js +578 -15
  90. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  91. package/dist/matterbridgeDynamicPlatform.d.ts +40 -0
  92. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  93. package/dist/matterbridgeDynamicPlatform.js +34 -0
  94. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  95. package/dist/matterbridgeEndpoint.d.ts +1145 -0
  96. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  97. package/dist/matterbridgeEndpoint.js +995 -40
  98. package/dist/matterbridgeEndpoint.js.map +1 -0
  99. package/dist/matterbridgeEndpointHelpers.d.ts +3083 -0
  100. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  101. package/dist/matterbridgeEndpointHelpers.js +204 -10
  102. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  103. package/dist/matterbridgePlatform.d.ts +290 -0
  104. package/dist/matterbridgePlatform.d.ts.map +1 -0
  105. package/dist/matterbridgePlatform.js +221 -6
  106. package/dist/matterbridgePlatform.js.map +1 -0
  107. package/dist/matterbridgeTypes.d.ts +196 -0
  108. package/dist/matterbridgeTypes.d.ts.map +1 -0
  109. package/dist/matterbridgeTypes.js +24 -0
  110. package/dist/matterbridgeTypes.js.map +1 -0
  111. package/dist/pluginManager.d.ts +273 -0
  112. package/dist/pluginManager.d.ts.map +1 -0
  113. package/dist/pluginManager.js +269 -3
  114. package/dist/pluginManager.js.map +1 -0
  115. package/dist/roboticVacuumCleaner.d.ts +102 -0
  116. package/dist/roboticVacuumCleaner.d.ts.map +1 -0
  117. package/dist/roboticVacuumCleaner.js +81 -6
  118. package/dist/roboticVacuumCleaner.js.map +1 -0
  119. package/dist/shelly.d.ts +161 -0
  120. package/dist/shelly.d.ts.map +1 -0
  121. package/dist/shelly.js +155 -7
  122. package/dist/shelly.js.map +1 -0
  123. package/dist/storage/export.d.ts +2 -0
  124. package/dist/storage/export.d.ts.map +1 -0
  125. package/dist/storage/export.js +1 -0
  126. package/dist/storage/export.js.map +1 -0
  127. package/dist/update.d.ts +58 -0
  128. package/dist/update.d.ts.map +1 -0
  129. package/dist/update.js +53 -0
  130. package/dist/update.js.map +1 -0
  131. package/dist/utils/colorUtils.d.ts +61 -0
  132. package/dist/utils/colorUtils.d.ts.map +1 -0
  133. package/dist/utils/colorUtils.js +205 -2
  134. package/dist/utils/colorUtils.js.map +1 -0
  135. package/dist/utils/commandLine.d.ts +58 -0
  136. package/dist/utils/commandLine.d.ts.map +1 -0
  137. package/dist/utils/commandLine.js +53 -0
  138. package/dist/utils/commandLine.js.map +1 -0
  139. package/dist/utils/copyDirectory.d.ts +32 -0
  140. package/dist/utils/copyDirectory.d.ts.map +1 -0
  141. package/dist/utils/copyDirectory.js +37 -1
  142. package/dist/utils/copyDirectory.js.map +1 -0
  143. package/dist/utils/createDirectory.d.ts +32 -0
  144. package/dist/utils/createDirectory.d.ts.map +1 -0
  145. package/dist/utils/createDirectory.js +31 -0
  146. package/dist/utils/createDirectory.js.map +1 -0
  147. package/dist/utils/createZip.d.ts +38 -0
  148. package/dist/utils/createZip.d.ts.map +1 -0
  149. package/dist/utils/createZip.js +42 -2
  150. package/dist/utils/createZip.js.map +1 -0
  151. package/dist/utils/deepCopy.d.ts +31 -0
  152. package/dist/utils/deepCopy.d.ts.map +1 -0
  153. package/dist/utils/deepCopy.js +38 -0
  154. package/dist/utils/deepCopy.js.map +1 -0
  155. package/dist/utils/deepEqual.d.ts +53 -0
  156. package/dist/utils/deepEqual.d.ts.map +1 -0
  157. package/dist/utils/deepEqual.js +71 -1
  158. package/dist/utils/deepEqual.js.map +1 -0
  159. package/dist/utils/export.d.ts +12 -0
  160. package/dist/utils/export.d.ts.map +1 -0
  161. package/dist/utils/export.js +1 -0
  162. package/dist/utils/export.js.map +1 -0
  163. package/dist/utils/hex.d.ts +48 -0
  164. package/dist/utils/hex.d.ts.map +1 -0
  165. package/dist/utils/hex.js +57 -0
  166. package/dist/utils/hex.js.map +1 -0
  167. package/dist/utils/isvalid.d.ts +102 -0
  168. package/dist/utils/isvalid.d.ts.map +1 -0
  169. package/dist/utils/isvalid.js +100 -0
  170. package/dist/utils/isvalid.js.map +1 -0
  171. package/dist/utils/network.d.ts +69 -0
  172. package/dist/utils/network.d.ts.map +1 -0
  173. package/dist/utils/network.js +76 -5
  174. package/dist/utils/network.js.map +1 -0
  175. package/dist/utils/spawn.d.ts +12 -0
  176. package/dist/utils/spawn.d.ts.map +1 -0
  177. package/dist/utils/spawn.js +16 -0
  178. package/dist/utils/spawn.js.map +1 -0
  179. package/dist/utils/wait.d.ts +52 -0
  180. package/dist/utils/wait.d.ts.map +1 -0
  181. package/dist/utils/wait.js +58 -9
  182. package/dist/utils/wait.js.map +1 -0
  183. package/dist/waterHeater.d.ts +90 -0
  184. package/dist/waterHeater.d.ts.map +1 -0
  185. package/dist/waterHeater.js +62 -2
  186. package/dist/waterHeater.js.map +1 -0
  187. package/npm-shrinkwrap.json +2 -2
  188. 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.6.0
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, createDirectory } from './utils/export.js';
9
35
  import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
10
36
  import { dev, plg, typ } from './matterbridgeTypes.js';
@@ -15,11 +41,15 @@ import { bridge } from './matterbridgeDeviceTypes.js';
15
41
  import { Frontend } from './frontend.js';
16
42
  import { addVirtualDevices } from './helpers.js';
17
43
  import spawn from './utils/spawn.js';
44
+ // @matter
18
45
  import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
19
46
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
20
47
  import { AggregatorEndpoint } from '@matter/main/endpoints';
21
48
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
22
49
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
50
+ /**
51
+ * Represents the Matterbridge application.
52
+ */
23
53
  export class Matterbridge extends EventEmitter {
24
54
  systemInformation = {
25
55
  interfaceName: '',
@@ -67,7 +97,7 @@ export class Matterbridge extends EventEmitter {
67
97
  shellySysUpdate: false,
68
98
  shellyMainUpdate: false,
69
99
  profile: getParameter('profile'),
70
- loggerLevel: "info",
100
+ loggerLevel: "info" /* LogLevel.INFO */,
71
101
  fileLogger: false,
72
102
  matterLoggerLevel: MatterLogLevel.INFO,
73
103
  matterFileLogger: false,
@@ -106,9 +136,11 @@ export class Matterbridge extends EventEmitter {
106
136
  plugins;
107
137
  devices;
108
138
  frontend = new Frontend(this);
139
+ // Matterbridge storage
109
140
  nodeStorage;
110
141
  nodeContext;
111
142
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
143
+ // Cleanup
112
144
  hasCleanupStarted = false;
113
145
  initialized = false;
114
146
  execRunningCount = 0;
@@ -122,19 +154,22 @@ export class Matterbridge extends EventEmitter {
122
154
  sigtermHandler;
123
155
  exceptionHandler;
124
156
  rejectionHandler;
157
+ // Matter environment
125
158
  environment = Environment.default;
159
+ // Matter storage
126
160
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
127
161
  matterStorageService;
128
162
  matterStorageManager;
129
163
  matterbridgeContext;
130
164
  controllerContext;
131
- mdnsInterface;
132
- ipv4address;
133
- ipv6address;
134
- port;
135
- passcode;
136
- discriminator;
137
- certification;
165
+ // Matter parameters
166
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
167
+ ipv4address; // matter server node listeningAddressIpv4
168
+ ipv6address; // matter server node listeningAddressIpv6
169
+ port; // first server node port
170
+ passcode; // first server node passcode
171
+ discriminator; // first server node discriminator
172
+ certification; // device certification
138
173
  serverNode;
139
174
  aggregatorNode;
140
175
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
@@ -142,21 +177,50 @@ export class Matterbridge extends EventEmitter {
142
177
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
143
178
  aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
144
179
  static instance;
180
+ // We load asyncronously so is private
145
181
  constructor() {
146
182
  super();
147
183
  }
184
+ /**
185
+ * Emits an event of the specified type with the provided arguments.
186
+ *
187
+ * @template K - The type of the event.
188
+ * @param {K} eventName - The name of the event to emit.
189
+ * @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
190
+ * @returns {boolean} - Returns true if the event had listeners, false otherwise.
191
+ */
148
192
  emit(eventName, ...args) {
149
193
  return super.emit(eventName, ...args);
150
194
  }
195
+ /**
196
+ * Registers an event listener for the specified event type.
197
+ *
198
+ * @template K - The type of the event.
199
+ * @param {K} eventName - The name of the event to listen for.
200
+ * @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
201
+ * @returns {this} - Returns the instance of the Matterbridge class.
202
+ */
151
203
  on(eventName, listener) {
152
204
  return super.on(eventName, listener);
153
205
  }
206
+ /**
207
+ * Retrieves the list of Matterbridge devices.
208
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
209
+ */
154
210
  getDevices() {
155
211
  return this.devices.array();
156
212
  }
213
+ /**
214
+ * Retrieves the list of registered plugins.
215
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
216
+ */
157
217
  getPlugins() {
158
218
  return this.plugins.array();
159
219
  }
220
+ /**
221
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
222
+ * @param {LogLevel} logLevel The logger logLevel to set.
223
+ */
160
224
  async setLogLevel(logLevel) {
161
225
  if (this.log)
162
226
  this.log.logLevel = logLevel;
@@ -170,19 +234,31 @@ export class Matterbridge extends EventEmitter {
170
234
  for (const plugin of this.plugins) {
171
235
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
172
236
  continue;
173
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
174
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
175
- }
176
- let callbackLogLevel = "notice";
177
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
178
- callbackLogLevel = "info";
179
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
180
- callbackLogLevel = "debug";
237
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
238
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
239
+ }
240
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
241
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
242
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
243
+ callbackLogLevel = "info" /* LogLevel.INFO */;
244
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
245
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
181
246
  AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
182
247
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
183
248
  }
249
+ /** ***********************************************************************************************************************************/
250
+ /** loadInstance() and cleanup() methods */
251
+ /** ***********************************************************************************************************************************/
252
+ /**
253
+ * Loads an instance of the Matterbridge class.
254
+ * If an instance already exists, return that instance.
255
+ *
256
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
257
+ * @returns The loaded Matterbridge instance.
258
+ */
184
259
  static async loadInstance(initialize = false) {
185
260
  if (!Matterbridge.instance) {
261
+ // eslint-disable-next-line no-console
186
262
  if (hasParameter('debug'))
187
263
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
188
264
  Matterbridge.instance = new Matterbridge();
@@ -191,8 +267,14 @@ export class Matterbridge extends EventEmitter {
191
267
  }
192
268
  return Matterbridge.instance;
193
269
  }
270
+ /**
271
+ * Call cleanup() and dispose MdnsService.
272
+ *
273
+ * @deprecated This method is deprecated and is ONLY used for jest tests.
274
+ */
194
275
  async destroyInstance() {
195
276
  this.log.info(`Destroy instance...`);
277
+ // Save server nodes to close
196
278
  const servers = [];
197
279
  if (this.bridgeMode === 'bridge') {
198
280
  if (this.serverNode)
@@ -204,74 +286,107 @@ export class Matterbridge extends EventEmitter {
204
286
  servers.push(plugin.serverNode);
205
287
  }
206
288
  }
289
+ // Let any already‐queued microtasks run first
207
290
  await Promise.resolve();
291
+ // Cleanup
208
292
  await this.cleanup('destroying instance...', false);
293
+ // Close servers mdns service
209
294
  this.log.info(`Dispose ${servers.length} MdnsService...`);
210
295
  for (const server of servers) {
211
296
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
212
297
  this.log.info(`Closed ${server.id} MdnsService`);
213
298
  }
299
+ // Let any already‐queued microtasks run first
214
300
  await Promise.resolve();
301
+ // Wait for the cleanup to finish
215
302
  await new Promise((resolve) => {
216
303
  setTimeout(resolve, 500);
217
304
  });
218
305
  }
306
+ /**
307
+ * Initializes the Matterbridge application.
308
+ *
309
+ * @remarks
310
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
311
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
312
+ * node version, registers signal handlers, initializes storage, and parses the command line.
313
+ *
314
+ * @returns A Promise that resolves when the initialization is complete.
315
+ */
219
316
  async initialize() {
317
+ // Emit the initialize_started event
220
318
  this.emit('initialize_started');
221
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
319
+ // Create the matterbridge logger
320
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
321
+ // Set the restart mode
222
322
  if (hasParameter('service'))
223
323
  this.restartMode = 'service';
224
324
  if (hasParameter('docker'))
225
325
  this.restartMode = 'docker';
326
+ // Set the matterbridge home directory
226
327
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
227
328
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
228
329
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
330
+ // Set the matterbridge directory
229
331
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
230
332
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
231
333
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
232
334
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
233
335
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
336
+ // Set the matterbridge plugin directory
234
337
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
235
338
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
236
339
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
340
+ // Set the matterbridge cert directory
237
341
  this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
238
342
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
239
343
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
344
+ // Set the matterbridge root directory
240
345
  const { fileURLToPath } = await import('node:url');
241
346
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
242
347
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
243
348
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
349
+ // Setup the matter environment
244
350
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
245
351
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
246
352
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
247
353
  this.environment.vars.set('runtime.signals', false);
248
354
  this.environment.vars.set('runtime.exitcode', false);
355
+ // Register process handlers
249
356
  this.registerProcessHandlers();
357
+ // Initialize nodeStorage and nodeContext
250
358
  try {
251
359
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
252
360
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
253
361
  this.log.debug('Creating node storage context for matterbridge');
254
362
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
363
+ // TODO: Remove this code when node-persist-manager is updated
364
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
255
365
  const keys = (await this.nodeStorage?.storage.keys());
256
366
  for (const key of keys) {
257
367
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
368
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
258
369
  await this.nodeStorage?.storage.get(key);
259
370
  }
260
371
  const storages = await this.nodeStorage.getStorageNames();
261
372
  for (const storage of storages) {
262
373
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
263
374
  const nodeContext = await this.nodeStorage?.createStorage(storage);
375
+ // TODO: Remove this code when node-persist-manager is updated
376
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
264
377
  const keys = (await nodeContext?.storage.keys());
265
378
  keys.forEach(async (key) => {
266
379
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
267
380
  await nodeContext?.get(key);
268
381
  });
269
382
  }
383
+ // Creating a backup of the node storage since it is not corrupted
270
384
  this.log.debug('Creating node storage backup...');
271
385
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
272
386
  this.log.debug('Created node storage backup');
273
387
  }
274
388
  catch (error) {
389
+ // Restoring the backup of the node storage since it is corrupted
275
390
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
276
391
  if (hasParameter('norestore')) {
277
392
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -286,14 +401,19 @@ export class Matterbridge extends EventEmitter {
286
401
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
287
402
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
288
403
  }
404
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
289
405
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
406
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
290
407
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
408
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
291
409
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
410
+ // Certificate management
292
411
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
293
412
  try {
294
413
  await fs.access(pairingFilePath, fs.constants.R_OK);
295
414
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
296
415
  const pairingFileJson = JSON.parse(pairingFileContent);
416
+ // Set the vendorId, vendorName, productId and productName if they are present in the pairing file
297
417
  if (isValidNumber(pairingFileJson.vendorId))
298
418
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
299
419
  if (isValidString(pairingFileJson.vendorName, 3))
@@ -302,16 +422,20 @@ export class Matterbridge extends EventEmitter {
302
422
  this.aggregatorProductId = VendorId(pairingFileJson.productId);
303
423
  if (isValidString(pairingFileJson.productName, 3))
304
424
  this.aggregatorProductName = pairingFileJson.productName;
425
+ // Override the passcode and discriminator if they are present in the pairing file
305
426
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
306
427
  this.passcode = pairingFileJson.passcode;
307
428
  this.discriminator = pairingFileJson.discriminator;
308
429
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
309
430
  }
431
+ // Set the certification if it is present in the pairing file
310
432
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
311
433
  const hexStringToUint8Array = (hexString) => {
312
434
  const matches = hexString.match(/.{1,2}/g);
313
435
  return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
314
436
  };
437
+ // const hexString = Buffer.from('Test string', 'utf-8').toString('hex');
438
+ // console.log(hexString, Buffer.from(hexStringToUint8Array(hexString)).toString('utf-8'));
315
439
  this.certification = {
316
440
  privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
317
441
  certificate: hexStringToUint8Array(pairingFileJson.certificate),
@@ -324,41 +448,44 @@ export class Matterbridge extends EventEmitter {
324
448
  catch (error) {
325
449
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
326
450
  }
451
+ // Store the passcode, discriminator and port in the node context
327
452
  await this.nodeContext.set('matterport', this.port);
328
453
  await this.nodeContext.set('matterpasscode', this.passcode);
329
454
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
330
455
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
456
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
331
457
  if (hasParameter('logger')) {
332
458
  const level = getParameter('logger');
333
459
  if (level === 'debug') {
334
- this.log.logLevel = "debug";
460
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
335
461
  }
336
462
  else if (level === 'info') {
337
- this.log.logLevel = "info";
463
+ this.log.logLevel = "info" /* LogLevel.INFO */;
338
464
  }
339
465
  else if (level === 'notice') {
340
- this.log.logLevel = "notice";
466
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
341
467
  }
342
468
  else if (level === 'warn') {
343
- this.log.logLevel = "warn";
469
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
344
470
  }
345
471
  else if (level === 'error') {
346
- this.log.logLevel = "error";
472
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
347
473
  }
348
474
  else if (level === 'fatal') {
349
- this.log.logLevel = "fatal";
475
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
350
476
  }
351
477
  else {
352
478
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
353
- this.log.logLevel = "info";
479
+ this.log.logLevel = "info" /* LogLevel.INFO */;
354
480
  }
355
481
  }
356
482
  else {
357
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
483
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
358
484
  }
359
485
  this.frontend.logLevel = this.log.logLevel;
360
486
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
361
487
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
488
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
362
489
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
363
490
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
364
491
  this.matterbridgeInformation.fileLogger = true;
@@ -367,6 +494,7 @@ export class Matterbridge extends EventEmitter {
367
494
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
368
495
  if (this.profile !== undefined)
369
496
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
497
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
370
498
  if (hasParameter('matterlogger')) {
371
499
  const level = getParameter('matterlogger');
372
500
  if (level === 'debug') {
@@ -398,6 +526,7 @@ export class Matterbridge extends EventEmitter {
398
526
  Logger.format = MatterLogFormat.ANSI;
399
527
  Logger.setLogger('default', this.createMatterLogger());
400
528
  this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
529
+ // Create the file logger for matter.js (context: matterFileLog)
401
530
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
402
531
  this.matterbridgeInformation.matterFileLogger = true;
403
532
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -406,6 +535,7 @@ export class Matterbridge extends EventEmitter {
406
535
  });
407
536
  }
408
537
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
538
+ // Log network interfaces
409
539
  const networkInterfaces = os.networkInterfaces();
410
540
  const availableAddresses = Object.entries(networkInterfaces);
411
541
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -417,6 +547,7 @@ export class Matterbridge extends EventEmitter {
417
547
  });
418
548
  }
419
549
  }
550
+ // Set the interface to use for matter server node mdnsInterface
420
551
  if (hasParameter('mdnsinterface')) {
421
552
  this.mdnsInterface = getParameter('mdnsinterface');
422
553
  }
@@ -425,6 +556,7 @@ export class Matterbridge extends EventEmitter {
425
556
  if (this.mdnsInterface === '')
426
557
  this.mdnsInterface = undefined;
427
558
  }
559
+ // Validate mdnsInterface
428
560
  if (this.mdnsInterface) {
429
561
  if (!availableInterfaces.includes(this.mdnsInterface)) {
430
562
  this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -437,6 +569,7 @@ export class Matterbridge extends EventEmitter {
437
569
  }
438
570
  if (this.mdnsInterface)
439
571
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
572
+ // Set the listeningAddressIpv4 for the matter commissioning server
440
573
  if (hasParameter('ipv4address')) {
441
574
  this.ipv4address = getParameter('ipv4address');
442
575
  }
@@ -445,6 +578,7 @@ export class Matterbridge extends EventEmitter {
445
578
  if (this.ipv4address === '')
446
579
  this.ipv4address = undefined;
447
580
  }
581
+ // Validate ipv4address
448
582
  if (this.ipv4address) {
449
583
  let isValid = false;
450
584
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -460,6 +594,7 @@ export class Matterbridge extends EventEmitter {
460
594
  await this.nodeContext.remove('matteripv4address');
461
595
  }
462
596
  }
597
+ // Set the listeningAddressIpv6 for the matter commissioning server
463
598
  if (hasParameter('ipv6address')) {
464
599
  this.ipv6address = getParameter('ipv6address');
465
600
  }
@@ -468,6 +603,7 @@ export class Matterbridge extends EventEmitter {
468
603
  if (this.ipv6address === '')
469
604
  this.ipv6address = undefined;
470
605
  }
606
+ // Validate ipv6address
471
607
  if (this.ipv6address) {
472
608
  let isValid = false;
473
609
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -488,6 +624,7 @@ export class Matterbridge extends EventEmitter {
488
624
  await this.nodeContext.remove('matteripv6address');
489
625
  }
490
626
  }
627
+ // Initialize the virtual mode
491
628
  if (hasParameter('novirtual')) {
492
629
  this.matterbridgeInformation.virtualMode = 'disabled';
493
630
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -496,14 +633,19 @@ export class Matterbridge extends EventEmitter {
496
633
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
497
634
  }
498
635
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
636
+ // Initialize PluginManager
499
637
  this.plugins = new PluginManager(this);
500
638
  await this.plugins.loadFromStorage();
501
639
  this.plugins.logLevel = this.log.logLevel;
640
+ // Initialize DeviceManager
502
641
  this.devices = new DeviceManager(this, this.nodeContext);
503
642
  this.devices.logLevel = this.log.logLevel;
643
+ // Get the plugins from node storage and create the plugins node storage contexts
504
644
  for (const plugin of this.plugins) {
505
645
  const packageJson = await this.plugins.parse(plugin);
506
646
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
647
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
648
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
507
649
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
508
650
  try {
509
651
  await spawn.spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -525,6 +667,7 @@ export class Matterbridge extends EventEmitter {
525
667
  await plugin.nodeContext.set('description', plugin.description);
526
668
  await plugin.nodeContext.set('author', plugin.author);
527
669
  }
670
+ // Log system info and create .matterbridge directory
528
671
  await this.logNodeAndSystemInfo();
529
672
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
530
673
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -532,6 +675,7 @@ export class Matterbridge extends EventEmitter {
532
675
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
533
676
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
534
677
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
678
+ // Check node version and throw error
535
679
  const minNodeVersion = 18;
536
680
  const nodeVersion = process.versions.node;
537
681
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -539,10 +683,17 @@ export class Matterbridge extends EventEmitter {
539
683
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
540
684
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
541
685
  }
686
+ // Parse command line
542
687
  await this.parseCommandLine();
688
+ // Emit the initialize_completed event
543
689
  this.emit('initialize_completed');
544
690
  this.initialized = true;
545
691
  }
692
+ /**
693
+ * Parses the command line arguments and performs the corresponding actions.
694
+ * @private
695
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
696
+ */
546
697
  async parseCommandLine() {
547
698
  if (hasParameter('help')) {
548
699
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -664,6 +815,7 @@ export class Matterbridge extends EventEmitter {
664
815
  this.shutdown = true;
665
816
  return;
666
817
  }
818
+ // Start the matter storage and create the matterbridge context
667
819
  try {
668
820
  await this.startMatterStorage();
669
821
  }
@@ -671,12 +823,14 @@ export class Matterbridge extends EventEmitter {
671
823
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
672
824
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
673
825
  }
826
+ // Clear the matterbridge context if the reset parameter is set
674
827
  if (hasParameter('reset') && getParameter('reset') === undefined) {
675
828
  this.initialized = true;
676
829
  await this.shutdownProcessAndReset();
677
830
  this.shutdown = true;
678
831
  return;
679
832
  }
833
+ // Clear matterbridge plugin context if the reset parameter is set
680
834
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
681
835
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
682
836
  const plugin = this.plugins.get(getParameter('reset'));
@@ -701,30 +855,37 @@ export class Matterbridge extends EventEmitter {
701
855
  this.shutdown = true;
702
856
  return;
703
857
  }
858
+ // Initialize frontend
704
859
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
705
860
  await this.frontend.start(getIntParameter('frontend'));
861
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
706
862
  this.checkUpdateTimeout = setTimeout(async () => {
707
863
  const { checkUpdates } = await import('./update.js');
708
864
  checkUpdates(this);
709
865
  }, 30 * 1000).unref();
866
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
710
867
  this.checkUpdateInterval = setInterval(async () => {
711
868
  const { checkUpdates } = await import('./update.js');
712
869
  checkUpdates(this);
713
870
  }, 12 * 60 * 60 * 1000).unref();
871
+ // Start the matterbridge in mode test
714
872
  if (hasParameter('test')) {
715
873
  this.bridgeMode = 'bridge';
716
874
  MatterbridgeEndpoint.bridgeMode = 'bridge';
717
875
  return;
718
876
  }
877
+ // Start the matterbridge in mode controller
719
878
  if (hasParameter('controller')) {
720
879
  this.bridgeMode = 'controller';
721
880
  await this.startController();
722
881
  return;
723
882
  }
883
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
724
884
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
725
885
  this.log.info('Setting default matterbridge start mode to bridge');
726
886
  await this.nodeContext?.set('bridgeMode', 'bridge');
727
887
  }
888
+ // Start matterbridge in bridge mode
728
889
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
729
890
  this.bridgeMode = 'bridge';
730
891
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -732,6 +893,7 @@ export class Matterbridge extends EventEmitter {
732
893
  await this.startBridge();
733
894
  return;
734
895
  }
896
+ // Start matterbridge in childbridge mode
735
897
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
736
898
  this.bridgeMode = 'childbridge';
737
899
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -740,10 +902,20 @@ export class Matterbridge extends EventEmitter {
740
902
  return;
741
903
  }
742
904
  }
905
+ /**
906
+ * Asynchronously loads and starts the registered plugins.
907
+ *
908
+ * This method is responsible for initializing and starting all enabled plugins.
909
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
910
+ *
911
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
912
+ */
743
913
  async startPlugins() {
914
+ // Check, load and start the plugins
744
915
  for (const plugin of this.plugins) {
745
916
  plugin.configJson = await this.plugins.loadConfig(plugin);
746
917
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
918
+ // Check if the plugin is available
747
919
  if (!(await this.plugins.resolve(plugin.path))) {
748
920
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
749
921
  plugin.enabled = false;
@@ -763,10 +935,14 @@ export class Matterbridge extends EventEmitter {
763
935
  plugin.addedDevices = undefined;
764
936
  plugin.qrPairingCode = undefined;
765
937
  plugin.manualPairingCode = undefined;
766
- this.plugins.load(plugin, true, 'Matterbridge is starting');
938
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
767
939
  }
768
940
  this.frontend.wssSendRefreshRequired('plugins');
769
941
  }
942
+ /**
943
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
944
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
945
+ */
770
946
  registerProcessHandlers() {
771
947
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
772
948
  process.removeAllListeners('uncaughtException');
@@ -793,6 +969,9 @@ export class Matterbridge extends EventEmitter {
793
969
  };
794
970
  process.on('SIGTERM', this.sigtermHandler);
795
971
  }
972
+ /**
973
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
974
+ */
796
975
  deregisterProcessHandlers() {
797
976
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
798
977
  if (this.exceptionHandler)
@@ -809,12 +988,17 @@ export class Matterbridge extends EventEmitter {
809
988
  process.off('SIGTERM', this.sigtermHandler);
810
989
  this.sigtermHandler = undefined;
811
990
  }
991
+ /**
992
+ * Logs the node and system information.
993
+ */
812
994
  async logNodeAndSystemInfo() {
995
+ // IP address information
813
996
  const networkInterfaces = os.networkInterfaces();
814
997
  this.systemInformation.interfaceName = '';
815
998
  this.systemInformation.ipv4Address = '';
816
999
  this.systemInformation.ipv6Address = '';
817
1000
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
1001
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
818
1002
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
819
1003
  continue;
820
1004
  if (!interfaceDetails) {
@@ -840,19 +1024,22 @@ export class Matterbridge extends EventEmitter {
840
1024
  break;
841
1025
  }
842
1026
  }
1027
+ // Node information
843
1028
  this.systemInformation.nodeVersion = process.versions.node;
844
1029
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
845
1030
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
846
1031
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1032
+ // Host system information
847
1033
  this.systemInformation.hostname = os.hostname();
848
1034
  this.systemInformation.user = os.userInfo().username;
849
- this.systemInformation.osType = os.type();
850
- this.systemInformation.osRelease = os.release();
851
- this.systemInformation.osPlatform = os.platform();
852
- this.systemInformation.osArch = os.arch();
853
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
854
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
855
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
1035
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1036
+ this.systemInformation.osRelease = os.release(); // Kernel version
1037
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1038
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
1039
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1040
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1041
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
1042
+ // Log the system information
856
1043
  this.log.debug('Host System Information:');
857
1044
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
858
1045
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -868,14 +1055,17 @@ export class Matterbridge extends EventEmitter {
868
1055
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
869
1056
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
870
1057
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1058
+ // Log directories
871
1059
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
872
1060
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
873
1061
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
874
1062
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
875
1063
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1064
+ // Global node_modules directory
876
1065
  if (this.nodeContext)
877
1066
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
878
1067
  if (this.globalModulesDirectory === '') {
1068
+ // First run of Matterbridge so the node storage is empty
879
1069
  try {
880
1070
  this.execRunningCount++;
881
1071
  this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory = await getGlobalNodeModules();
@@ -889,53 +1079,84 @@ export class Matterbridge extends EventEmitter {
889
1079
  }
890
1080
  else
891
1081
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1082
+ /* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
1083
+ else {
1084
+ this.getGlobalNodeModules()
1085
+ .then(async (globalModulesDirectory) => {
1086
+ this.globalModulesDirectory = globalModulesDirectory;
1087
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
1088
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1089
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
1090
+ })
1091
+ .catch((error) => {
1092
+ this.log.error(`Error getting global node_modules directory: ${error}`);
1093
+ });
1094
+ }*/
1095
+ // Matterbridge version
892
1096
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
893
1097
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
894
1098
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
895
1099
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1100
+ // Matterbridge latest version (will be set in the checkUpdate function)
896
1101
  if (this.nodeContext)
897
1102
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
898
1103
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1104
+ // Matterbridge dev version (will be set in the checkUpdate function)
899
1105
  if (this.nodeContext)
900
1106
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
901
1107
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1108
+ // Current working directory
902
1109
  const currentDir = process.cwd();
903
1110
  this.log.debug(`Current Working Directory: ${currentDir}`);
1111
+ // Command line arguments (excluding 'node' and the script name)
904
1112
  const cmdArgs = process.argv.slice(2).join(' ');
905
1113
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
906
1114
  }
1115
+ /**
1116
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1117
+ *
1118
+ * @returns {Function} The MatterLogger function.
1119
+ */
907
1120
  createMatterLogger() {
908
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1121
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
909
1122
  return (level, formattedLog) => {
910
1123
  const logger = formattedLog.slice(44, 44 + 20).trim();
911
1124
  const message = formattedLog.slice(65);
912
1125
  matterLogger.logName = logger;
913
1126
  switch (level) {
914
1127
  case MatterLogLevel.DEBUG:
915
- matterLogger.log("debug", message);
1128
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
916
1129
  break;
917
1130
  case MatterLogLevel.INFO:
918
- matterLogger.log("info", message);
1131
+ matterLogger.log("info" /* LogLevel.INFO */, message);
919
1132
  break;
920
1133
  case MatterLogLevel.NOTICE:
921
- matterLogger.log("notice", message);
1134
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
922
1135
  break;
923
1136
  case MatterLogLevel.WARN:
924
- matterLogger.log("warn", message);
1137
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
925
1138
  break;
926
1139
  case MatterLogLevel.ERROR:
927
- matterLogger.log("error", message);
1140
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
928
1141
  break;
929
1142
  case MatterLogLevel.FATAL:
930
- matterLogger.log("fatal", message);
1143
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
931
1144
  break;
932
1145
  default:
933
- matterLogger.log("debug", message);
1146
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
934
1147
  break;
935
1148
  }
936
1149
  };
937
1150
  }
1151
+ /**
1152
+ * Creates a Matter File Logger.
1153
+ *
1154
+ * @param {string} filePath - The path to the log file.
1155
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1156
+ * @returns {Function} - A function that logs formatted messages to the log file.
1157
+ */
938
1158
  async createMatterFileLogger(filePath, unlink = false) {
1159
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
939
1160
  let fileSize = 0;
940
1161
  if (unlink) {
941
1162
  try {
@@ -984,12 +1205,21 @@ export class Matterbridge extends EventEmitter {
984
1205
  }
985
1206
  };
986
1207
  }
1208
+ /**
1209
+ * Restarts the process by exiting the current instance and loading a new instance.
1210
+ */
987
1211
  async restartProcess() {
988
1212
  await this.cleanup('restarting...', true);
989
1213
  }
1214
+ /**
1215
+ * Shut down the process by exiting the current process.
1216
+ */
990
1217
  async shutdownProcess() {
991
1218
  await this.cleanup('shutting down...', false);
992
1219
  }
1220
+ /**
1221
+ * Update matterbridge and and shut down the process.
1222
+ */
993
1223
  async updateProcess() {
994
1224
  this.log.info('Updating matterbridge...');
995
1225
  try {
@@ -1002,52 +1232,73 @@ export class Matterbridge extends EventEmitter {
1002
1232
  this.frontend.wssSendRestartRequired();
1003
1233
  await this.cleanup('updating...', false);
1004
1234
  }
1235
+ /**
1236
+ * Unregister all devices and shut down the process.
1237
+ */
1005
1238
  async unregisterAndShutdownProcess() {
1006
1239
  this.log.info('Unregistering all devices and shutting down...');
1007
1240
  for (const plugin of this.plugins) {
1008
1241
  await this.removeAllBridgedEndpoints(plugin.name, 250);
1009
1242
  }
1010
1243
  this.log.debug('Waiting for the MessageExchange to finish...');
1011
- await new Promise((resolve) => setTimeout(resolve, 1000));
1244
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
1012
1245
  this.log.debug('Cleaning up and shutting down...');
1013
1246
  await this.cleanup('unregistered all devices and shutting down...', false);
1014
1247
  }
1248
+ /**
1249
+ * Reset commissioning and shut down the process.
1250
+ */
1015
1251
  async shutdownProcessAndReset() {
1016
1252
  await this.cleanup('shutting down with reset...', false);
1017
1253
  }
1254
+ /**
1255
+ * Factory reset and shut down the process.
1256
+ */
1018
1257
  async shutdownProcessAndFactoryReset() {
1019
1258
  await this.cleanup('shutting down with factory reset...', false);
1020
1259
  }
1260
+ /**
1261
+ * Cleans up the Matterbridge instance.
1262
+ * @param {string} message - The cleanup message.
1263
+ * @param {boolean} [restart=false] - Indicates whether to restart the instance after cleanup. Default is `false`.
1264
+ * @returns A promise that resolves when the cleanup is completed.
1265
+ */
1021
1266
  async cleanup(message, restart = false) {
1022
1267
  if (this.initialized && !this.hasCleanupStarted) {
1023
1268
  this.emit('cleanup_started');
1024
1269
  this.hasCleanupStarted = true;
1025
1270
  this.log.info(message);
1271
+ // Clear the start matter interval
1026
1272
  if (this.startMatterInterval) {
1027
1273
  clearInterval(this.startMatterInterval);
1028
1274
  this.startMatterInterval = undefined;
1029
1275
  this.log.debug('Start matter interval cleared');
1030
1276
  }
1277
+ // Clear the check update timeout
1031
1278
  if (this.checkUpdateTimeout) {
1032
1279
  clearInterval(this.checkUpdateTimeout);
1033
1280
  this.checkUpdateTimeout = undefined;
1034
1281
  this.log.debug('Check update timeout cleared');
1035
1282
  }
1283
+ // Clear the check update interval
1036
1284
  if (this.checkUpdateInterval) {
1037
1285
  clearInterval(this.checkUpdateInterval);
1038
1286
  this.checkUpdateInterval = undefined;
1039
1287
  this.log.debug('Check update interval cleared');
1040
1288
  }
1289
+ // Clear the configure timeout
1041
1290
  if (this.configureTimeout) {
1042
1291
  clearTimeout(this.configureTimeout);
1043
1292
  this.configureTimeout = undefined;
1044
1293
  this.log.debug('Matterbridge configure timeout cleared');
1045
1294
  }
1295
+ // Clear the reachability timeout
1046
1296
  if (this.reachabilityTimeout) {
1047
1297
  clearTimeout(this.reachabilityTimeout);
1048
1298
  this.reachabilityTimeout = undefined;
1049
1299
  this.log.debug('Matterbridge reachability timeout cleared');
1050
1300
  }
1301
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
1051
1302
  for (const plugin of this.plugins) {
1052
1303
  if (!plugin.enabled || plugin.error)
1053
1304
  continue;
@@ -1058,9 +1309,10 @@ export class Matterbridge extends EventEmitter {
1058
1309
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1059
1310
  }
1060
1311
  }
1312
+ // Stop matter server nodes
1061
1313
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1062
1314
  this.log.debug('Waiting for the MessageExchange to finish...');
1063
- await new Promise((resolve) => setTimeout(resolve, 1000));
1315
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
1064
1316
  if (this.bridgeMode === 'bridge') {
1065
1317
  if (this.serverNode) {
1066
1318
  await this.stopServerNode(this.serverNode);
@@ -1076,6 +1328,7 @@ export class Matterbridge extends EventEmitter {
1076
1328
  }
1077
1329
  }
1078
1330
  this.log.notice('Stopped matter server nodes');
1331
+ // Matter commisioning reset
1079
1332
  if (message === 'shutting down with reset...') {
1080
1333
  this.log.info('Resetting Matterbridge commissioning information...');
1081
1334
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1085,18 +1338,36 @@ export class Matterbridge extends EventEmitter {
1085
1338
  await this.matterbridgeContext?.clearAll();
1086
1339
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1087
1340
  }
1341
+ // Stop matter storage
1088
1342
  await this.stopMatterStorage();
1343
+ // Stop the frontend
1089
1344
  await this.frontend.stop();
1345
+ // Remove the matterfilelogger
1090
1346
  try {
1091
1347
  Logger.removeLogger('matterfilelogger');
1092
1348
  }
1093
1349
  catch (error) {
1094
1350
  this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
1095
1351
  }
1352
+ // Close the matterbridge node storage and context
1096
1353
  if (this.nodeStorage && this.nodeContext) {
1354
+ /*
1355
+ TODO: Implement serialization of registered devices in edge mode
1356
+ this.log.info('Saving registered devices...');
1357
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1358
+ this.devices.forEach(async (device) => {
1359
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1360
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1361
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1362
+ });
1363
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1364
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1365
+ */
1366
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1097
1367
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1098
1368
  await this.nodeContext.close();
1099
1369
  this.nodeContext = undefined;
1370
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1100
1371
  for (const plugin of this.plugins) {
1101
1372
  if (plugin.nodeContext) {
1102
1373
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1113,8 +1384,10 @@ export class Matterbridge extends EventEmitter {
1113
1384
  }
1114
1385
  this.plugins.clear();
1115
1386
  this.devices.clear();
1387
+ // Factory reset
1116
1388
  if (message === 'shutting down with factory reset...') {
1117
1389
  try {
1390
+ // Delete matter storage directory with its subdirectories and backup
1118
1391
  const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
1119
1392
  this.log.info(`Removing matter storage directory: ${dir}`);
1120
1393
  await fs.rm(dir, { recursive: true });
@@ -1128,6 +1401,7 @@ export class Matterbridge extends EventEmitter {
1128
1401
  }
1129
1402
  }
1130
1403
  try {
1404
+ // Delete matterbridge storage directory with its subdirectories and backup
1131
1405
  const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
1132
1406
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1133
1407
  await fs.rm(dir, { recursive: true });
@@ -1142,12 +1416,13 @@ export class Matterbridge extends EventEmitter {
1142
1416
  }
1143
1417
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1144
1418
  }
1419
+ // Deregisters the process handlers
1145
1420
  this.deregisterProcessHandlers();
1146
1421
  if (restart) {
1147
1422
  if (message === 'updating...') {
1148
1423
  this.log.info('Cleanup completed. Updating...');
1149
1424
  Matterbridge.instance = undefined;
1150
- this.emit('update');
1425
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1151
1426
  }
1152
1427
  else if (message === 'restarting...') {
1153
1428
  this.log.info('Cleanup completed. Restarting...');
@@ -1168,6 +1443,14 @@ export class Matterbridge extends EventEmitter {
1168
1443
  this.log.debug('Cleanup already started...');
1169
1444
  }
1170
1445
  }
1446
+ /**
1447
+ * Creates and configures the server node for an accessory plugin for a given device.
1448
+ *
1449
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1450
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1451
+ * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1452
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1453
+ */
1171
1454
  async createAccessoryPlugin(plugin, device, start = false) {
1172
1455
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1173
1456
  plugin.locked = true;
@@ -1181,6 +1464,13 @@ export class Matterbridge extends EventEmitter {
1181
1464
  await this.startServerNode(plugin.serverNode);
1182
1465
  }
1183
1466
  }
1467
+ /**
1468
+ * Creates and configures the server node for a dynamic plugin.
1469
+ *
1470
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1471
+ * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1472
+ * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1473
+ */
1184
1474
  async createDynamicPlugin(plugin, start = false) {
1185
1475
  if (!plugin.locked) {
1186
1476
  plugin.locked = true;
@@ -1193,7 +1483,13 @@ export class Matterbridge extends EventEmitter {
1193
1483
  await this.startServerNode(plugin.serverNode);
1194
1484
  }
1195
1485
  }
1486
+ /**
1487
+ * Starts the Matterbridge in bridge mode.
1488
+ * @private
1489
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1490
+ */
1196
1491
  async startBridge() {
1492
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1197
1493
  if (!this.matterStorageManager)
1198
1494
  throw new Error('No storage manager initialized');
1199
1495
  if (!this.matterbridgeContext)
@@ -1232,7 +1528,9 @@ export class Matterbridge extends EventEmitter {
1232
1528
  clearInterval(this.startMatterInterval);
1233
1529
  this.startMatterInterval = undefined;
1234
1530
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1531
+ // Start the Matter server node
1235
1532
  this.startServerNode(this.serverNode);
1533
+ // Configure the plugins
1236
1534
  this.configureTimeout = setTimeout(async () => {
1237
1535
  for (const plugin of this.plugins) {
1238
1536
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1250,6 +1548,7 @@ export class Matterbridge extends EventEmitter {
1250
1548
  }
1251
1549
  this.frontend.wssSendRefreshRequired('plugins');
1252
1550
  }, 30 * 1000);
1551
+ // Setting reachability to true
1253
1552
  this.reachabilityTimeout = setTimeout(() => {
1254
1553
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1255
1554
  if (this.aggregatorNode)
@@ -1258,6 +1557,11 @@ export class Matterbridge extends EventEmitter {
1258
1557
  }, 60 * 1000);
1259
1558
  }, 1000);
1260
1559
  }
1560
+ /**
1561
+ * Starts the Matterbridge in childbridge mode.
1562
+ * @private
1563
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1564
+ */
1261
1565
  async startChildbridge() {
1262
1566
  if (!this.matterStorageManager)
1263
1567
  throw new Error('No storage manager initialized');
@@ -1295,6 +1599,7 @@ export class Matterbridge extends EventEmitter {
1295
1599
  clearInterval(this.startMatterInterval);
1296
1600
  this.startMatterInterval = undefined;
1297
1601
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1602
+ // Configure the plugins
1298
1603
  this.configureTimeout = setTimeout(async () => {
1299
1604
  for (const plugin of this.plugins) {
1300
1605
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1331,7 +1636,9 @@ export class Matterbridge extends EventEmitter {
1331
1636
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1332
1637
  continue;
1333
1638
  }
1639
+ // Start the Matter server node
1334
1640
  this.startServerNode(plugin.serverNode);
1641
+ // Setting reachability to true
1335
1642
  plugin.reachabilityTimeout = setTimeout(() => {
1336
1643
  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}`);
1337
1644
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1341,9 +1648,227 @@ export class Matterbridge extends EventEmitter {
1341
1648
  }
1342
1649
  }, 1000);
1343
1650
  }
1651
+ /**
1652
+ * Starts the Matterbridge controller.
1653
+ * @private
1654
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1655
+ */
1344
1656
  async startController() {
1657
+ /*
1658
+ if (!this.matterStorageManager) {
1659
+ this.log.error('No storage manager initialized');
1660
+ await this.cleanup('No storage manager initialized');
1661
+ return;
1662
+ }
1663
+ this.log.info('Creating context: mattercontrollerContext');
1664
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1665
+ if (!this.controllerContext) {
1666
+ this.log.error('No storage context mattercontrollerContext initialized');
1667
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1668
+ return;
1669
+ }
1670
+
1671
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1672
+ this.matterServer = await this.createMatterServer(this.storageManager);
1673
+ this.log.info('Creating matter commissioning controller');
1674
+ this.commissioningController = new CommissioningController({
1675
+ autoConnect: false,
1676
+ });
1677
+ this.log.info('Adding matter commissioning controller to matter server');
1678
+ await this.matterServer.addCommissioningController(this.commissioningController);
1679
+
1680
+ this.log.info('Starting matter server');
1681
+ await this.matterServer.start();
1682
+ this.log.info('Matter server started');
1683
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1684
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1685
+ regulatoryCountryCode: 'XX',
1686
+ };
1687
+ const commissioningController = new CommissioningController({
1688
+ environment: {
1689
+ environment,
1690
+ id: uniqueId,
1691
+ },
1692
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1693
+ adminFabricLabel,
1694
+ });
1695
+
1696
+ if (hasParameter('pairingcode')) {
1697
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1698
+ const pairingCode = getParameter('pairingcode');
1699
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1700
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1701
+
1702
+ let longDiscriminator, setupPin, shortDiscriminator;
1703
+ if (pairingCode !== undefined) {
1704
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1705
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1706
+ longDiscriminator = undefined;
1707
+ setupPin = pairingCodeCodec.passcode;
1708
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1709
+ } else {
1710
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1711
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1712
+ setupPin = this.controllerContext.get('pin', 20202021);
1713
+ }
1714
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1715
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1716
+ }
1717
+
1718
+ const options = {
1719
+ commissioning: commissioningOptions,
1720
+ discovery: {
1721
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1722
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1723
+ },
1724
+ passcode: setupPin,
1725
+ } as NodeCommissioningOptions;
1726
+ this.log.info('Commissioning with options:', options);
1727
+ const nodeId = await this.commissioningController.commissionNode(options);
1728
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1729
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1730
+ } // (hasParameter('pairingcode'))
1731
+
1732
+ if (hasParameter('unpairall')) {
1733
+ this.log.info('***Commissioning controller unpairing all nodes...');
1734
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1735
+ for (const nodeId of nodeIds) {
1736
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1737
+ await this.commissioningController.removeNode(nodeId);
1738
+ }
1739
+ return;
1740
+ }
1741
+
1742
+ if (hasParameter('discover')) {
1743
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1744
+ // console.log(discover);
1745
+ }
1746
+
1747
+ if (!this.commissioningController.isCommissioned()) {
1748
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1749
+ return;
1750
+ }
1751
+
1752
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1753
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1754
+ for (const nodeId of nodeIds) {
1755
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1756
+
1757
+ const node = await this.commissioningController.connectNode(nodeId, {
1758
+ autoSubscribe: false,
1759
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1760
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1761
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1762
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1763
+ stateInformationCallback: (peerNodeId, info) => {
1764
+ switch (info) {
1765
+ case NodeStateInformation.Connected:
1766
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1767
+ break;
1768
+ case NodeStateInformation.Disconnected:
1769
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1770
+ break;
1771
+ case NodeStateInformation.Reconnecting:
1772
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1773
+ break;
1774
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1775
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1776
+ break;
1777
+ case NodeStateInformation.StructureChanged:
1778
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1779
+ break;
1780
+ case NodeStateInformation.Decommissioned:
1781
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1782
+ break;
1783
+ default:
1784
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1785
+ break;
1786
+ }
1787
+ },
1788
+ });
1789
+
1790
+ node.logStructure();
1791
+
1792
+ // Get the interaction client
1793
+ this.log.info('Getting the interaction client');
1794
+ const interactionClient = await node.getInteractionClient();
1795
+ let cluster;
1796
+ let attributes;
1797
+
1798
+ // Log BasicInformationCluster
1799
+ cluster = BasicInformationCluster;
1800
+ attributes = await interactionClient.getMultipleAttributes({
1801
+ attributes: [{ clusterId: cluster.id }],
1802
+ });
1803
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1804
+ attributes.forEach((attribute) => {
1805
+ this.log.info(
1806
+ `- 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}`,
1807
+ );
1808
+ });
1809
+
1810
+ // Log PowerSourceCluster
1811
+ cluster = PowerSourceCluster;
1812
+ attributes = await interactionClient.getMultipleAttributes({
1813
+ attributes: [{ clusterId: cluster.id }],
1814
+ });
1815
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1816
+ attributes.forEach((attribute) => {
1817
+ this.log.info(
1818
+ `- 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}`,
1819
+ );
1820
+ });
1821
+
1822
+ // Log ThreadNetworkDiagnostics
1823
+ cluster = ThreadNetworkDiagnosticsCluster;
1824
+ attributes = await interactionClient.getMultipleAttributes({
1825
+ attributes: [{ clusterId: cluster.id }],
1826
+ });
1827
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1828
+ attributes.forEach((attribute) => {
1829
+ this.log.info(
1830
+ `- 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}`,
1831
+ );
1832
+ });
1833
+
1834
+ // Log SwitchCluster
1835
+ cluster = SwitchCluster;
1836
+ attributes = await interactionClient.getMultipleAttributes({
1837
+ attributes: [{ clusterId: cluster.id }],
1838
+ });
1839
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1840
+ attributes.forEach((attribute) => {
1841
+ this.log.info(
1842
+ `- 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}`,
1843
+ );
1844
+ });
1845
+
1846
+ this.log.info('Subscribing to all attributes and events');
1847
+ await node.subscribeAllAttributesAndEvents({
1848
+ ignoreInitialTriggers: false,
1849
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1850
+ this.log.info(
1851
+ `***${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}`,
1852
+ ),
1853
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1854
+ this.log.info(
1855
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1856
+ );
1857
+ },
1858
+ });
1859
+ this.log.info('Subscribed to all attributes and events');
1860
+ }
1861
+ */
1345
1862
  }
1863
+ /** ***********************************************************************************************************************************/
1864
+ /** Matter.js methods */
1865
+ /** ***********************************************************************************************************************************/
1866
+ /**
1867
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1868
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1869
+ */
1346
1870
  async startMatterStorage() {
1871
+ // Setup Matter storage
1347
1872
  this.log.info(`Starting matter node storage...`);
1348
1873
  this.matterStorageService = this.environment.get(StorageService);
1349
1874
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1352,8 +1877,16 @@ export class Matterbridge extends EventEmitter {
1352
1877
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
1353
1878
  this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
1354
1879
  this.log.info('Matter node storage started');
1880
+ // Backup matter storage since it is created/opened correctly
1355
1881
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1356
1882
  }
1883
+ /**
1884
+ * Makes a backup copy of the specified matter storage directory.
1885
+ *
1886
+ * @param storageName - The name of the storage directory to be backed up.
1887
+ * @param backupName - The name of the backup directory to be created.
1888
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1889
+ */
1357
1890
  async backupMatterStorage(storageName, backupName) {
1358
1891
  this.log.info('Creating matter node storage backup...');
1359
1892
  try {
@@ -1364,6 +1897,10 @@ export class Matterbridge extends EventEmitter {
1364
1897
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1365
1898
  }
1366
1899
  }
1900
+ /**
1901
+ * Stops the matter storage.
1902
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1903
+ */
1367
1904
  async stopMatterStorage() {
1368
1905
  this.log.info('Closing matter node storage...');
1369
1906
  await this.matterStorageManager?.close();
@@ -1372,6 +1909,19 @@ export class Matterbridge extends EventEmitter {
1372
1909
  this.matterbridgeContext = undefined;
1373
1910
  this.log.info('Matter node storage closed');
1374
1911
  }
1912
+ /**
1913
+ * Creates a server node storage context.
1914
+ *
1915
+ * @param {string} pluginName - The name of the plugin.
1916
+ * @param {string} deviceName - The name of the device.
1917
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1918
+ * @param {number} vendorId - The vendor ID.
1919
+ * @param {string} vendorName - The vendor name.
1920
+ * @param {number} productId - The product ID.
1921
+ * @param {string} productName - The product name.
1922
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1923
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1924
+ */
1375
1925
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1376
1926
  const { randomBytes } = await import('node:crypto');
1377
1927
  if (!this.matterStorageService)
@@ -1405,6 +1955,15 @@ export class Matterbridge extends EventEmitter {
1405
1955
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1406
1956
  return storageContext;
1407
1957
  }
1958
+ /**
1959
+ * Creates a server node.
1960
+ *
1961
+ * @param {StorageContext} storageContext - The storage context for the server node.
1962
+ * @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
1963
+ * @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
1964
+ * @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
1965
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1966
+ */
1408
1967
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1409
1968
  const storeId = await storageContext.get('storeId');
1410
1969
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1414,24 +1973,37 @@ export class Matterbridge extends EventEmitter {
1414
1973
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1415
1974
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1416
1975
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1976
+ /**
1977
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1978
+ */
1417
1979
  const serverNode = await ServerNode.create({
1980
+ // Required: Give the Node a unique ID which is used to store the state of this node
1418
1981
  id: storeId,
1982
+ // Provide Network relevant configuration like the port
1983
+ // Optional when operating only one device on a host, Default port is 5540
1419
1984
  network: {
1420
1985
  listeningAddressIpv4: this.ipv4address,
1421
1986
  listeningAddressIpv6: this.ipv6address,
1422
1987
  port,
1423
1988
  },
1989
+ // Provide the certificate for the device
1424
1990
  operationalCredentials: {
1425
1991
  certification: this.certification,
1426
1992
  },
1993
+ // Provide Commissioning relevant settings
1994
+ // Optional for development/testing purposes
1427
1995
  commissioning: {
1428
1996
  passcode,
1429
1997
  discriminator,
1430
1998
  },
1999
+ // Provide Node announcement settings
2000
+ // Optional: If Ommitted some development defaults are used
1431
2001
  productDescription: {
1432
2002
  name: await storageContext.get('deviceName'),
1433
2003
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1434
2004
  },
2005
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
2006
+ // Optional: If Omitted some development defaults are used
1435
2007
  basicInformation: {
1436
2008
  vendorId: VendorId(await storageContext.get('vendorId')),
1437
2009
  vendorName: await storageContext.get('vendorName'),
@@ -1449,12 +2021,13 @@ export class Matterbridge extends EventEmitter {
1449
2021
  },
1450
2022
  });
1451
2023
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
2024
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1452
2025
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1453
2026
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1454
2027
  if (this.bridgeMode === 'bridge') {
1455
2028
  this.matterbridgeFabricInformations = sanitizedFabrics;
1456
2029
  if (resetSessions)
1457
- this.matterbridgeSessionInformations = undefined;
2030
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1458
2031
  this.matterbridgePaired = true;
1459
2032
  }
1460
2033
  if (this.bridgeMode === 'childbridge') {
@@ -1462,16 +2035,22 @@ export class Matterbridge extends EventEmitter {
1462
2035
  if (plugin) {
1463
2036
  plugin.fabricInformations = sanitizedFabrics;
1464
2037
  if (resetSessions)
1465
- plugin.sessionInformations = undefined;
2038
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1466
2039
  plugin.paired = true;
1467
2040
  }
1468
2041
  }
1469
2042
  };
2043
+ /**
2044
+ * This event is triggered when the device is initially commissioned successfully.
2045
+ * This means: It is added to the first fabric.
2046
+ */
1470
2047
  serverNode.lifecycle.commissioned.on(() => {
1471
2048
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1472
2049
  clearTimeout(this.endAdvertiseTimeout);
1473
2050
  });
2051
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1474
2052
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
2053
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1475
2054
  serverNode.lifecycle.online.on(async () => {
1476
2055
  this.log.notice(`Server node for ${storeId} is online`);
1477
2056
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1510,6 +2089,7 @@ export class Matterbridge extends EventEmitter {
1510
2089
  }
1511
2090
  }
1512
2091
  }
2092
+ // Set a timeout to show that advertising stops after 15 minutes if not commissioned
1513
2093
  this.startEndAdvertiseTimer(serverNode);
1514
2094
  }
1515
2095
  else {
@@ -1521,6 +2101,7 @@ export class Matterbridge extends EventEmitter {
1521
2101
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1522
2102
  this.emit('online', storeId);
1523
2103
  });
2104
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1524
2105
  serverNode.lifecycle.offline.on(() => {
1525
2106
  this.log.notice(`Server node for ${storeId} is offline`);
1526
2107
  if (this.bridgeMode === 'bridge') {
@@ -1545,6 +2126,10 @@ export class Matterbridge extends EventEmitter {
1545
2126
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1546
2127
  this.emit('offline', storeId);
1547
2128
  });
2129
+ /**
2130
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2131
+ * information is needed.
2132
+ */
1548
2133
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1549
2134
  let action = '';
1550
2135
  switch (fabricAction) {
@@ -1578,16 +2163,24 @@ export class Matterbridge extends EventEmitter {
1578
2163
  }
1579
2164
  }
1580
2165
  };
2166
+ /**
2167
+ * This event is triggered when an operative new session was opened by a Controller.
2168
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2169
+ */
1581
2170
  serverNode.events.sessions.opened.on((session) => {
1582
2171
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1583
2172
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1584
2173
  this.frontend.wssSendRefreshRequired('sessions');
1585
2174
  });
2175
+ /**
2176
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2177
+ */
1586
2178
  serverNode.events.sessions.closed.on((session) => {
1587
2179
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1588
2180
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1589
2181
  this.frontend.wssSendRefreshRequired('sessions');
1590
2182
  });
2183
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1591
2184
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1592
2185
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1593
2186
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1596,6 +2189,12 @@ export class Matterbridge extends EventEmitter {
1596
2189
  this.log.info(`Created server node for ${storeId}`);
1597
2190
  return serverNode;
1598
2191
  }
2192
+ /**
2193
+ * Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
2194
+ *
2195
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2196
+ * @param {string} storeId - The store ID of the server node.
2197
+ */
1599
2198
  startEndAdvertiseTimer(matterServerNode) {
1600
2199
  if (this.endAdvertiseTimeout) {
1601
2200
  this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
@@ -1624,12 +2223,25 @@ export class Matterbridge extends EventEmitter {
1624
2223
  this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
1625
2224
  }, 15 * 60 * 1000).unref();
1626
2225
  }
2226
+ /**
2227
+ * Starts the specified server node.
2228
+ *
2229
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2230
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2231
+ */
1627
2232
  async startServerNode(matterServerNode) {
1628
2233
  if (!matterServerNode)
1629
2234
  return;
1630
2235
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1631
2236
  await matterServerNode.start();
1632
2237
  }
2238
+ /**
2239
+ * Stops the specified server node.
2240
+ *
2241
+ * @param {ServerNode} matterServerNode - The server node to stop.
2242
+ * @param {number} [timeout=30000] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2243
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2244
+ */
1633
2245
  async stopServerNode(matterServerNode, timeout = 30000) {
1634
2246
  if (!matterServerNode)
1635
2247
  return;
@@ -1642,6 +2254,12 @@ export class Matterbridge extends EventEmitter {
1642
2254
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1643
2255
  }
1644
2256
  }
2257
+ /**
2258
+ * Advertises the specified server node.
2259
+ *
2260
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2261
+ * @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.
2262
+ */
1645
2263
  async advertiseServerNode(matterServerNode) {
1646
2264
  if (matterServerNode) {
1647
2265
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1650,25 +2268,46 @@ export class Matterbridge extends EventEmitter {
1650
2268
  return { qrPairingCode, manualPairingCode };
1651
2269
  }
1652
2270
  }
2271
+ /**
2272
+ * Stop advertise the specified server node.
2273
+ *
2274
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2275
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2276
+ */
1653
2277
  async stopAdvertiseServerNode(matterServerNode) {
1654
2278
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1655
2279
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1656
2280
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1657
2281
  }
1658
2282
  }
2283
+ /**
2284
+ * Creates an aggregator node with the specified storage context.
2285
+ *
2286
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2287
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2288
+ */
1659
2289
  async createAggregatorNode(storageContext) {
1660
2290
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1661
2291
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1662
2292
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1663
2293
  return aggregatorNode;
1664
2294
  }
2295
+ /**
2296
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2297
+ *
2298
+ * @param {string} pluginName - The name of the plugin.
2299
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2300
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2301
+ */
1665
2302
  async addBridgedEndpoint(pluginName, device) {
2303
+ // Check if the plugin is registered
1666
2304
  const plugin = this.plugins.get(pluginName);
1667
2305
  if (!plugin) {
1668
2306
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1669
2307
  return;
1670
2308
  }
1671
2309
  if (this.bridgeMode === 'bridge') {
2310
+ // Register and add the device to the matterbridge aggregator node
1672
2311
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1673
2312
  if (!this.aggregatorNode) {
1674
2313
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1685,6 +2324,7 @@ export class Matterbridge extends EventEmitter {
1685
2324
  }
1686
2325
  }
1687
2326
  else if (this.bridgeMode === 'childbridge') {
2327
+ // Register and add the device to the plugin server node
1688
2328
  if (plugin.type === 'AccessoryPlatform') {
1689
2329
  try {
1690
2330
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1701,10 +2341,12 @@ export class Matterbridge extends EventEmitter {
1701
2341
  return;
1702
2342
  }
1703
2343
  }
2344
+ // Register and add the device to the plugin aggregator node
1704
2345
  if (plugin.type === 'DynamicPlatform') {
1705
2346
  try {
1706
2347
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1707
2348
  await this.createDynamicPlugin(plugin);
2349
+ // Fast plugins can add another device before the server node is created
1708
2350
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1709
2351
  if (!plugin.aggregatorNode) {
1710
2352
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1724,17 +2366,28 @@ export class Matterbridge extends EventEmitter {
1724
2366
  plugin.registeredDevices++;
1725
2367
  if (plugin.addedDevices !== undefined)
1726
2368
  plugin.addedDevices++;
2369
+ // Add the device to the DeviceManager
1727
2370
  this.devices.set(device);
2371
+ // Subscribe to the reachable$Changed event
1728
2372
  await this.subscribeAttributeChanged(plugin, device);
1729
2373
  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}`);
1730
2374
  }
2375
+ /**
2376
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2377
+ *
2378
+ * @param {string} pluginName - The name of the plugin.
2379
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2380
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2381
+ */
1731
2382
  async removeBridgedEndpoint(pluginName, device) {
1732
2383
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2384
+ // Check if the plugin is registered
1733
2385
  const plugin = this.plugins.get(pluginName);
1734
2386
  if (!plugin) {
1735
2387
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1736
2388
  return;
1737
2389
  }
2390
+ // Register and add the device to the matterbridge aggregator node
1738
2391
  if (this.bridgeMode === 'bridge') {
1739
2392
  if (!this.aggregatorNode) {
1740
2393
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1749,6 +2402,7 @@ export class Matterbridge extends EventEmitter {
1749
2402
  }
1750
2403
  else if (this.bridgeMode === 'childbridge') {
1751
2404
  if (plugin.type === 'AccessoryPlatform') {
2405
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1752
2406
  }
1753
2407
  else if (plugin.type === 'DynamicPlatform') {
1754
2408
  if (!plugin.aggregatorNode) {
@@ -1763,8 +2417,21 @@ export class Matterbridge extends EventEmitter {
1763
2417
  if (plugin.addedDevices !== undefined)
1764
2418
  plugin.addedDevices--;
1765
2419
  }
2420
+ // Remove the device from the DeviceManager
1766
2421
  this.devices.remove(device);
1767
2422
  }
2423
+ /**
2424
+ * Removes all bridged endpoints from the specified plugin.
2425
+ *
2426
+ * @param {string} pluginName - The name of the plugin.
2427
+ * @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2428
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2429
+ *
2430
+ * @remarks
2431
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2432
+ * It also applies a delay between each removal if specified.
2433
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2434
+ */
1768
2435
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1769
2436
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1770
2437
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1775,6 +2442,15 @@ export class Matterbridge extends EventEmitter {
1775
2442
  if (delay > 0)
1776
2443
  await new Promise((resolve) => setTimeout(resolve, 2000));
1777
2444
  }
2445
+ /**
2446
+ * Subscribes to the attribute change event for the given device and plugin.
2447
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2448
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2449
+ *
2450
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2451
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2452
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2453
+ */
1778
2454
  async subscribeAttributeChanged(plugin, device) {
1779
2455
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
1780
2456
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
@@ -1790,6 +2466,12 @@ export class Matterbridge extends EventEmitter {
1790
2466
  });
1791
2467
  }
1792
2468
  }
2469
+ /**
2470
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2471
+ *
2472
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2473
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2474
+ */
1793
2475
  sanitizeFabricInformations(fabricInfo) {
1794
2476
  return fabricInfo.map((info) => {
1795
2477
  return {
@@ -1803,6 +2485,12 @@ export class Matterbridge extends EventEmitter {
1803
2485
  };
1804
2486
  });
1805
2487
  }
2488
+ /**
2489
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2490
+ *
2491
+ * @param {SessionInformation[]} sessionInfo - The array of session information objects.
2492
+ * @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
2493
+ */
1806
2494
  sanitizeSessionInformation(sessionInfo) {
1807
2495
  return sessionInfo
1808
2496
  .filter((session) => session.isPeerActive)
@@ -1830,7 +2518,20 @@ export class Matterbridge extends EventEmitter {
1830
2518
  };
1831
2519
  });
1832
2520
  }
2521
+ /**
2522
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2523
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2524
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2525
+ */
2526
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1833
2527
  async setAggregatorReachability(aggregatorNode, reachable) {
2528
+ /*
2529
+ for (const child of aggregatorNode.parts) {
2530
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2531
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2532
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2533
+ }
2534
+ */
1834
2535
  }
1835
2536
  getVendorIdName = (vendorId) => {
1836
2537
  if (!vendorId)
@@ -1874,3 +2575,4 @@ export class Matterbridge extends EventEmitter {
1874
2575
  return vendorName;
1875
2576
  };
1876
2577
  }
2578
+ //# sourceMappingURL=matterbridge.js.map