matterbridge 3.0.1-dev-20250506-c77ed36 → 3.0.1

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 (152) hide show
  1. package/CHANGELOG.md +4 -4
  2. package/dist/cli.d.ts +29 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +37 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cluster/export.d.ts +2 -0
  7. package/dist/cluster/export.d.ts.map +1 -0
  8. package/dist/cluster/export.js +2 -0
  9. package/dist/cluster/export.js.map +1 -0
  10. package/dist/defaultConfigSchema.d.ts +27 -0
  11. package/dist/defaultConfigSchema.d.ts.map +1 -0
  12. package/dist/defaultConfigSchema.js +23 -0
  13. package/dist/defaultConfigSchema.js.map +1 -0
  14. package/dist/deviceManager.d.ts +114 -0
  15. package/dist/deviceManager.d.ts.map +1 -0
  16. package/dist/deviceManager.js +94 -1
  17. package/dist/deviceManager.js.map +1 -0
  18. package/dist/frontend.d.ts +240 -0
  19. package/dist/frontend.d.ts.map +1 -0
  20. package/dist/frontend.js +328 -15
  21. package/dist/frontend.js.map +1 -0
  22. package/dist/index.d.ts +35 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +28 -1
  25. package/dist/index.js.map +1 -0
  26. package/dist/logger/export.d.ts +2 -0
  27. package/dist/logger/export.d.ts.map +1 -0
  28. package/dist/logger/export.js +1 -0
  29. package/dist/logger/export.js.map +1 -0
  30. package/dist/matter/behaviors.d.ts +2 -0
  31. package/dist/matter/behaviors.d.ts.map +1 -0
  32. package/dist/matter/behaviors.js +2 -0
  33. package/dist/matter/behaviors.js.map +1 -0
  34. package/dist/matter/clusters.d.ts +2 -0
  35. package/dist/matter/clusters.d.ts.map +1 -0
  36. package/dist/matter/clusters.js +2 -0
  37. package/dist/matter/clusters.js.map +1 -0
  38. package/dist/matter/devices.d.ts +2 -0
  39. package/dist/matter/devices.d.ts.map +1 -0
  40. package/dist/matter/devices.js +2 -0
  41. package/dist/matter/devices.js.map +1 -0
  42. package/dist/matter/endpoints.d.ts +2 -0
  43. package/dist/matter/endpoints.d.ts.map +1 -0
  44. package/dist/matter/endpoints.js +2 -0
  45. package/dist/matter/endpoints.js.map +1 -0
  46. package/dist/matter/export.d.ts +5 -0
  47. package/dist/matter/export.d.ts.map +1 -0
  48. package/dist/matter/export.js +2 -0
  49. package/dist/matter/export.js.map +1 -0
  50. package/dist/matter/types.d.ts +3 -0
  51. package/dist/matter/types.d.ts.map +1 -0
  52. package/dist/matter/types.js +2 -0
  53. package/dist/matter/types.js.map +1 -0
  54. package/dist/matterbridge.d.ts +433 -0
  55. package/dist/matterbridge.d.ts.map +1 -0
  56. package/dist/matterbridge.js +747 -46
  57. package/dist/matterbridge.js.map +1 -0
  58. package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
  59. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  60. package/dist/matterbridgeAccessoryPlatform.js +34 -0
  61. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  62. package/dist/matterbridgeBehaviors.d.ts +1166 -0
  63. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  64. package/dist/matterbridgeBehaviors.js +48 -1
  65. package/dist/matterbridgeBehaviors.js.map +1 -0
  66. package/dist/matterbridgeDeviceTypes.d.ts +494 -0
  67. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  68. package/dist/matterbridgeDeviceTypes.js +431 -12
  69. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  70. package/dist/matterbridgeDynamicPlatform.d.ts +40 -0
  71. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  72. package/dist/matterbridgeDynamicPlatform.js +34 -0
  73. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  74. package/dist/matterbridgeEndpoint.d.ts +956 -0
  75. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  76. package/dist/matterbridgeEndpoint.js +801 -11
  77. package/dist/matterbridgeEndpoint.js.map +1 -0
  78. package/dist/matterbridgeEndpointHelpers.d.ts +2706 -0
  79. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  80. package/dist/matterbridgeEndpointHelpers.js +142 -9
  81. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  82. package/dist/matterbridgePlatform.d.ts +294 -0
  83. package/dist/matterbridgePlatform.d.ts.map +1 -0
  84. package/dist/matterbridgePlatform.js +225 -7
  85. package/dist/matterbridgePlatform.js.map +1 -0
  86. package/dist/matterbridgeTypes.d.ts +187 -0
  87. package/dist/matterbridgeTypes.d.ts.map +1 -0
  88. package/dist/matterbridgeTypes.js +24 -0
  89. package/dist/matterbridgeTypes.js.map +1 -0
  90. package/dist/pluginManager.d.ts +273 -0
  91. package/dist/pluginManager.d.ts.map +1 -0
  92. package/dist/pluginManager.js +264 -3
  93. package/dist/pluginManager.js.map +1 -0
  94. package/dist/shelly.d.ts +92 -0
  95. package/dist/shelly.d.ts.map +1 -0
  96. package/dist/shelly.js +146 -6
  97. package/dist/shelly.js.map +1 -0
  98. package/dist/storage/export.d.ts +2 -0
  99. package/dist/storage/export.d.ts.map +1 -0
  100. package/dist/storage/export.js +1 -0
  101. package/dist/storage/export.js.map +1 -0
  102. package/dist/update.d.ts +32 -0
  103. package/dist/update.d.ts.map +1 -0
  104. package/dist/update.js +52 -0
  105. package/dist/update.js.map +1 -0
  106. package/dist/utils/colorUtils.d.ts +61 -0
  107. package/dist/utils/colorUtils.d.ts.map +1 -0
  108. package/dist/utils/colorUtils.js +205 -2
  109. package/dist/utils/colorUtils.js.map +1 -0
  110. package/dist/utils/copyDirectory.d.ts +32 -0
  111. package/dist/utils/copyDirectory.d.ts.map +1 -0
  112. package/dist/utils/copyDirectory.js +37 -1
  113. package/dist/utils/copyDirectory.js.map +1 -0
  114. package/dist/utils/createZip.d.ts +38 -0
  115. package/dist/utils/createZip.d.ts.map +1 -0
  116. package/dist/utils/createZip.js +42 -2
  117. package/dist/utils/createZip.js.map +1 -0
  118. package/dist/utils/deepCopy.d.ts +31 -0
  119. package/dist/utils/deepCopy.d.ts.map +1 -0
  120. package/dist/utils/deepCopy.js +40 -0
  121. package/dist/utils/deepCopy.js.map +1 -0
  122. package/dist/utils/deepEqual.d.ts +53 -0
  123. package/dist/utils/deepEqual.d.ts.map +1 -0
  124. package/dist/utils/deepEqual.js +65 -1
  125. package/dist/utils/deepEqual.js.map +1 -0
  126. package/dist/utils/export.d.ts +10 -0
  127. package/dist/utils/export.d.ts.map +1 -0
  128. package/dist/utils/export.js +1 -0
  129. package/dist/utils/export.js.map +1 -0
  130. package/dist/utils/isvalid.d.ts +95 -0
  131. package/dist/utils/isvalid.d.ts.map +1 -0
  132. package/dist/utils/isvalid.js +93 -0
  133. package/dist/utils/isvalid.js.map +1 -0
  134. package/dist/utils/network.d.ts +69 -0
  135. package/dist/utils/network.d.ts.map +1 -0
  136. package/dist/utils/network.js +76 -5
  137. package/dist/utils/network.js.map +1 -0
  138. package/dist/utils/parameter.d.ts +58 -0
  139. package/dist/utils/parameter.d.ts.map +1 -0
  140. package/dist/utils/parameter.js +53 -0
  141. package/dist/utils/parameter.js.map +1 -0
  142. package/dist/utils/wait.d.ts +43 -0
  143. package/dist/utils/wait.d.ts.map +1 -0
  144. package/dist/utils/wait.js +48 -5
  145. package/dist/utils/wait.js.map +1 -0
  146. package/frontend/build/asset-manifest.json +3 -3
  147. package/frontend/build/index.html +1 -1
  148. package/frontend/build/static/js/{main.f221e2c1.js → main.15906009.js} +3 -3
  149. package/frontend/build/static/js/{main.f221e2c1.js.map → main.15906009.js.map} +1 -1
  150. package/npm-shrinkwrap.json +2 -2
  151. package/package.json +2 -1
  152. /package/frontend/build/static/js/{main.f221e2c1.js.LICENSE.txt → main.15906009.js.LICENSE.txt} +0 -0
@@ -1,10 +1,36 @@
1
+ /**
2
+ * This file contains the class Matterbridge.
3
+ *
4
+ * @file matterbridge.ts
5
+ * @author Luca Liguori
6
+ * @date 2023-12-29
7
+ * @version 1.5.2
8
+ *
9
+ * Copyright 2023, 2024, 2025 Luca Liguori.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License. *
22
+ */
23
+ // Node.js modules
1
24
  import os from 'node:os';
2
25
  import path from 'node:path';
3
26
  import { promises as fs } from 'node:fs';
4
27
  import EventEmitter from 'node:events';
5
28
  import { inspect } from 'node:util';
29
+ // AnsiLogger module
6
30
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
31
+ // NodeStorage module
7
32
  import { NodeStorageManager } from './storage/export.js';
33
+ // Matterbridge
8
34
  import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber } from './utils/export.js';
9
35
  import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
10
36
  import { PluginManager } from './pluginManager.js';
@@ -12,14 +38,19 @@ import { DeviceManager } from './deviceManager.js';
12
38
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
13
39
  import { bridge } from './matterbridgeDeviceTypes.js';
14
40
  import { Frontend } from './frontend.js';
41
+ // @matter
15
42
  import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
16
43
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
17
44
  import { AggregatorEndpoint } from '@matter/main/endpoints';
18
45
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
19
46
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
47
+ // Default colors
20
48
  const plg = '\u001B[38;5;33m';
21
49
  const dev = '\u001B[38;5;79m';
22
50
  const typ = '\u001B[38;5;207m';
51
+ /**
52
+ * Represents the Matterbridge application.
53
+ */
23
54
  export class Matterbridge extends EventEmitter {
24
55
  systemInformation = {
25
56
  interfaceName: '',
@@ -66,7 +97,7 @@ export class Matterbridge extends EventEmitter {
66
97
  shellySysUpdate: false,
67
98
  shellyMainUpdate: false,
68
99
  profile: getParameter('profile'),
69
- loggerLevel: "info",
100
+ loggerLevel: "info" /* LogLevel.INFO */,
70
101
  fileLogger: false,
71
102
  matterLoggerLevel: MatterLogLevel.INFO,
72
103
  matterFileLogger: false,
@@ -105,9 +136,11 @@ export class Matterbridge extends EventEmitter {
105
136
  plugins;
106
137
  devices;
107
138
  frontend = new Frontend(this);
139
+ // Matterbridge storage
108
140
  nodeStorage;
109
141
  nodeContext;
110
142
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
143
+ // Cleanup
111
144
  hasCleanupStarted = false;
112
145
  initialized = false;
113
146
  execRunningCount = 0;
@@ -120,18 +153,21 @@ export class Matterbridge extends EventEmitter {
120
153
  sigtermHandler;
121
154
  exceptionHandler;
122
155
  rejectionHandler;
156
+ // Matter environment
123
157
  environment = Environment.default;
158
+ // Matter storage
124
159
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
125
160
  matterStorageService;
126
161
  matterStorageManager;
127
162
  matterbridgeContext;
128
163
  controllerContext;
129
- mdnsInterface;
130
- ipv4address;
131
- ipv6address;
132
- port;
133
- passcode;
134
- discriminator;
164
+ // Matter parameters
165
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
166
+ ipv4address; // matter server node listeningAddressIpv4
167
+ ipv6address; // matter server node listeningAddressIpv6
168
+ port; // first server node port
169
+ passcode; // first server node passcode
170
+ discriminator; // first server node discriminator
135
171
  serverNode;
136
172
  aggregatorNode;
137
173
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
@@ -139,21 +175,50 @@ export class Matterbridge extends EventEmitter {
139
175
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
140
176
  aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
141
177
  static instance;
178
+ // We load asyncronously so is private
142
179
  constructor() {
143
180
  super();
144
181
  }
182
+ /**
183
+ * Emits an event of the specified type with the provided arguments.
184
+ *
185
+ * @template K - The type of the event.
186
+ * @param {K} eventName - The name of the event to emit.
187
+ * @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
188
+ * @returns {boolean} - Returns true if the event had listeners, false otherwise.
189
+ */
145
190
  emit(eventName, ...args) {
146
191
  return super.emit(eventName, ...args);
147
192
  }
193
+ /**
194
+ * Registers an event listener for the specified event type.
195
+ *
196
+ * @template K - The type of the event.
197
+ * @param {K} eventName - The name of the event to listen for.
198
+ * @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
199
+ * @returns {this} - Returns the instance of the Matterbridge class.
200
+ */
148
201
  on(eventName, listener) {
149
202
  return super.on(eventName, listener);
150
203
  }
204
+ /**
205
+ * Retrieves the list of Matterbridge devices.
206
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
207
+ */
151
208
  getDevices() {
152
209
  return this.devices.array();
153
210
  }
211
+ /**
212
+ * Retrieves the list of registered plugins.
213
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
214
+ */
154
215
  getPlugins() {
155
216
  return this.plugins.array();
156
217
  }
218
+ /**
219
+ * Set the logger logLevel for the Matterbridge classes.
220
+ * @param {LogLevel} logLevel The logger logLevel to set.
221
+ */
157
222
  async setLogLevel(logLevel) {
158
223
  if (this.log)
159
224
  this.log.logLevel = logLevel;
@@ -167,19 +232,31 @@ export class Matterbridge extends EventEmitter {
167
232
  for (const plugin of this.plugins) {
168
233
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
169
234
  continue;
170
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
171
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
172
- }
173
- let callbackLogLevel = "notice";
174
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
175
- callbackLogLevel = "info";
176
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
177
- callbackLogLevel = "debug";
235
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
236
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
237
+ }
238
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
239
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
240
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
241
+ callbackLogLevel = "info" /* LogLevel.INFO */;
242
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
243
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
178
244
  AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
179
245
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
180
246
  }
247
+ /** ***********************************************************************************************************************************/
248
+ /** loadInstance() and cleanup() methods */
249
+ /** ***********************************************************************************************************************************/
250
+ /**
251
+ * Loads an instance of the Matterbridge class.
252
+ * If an instance already exists, return that instance.
253
+ *
254
+ * @param initialize - Whether to initialize the Matterbridge instance after loading.
255
+ * @returns The loaded Matterbridge instance.
256
+ */
181
257
  static async loadInstance(initialize = false) {
182
258
  if (!Matterbridge.instance) {
259
+ // eslint-disable-next-line no-console
183
260
  if (hasParameter('debug'))
184
261
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
185
262
  Matterbridge.instance = new Matterbridge();
@@ -188,8 +265,14 @@ export class Matterbridge extends EventEmitter {
188
265
  }
189
266
  return Matterbridge.instance;
190
267
  }
268
+ /**
269
+ * Call cleanup().
270
+ * @deprecated This method is deprecated and is only used for jest tests.
271
+ *
272
+ */
191
273
  async destroyInstance() {
192
274
  this.log.info(`Destroy instance...`);
275
+ // Save server nodes to close
193
276
  const servers = [];
194
277
  if (this.bridgeMode === 'bridge') {
195
278
  if (this.serverNode)
@@ -201,55 +284,81 @@ export class Matterbridge extends EventEmitter {
201
284
  servers.push(plugin.serverNode);
202
285
  }
203
286
  }
287
+ // Cleanup
204
288
  await this.cleanup('destroying instance...', false);
289
+ // Close servers mdns service
205
290
  this.log.info(`Dispose ${servers.length} MdnsService...`);
206
291
  for (const server of servers) {
207
292
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
208
293
  this.log.info(`Closed ${server.id} MdnsService`);
209
294
  }
295
+ // Wait for the cleanup to finish
210
296
  await new Promise((resolve) => {
211
297
  setTimeout(resolve, 1000);
212
298
  });
213
299
  }
300
+ /**
301
+ * Initializes the Matterbridge application.
302
+ *
303
+ * @remarks
304
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
305
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
306
+ * node version, registers signal handlers, initializes storage, and parses the command line.
307
+ *
308
+ * @returns A Promise that resolves when the initialization is complete.
309
+ */
214
310
  async initialize() {
311
+ // Set the restart mode
215
312
  if (hasParameter('service'))
216
313
  this.restartMode = 'service';
217
314
  if (hasParameter('docker'))
218
315
  this.restartMode = 'docker';
316
+ // Set the matterbridge directory
219
317
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
220
318
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
319
+ // Setup the matter environment
221
320
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
222
321
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
223
322
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
224
323
  this.environment.vars.set('runtime.signals', false);
225
324
  this.environment.vars.set('runtime.exitcode', false);
226
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
325
+ // Create the matterbridge logger
326
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
327
+ // Register process handlers
227
328
  this.registerProcessHandlers();
329
+ // Initialize nodeStorage and nodeContext
228
330
  try {
229
331
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
230
332
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
231
333
  this.log.debug('Creating node storage context for matterbridge');
232
334
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
335
+ // TODO: Remove this code when node-persist-manager is updated
336
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
337
  const keys = (await this.nodeStorage?.storage.keys());
234
338
  for (const key of keys) {
235
339
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
340
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
236
341
  await this.nodeStorage?.storage.get(key);
237
342
  }
238
343
  const storages = await this.nodeStorage.getStorageNames();
239
344
  for (const storage of storages) {
240
345
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
241
346
  const nodeContext = await this.nodeStorage?.createStorage(storage);
347
+ // TODO: Remove this code when node-persist-manager is updated
348
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
349
  const keys = (await nodeContext?.storage.keys());
243
350
  keys.forEach(async (key) => {
244
351
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
245
352
  await nodeContext?.get(key);
246
353
  });
247
354
  }
355
+ // Creating a backup of the node storage since it is not corrupted
248
356
  this.log.debug('Creating node storage backup...');
249
357
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
250
358
  this.log.debug('Created node storage backup');
251
359
  }
252
360
  catch (error) {
361
+ // Restoring the backup of the node storage since it is corrupted
253
362
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
254
363
  if (hasParameter('norestore')) {
255
364
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -264,41 +373,46 @@ export class Matterbridge extends EventEmitter {
264
373
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
265
374
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
266
375
  }
376
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
267
377
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
378
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
268
379
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
380
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
269
381
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
270
382
  this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
383
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
271
384
  if (hasParameter('logger')) {
272
385
  const level = getParameter('logger');
273
386
  if (level === 'debug') {
274
- this.log.logLevel = "debug";
387
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
275
388
  }
276
389
  else if (level === 'info') {
277
- this.log.logLevel = "info";
390
+ this.log.logLevel = "info" /* LogLevel.INFO */;
278
391
  }
279
392
  else if (level === 'notice') {
280
- this.log.logLevel = "notice";
393
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
281
394
  }
282
395
  else if (level === 'warn') {
283
- this.log.logLevel = "warn";
396
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
284
397
  }
285
398
  else if (level === 'error') {
286
- this.log.logLevel = "error";
399
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
287
400
  }
288
401
  else if (level === 'fatal') {
289
- this.log.logLevel = "fatal";
402
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
290
403
  }
291
404
  else {
292
405
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
293
- this.log.logLevel = "info";
406
+ this.log.logLevel = "info" /* LogLevel.INFO */;
294
407
  }
295
408
  }
296
409
  else {
297
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
410
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
298
411
  }
299
412
  this.frontend.logLevel = this.log.logLevel;
300
413
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
301
414
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
415
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
302
416
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
303
417
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
304
418
  this.matterbridgeInformation.fileLogger = true;
@@ -307,6 +421,7 @@ export class Matterbridge extends EventEmitter {
307
421
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
308
422
  if (this.profile !== undefined)
309
423
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
424
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
310
425
  if (hasParameter('matterlogger')) {
311
426
  const level = getParameter('matterlogger');
312
427
  if (level === 'debug') {
@@ -338,6 +453,7 @@ export class Matterbridge extends EventEmitter {
338
453
  Logger.format = MatterLogFormat.ANSI;
339
454
  Logger.setLogger('default', this.createMatterLogger());
340
455
  this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
456
+ // Create the file logger for matter.js (context: matterFileLog)
341
457
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
342
458
  this.matterbridgeInformation.matterFileLogger = true;
343
459
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -346,6 +462,7 @@ export class Matterbridge extends EventEmitter {
346
462
  });
347
463
  }
348
464
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
465
+ // Log network interfaces
349
466
  const networkInterfaces = os.networkInterfaces();
350
467
  const availableAddresses = Object.entries(networkInterfaces);
351
468
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -357,6 +474,7 @@ export class Matterbridge extends EventEmitter {
357
474
  });
358
475
  }
359
476
  }
477
+ // Set the interface to use for matter server node mdnsInterface
360
478
  if (hasParameter('mdnsinterface')) {
361
479
  this.mdnsInterface = getParameter('mdnsinterface');
362
480
  }
@@ -365,6 +483,7 @@ export class Matterbridge extends EventEmitter {
365
483
  if (this.mdnsInterface === '')
366
484
  this.mdnsInterface = undefined;
367
485
  }
486
+ // Validate mdnsInterface
368
487
  if (this.mdnsInterface) {
369
488
  if (!availableInterfaces.includes(this.mdnsInterface)) {
370
489
  this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -377,6 +496,7 @@ export class Matterbridge extends EventEmitter {
377
496
  }
378
497
  if (this.mdnsInterface)
379
498
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
499
+ // Set the listeningAddressIpv4 for the matter commissioning server
380
500
  if (hasParameter('ipv4address')) {
381
501
  this.ipv4address = getParameter('ipv4address');
382
502
  }
@@ -385,6 +505,7 @@ export class Matterbridge extends EventEmitter {
385
505
  if (this.ipv4address === '')
386
506
  this.ipv4address = undefined;
387
507
  }
508
+ // Validate ipv4address
388
509
  if (this.ipv4address) {
389
510
  let isValid = false;
390
511
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -400,6 +521,7 @@ export class Matterbridge extends EventEmitter {
400
521
  await this.nodeContext.remove('matteripv4address');
401
522
  }
402
523
  }
524
+ // Set the listeningAddressIpv6 for the matter commissioning server
403
525
  if (hasParameter('ipv6address')) {
404
526
  this.ipv6address = getParameter('ipv6address');
405
527
  }
@@ -408,6 +530,7 @@ export class Matterbridge extends EventEmitter {
408
530
  if (this.ipv6address === '')
409
531
  this.ipv6address = undefined;
410
532
  }
533
+ // Validate ipv6address
411
534
  if (this.ipv6address) {
412
535
  let isValid = false;
413
536
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -428,14 +551,19 @@ export class Matterbridge extends EventEmitter {
428
551
  await this.nodeContext.remove('matteripv6address');
429
552
  }
430
553
  }
554
+ // Initialize PluginManager
431
555
  this.plugins = new PluginManager(this);
432
556
  await this.plugins.loadFromStorage();
433
557
  this.plugins.logLevel = this.log.logLevel;
558
+ // Initialize DeviceManager
434
559
  this.devices = new DeviceManager(this, this.nodeContext);
435
560
  this.devices.logLevel = this.log.logLevel;
561
+ // Get the plugins from node storage and create the plugins node storage contexts
436
562
  for (const plugin of this.plugins) {
437
563
  const packageJson = await this.plugins.parse(plugin);
438
564
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
565
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
566
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
439
567
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
440
568
  try {
441
569
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
@@ -457,6 +585,7 @@ export class Matterbridge extends EventEmitter {
457
585
  await plugin.nodeContext.set('description', plugin.description);
458
586
  await plugin.nodeContext.set('author', plugin.author);
459
587
  }
588
+ // Log system info and create .matterbridge directory
460
589
  await this.logNodeAndSystemInfo();
461
590
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
462
591
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -464,6 +593,7 @@ export class Matterbridge extends EventEmitter {
464
593
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
465
594
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
466
595
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
596
+ // Check node version and throw error
467
597
  const minNodeVersion = 18;
468
598
  const nodeVersion = process.versions.node;
469
599
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -471,9 +601,15 @@ export class Matterbridge extends EventEmitter {
471
601
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
472
602
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
473
603
  }
604
+ // Parse command line
474
605
  await this.parseCommandLine();
475
606
  this.initialized = true;
476
607
  }
608
+ /**
609
+ * Parses the command line arguments and performs the corresponding actions.
610
+ * @private
611
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
612
+ */
477
613
  async parseCommandLine() {
478
614
  if (hasParameter('help')) {
479
615
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -592,6 +728,7 @@ export class Matterbridge extends EventEmitter {
592
728
  this.shutdown = true;
593
729
  return;
594
730
  }
731
+ // Start the matter storage and create the matterbridge context
595
732
  try {
596
733
  await this.startMatterStorage();
597
734
  }
@@ -599,12 +736,14 @@ export class Matterbridge extends EventEmitter {
599
736
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
600
737
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
601
738
  }
739
+ // Clear the matterbridge context if the reset parameter is set
602
740
  if (hasParameter('reset') && getParameter('reset') === undefined) {
603
741
  this.initialized = true;
604
742
  await this.shutdownProcessAndReset();
605
743
  this.shutdown = true;
606
744
  return;
607
745
  }
746
+ // Clear matterbridge plugin context if the reset parameter is set
608
747
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
609
748
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
610
749
  const plugin = this.plugins.get(getParameter('reset'));
@@ -629,30 +768,37 @@ export class Matterbridge extends EventEmitter {
629
768
  this.shutdown = true;
630
769
  return;
631
770
  }
771
+ // Initialize frontend
632
772
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
633
773
  await this.frontend.start(getIntParameter('frontend'));
774
+ // Check in 30 seconds the latest versions
634
775
  this.checkUpdateTimeout = setTimeout(async () => {
635
776
  const { checkUpdates } = await import('./update.js');
636
777
  checkUpdates(this);
637
778
  }, 30 * 1000).unref();
779
+ // Check each 24 hours the latest versions
638
780
  this.checkUpdateInterval = setInterval(async () => {
639
781
  const { checkUpdates } = await import('./update.js');
640
782
  checkUpdates(this);
641
783
  }, 12 * 60 * 60 * 1000).unref();
784
+ // Start the matterbridge in mode test
642
785
  if (hasParameter('test')) {
643
786
  this.bridgeMode = 'bridge';
644
787
  MatterbridgeEndpoint.bridgeMode = 'bridge';
645
788
  return;
646
789
  }
790
+ // Start the matterbridge in mode controller
647
791
  if (hasParameter('controller')) {
648
792
  this.bridgeMode = 'controller';
649
793
  await this.startController();
650
794
  return;
651
795
  }
796
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
652
797
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
653
798
  this.log.info('Setting default matterbridge start mode to bridge');
654
799
  await this.nodeContext?.set('bridgeMode', 'bridge');
655
800
  }
801
+ // Start matterbridge in bridge mode
656
802
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
657
803
  this.bridgeMode = 'bridge';
658
804
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -660,6 +806,7 @@ export class Matterbridge extends EventEmitter {
660
806
  await this.startBridge();
661
807
  return;
662
808
  }
809
+ // Start matterbridge in childbridge mode
663
810
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
664
811
  this.bridgeMode = 'childbridge';
665
812
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -668,10 +815,20 @@ export class Matterbridge extends EventEmitter {
668
815
  return;
669
816
  }
670
817
  }
818
+ /**
819
+ * Asynchronously loads and starts the registered plugins.
820
+ *
821
+ * This method is responsible for initializing and staarting all enabled plugins.
822
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
823
+ *
824
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
825
+ */
671
826
  async startPlugins() {
827
+ // Check, load and start the plugins
672
828
  for (const plugin of this.plugins) {
673
829
  plugin.configJson = await this.plugins.loadConfig(plugin);
674
830
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
831
+ // Check if the plugin is available
675
832
  if (!(await this.plugins.resolve(plugin.path))) {
676
833
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
677
834
  plugin.enabled = false;
@@ -691,10 +848,14 @@ export class Matterbridge extends EventEmitter {
691
848
  plugin.addedDevices = undefined;
692
849
  plugin.qrPairingCode = undefined;
693
850
  plugin.manualPairingCode = undefined;
694
- this.plugins.load(plugin, true, 'Matterbridge is starting');
851
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
695
852
  }
696
853
  this.frontend.wssSendRefreshRequired('plugins');
697
854
  }
855
+ /**
856
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
857
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
858
+ */
698
859
  registerProcessHandlers() {
699
860
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
700
861
  process.removeAllListeners('uncaughtException');
@@ -721,6 +882,9 @@ export class Matterbridge extends EventEmitter {
721
882
  };
722
883
  process.on('SIGTERM', this.sigtermHandler);
723
884
  }
885
+ /**
886
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
887
+ */
724
888
  deregisterProcesslHandlers() {
725
889
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
726
890
  if (this.exceptionHandler)
@@ -737,12 +901,17 @@ export class Matterbridge extends EventEmitter {
737
901
  process.off('SIGTERM', this.sigtermHandler);
738
902
  this.sigtermHandler = undefined;
739
903
  }
904
+ /**
905
+ * Logs the node and system information.
906
+ */
740
907
  async logNodeAndSystemInfo() {
908
+ // IP address information
741
909
  const networkInterfaces = os.networkInterfaces();
742
910
  this.systemInformation.interfaceName = '';
743
911
  this.systemInformation.ipv4Address = '';
744
912
  this.systemInformation.ipv6Address = '';
745
913
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
914
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
746
915
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
747
916
  continue;
748
917
  if (!interfaceDetails) {
@@ -768,19 +937,22 @@ export class Matterbridge extends EventEmitter {
768
937
  break;
769
938
  }
770
939
  }
940
+ // Node information
771
941
  this.systemInformation.nodeVersion = process.versions.node;
772
942
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
773
943
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
774
944
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
945
+ // Host system information
775
946
  this.systemInformation.hostname = os.hostname();
776
947
  this.systemInformation.user = os.userInfo().username;
777
- this.systemInformation.osType = os.type();
778
- this.systemInformation.osRelease = os.release();
779
- this.systemInformation.osPlatform = os.platform();
780
- this.systemInformation.osArch = os.arch();
781
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
782
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
783
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
948
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
949
+ this.systemInformation.osRelease = os.release(); // Kernel version
950
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
951
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
952
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
953
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
954
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
955
+ // Log the system information
784
956
  this.log.debug('Host System Information:');
785
957
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
786
958
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -796,16 +968,20 @@ export class Matterbridge extends EventEmitter {
796
968
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
797
969
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
798
970
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
971
+ // Home directory
799
972
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
800
973
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
801
974
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
975
+ // Package root directory
802
976
  const { fileURLToPath } = await import('node:url');
803
977
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
804
978
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
805
979
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
806
980
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
981
+ // Global node_modules directory
807
982
  if (this.nodeContext)
808
983
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
984
+ // First run of Matterbridge so the node storage is empty
809
985
  if (this.globalModulesDirectory === '') {
810
986
  try {
811
987
  this.execRunningCount++;
@@ -821,6 +997,20 @@ export class Matterbridge extends EventEmitter {
821
997
  }
822
998
  else
823
999
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1000
+ /* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
1001
+ else {
1002
+ this.getGlobalNodeModules()
1003
+ .then(async (globalModulesDirectory) => {
1004
+ this.globalModulesDirectory = globalModulesDirectory;
1005
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
1006
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1007
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
1008
+ })
1009
+ .catch((error) => {
1010
+ this.log.error(`Error getting global node_modules directory: ${error}`);
1011
+ });
1012
+ }*/
1013
+ // Create the data directory .matterbridge in the home directory
824
1014
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
825
1015
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
826
1016
  try {
@@ -844,6 +1034,7 @@ export class Matterbridge extends EventEmitter {
844
1034
  }
845
1035
  }
846
1036
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
1037
+ // Create the plugin directory Matterbridge in the home directory
847
1038
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
848
1039
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
849
1040
  try {
@@ -867,6 +1058,7 @@ export class Matterbridge extends EventEmitter {
867
1058
  }
868
1059
  }
869
1060
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
1061
+ // Create the matter cert directory in the home directory
870
1062
  this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
871
1063
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
872
1064
  try {
@@ -890,50 +1082,68 @@ export class Matterbridge extends EventEmitter {
890
1082
  }
891
1083
  }
892
1084
  this.log.debug(`Matterbridge Matter Cert Directory: ${this.matterbridgeCertDirectory}`);
1085
+ // Matterbridge version
893
1086
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
894
1087
  this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
895
1088
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
896
1089
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1090
+ // Matterbridge latest version
897
1091
  if (this.nodeContext)
898
1092
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
899
1093
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1094
+ // this.getMatterbridgeLatestVersion();
1095
+ // Current working directory
900
1096
  const currentDir = process.cwd();
901
1097
  this.log.debug(`Current Working Directory: ${currentDir}`);
1098
+ // Command line arguments (excluding 'node' and the script name)
902
1099
  const cmdArgs = process.argv.slice(2).join(' ');
903
1100
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
904
1101
  }
1102
+ /**
1103
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1104
+ *
1105
+ * @returns {Function} The MatterLogger function.
1106
+ */
905
1107
  createMatterLogger() {
906
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1108
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
907
1109
  return (_level, formattedLog) => {
908
1110
  const logger = formattedLog.slice(44, 44 + 20).trim();
909
1111
  const message = formattedLog.slice(65);
910
1112
  matterLogger.logName = logger;
911
1113
  switch (_level) {
912
1114
  case MatterLogLevel.DEBUG:
913
- matterLogger.log("debug", message);
1115
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
914
1116
  break;
915
1117
  case MatterLogLevel.INFO:
916
- matterLogger.log("info", message);
1118
+ matterLogger.log("info" /* LogLevel.INFO */, message);
917
1119
  break;
918
1120
  case MatterLogLevel.NOTICE:
919
- matterLogger.log("notice", message);
1121
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
920
1122
  break;
921
1123
  case MatterLogLevel.WARN:
922
- matterLogger.log("warn", message);
1124
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
923
1125
  break;
924
1126
  case MatterLogLevel.ERROR:
925
- matterLogger.log("error", message);
1127
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
926
1128
  break;
927
1129
  case MatterLogLevel.FATAL:
928
- matterLogger.log("fatal", message);
1130
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
929
1131
  break;
930
1132
  default:
931
- matterLogger.log("debug", message);
1133
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
932
1134
  break;
933
1135
  }
934
1136
  };
935
1137
  }
1138
+ /**
1139
+ * Creates a Matter File Logger.
1140
+ *
1141
+ * @param {string} filePath - The path to the log file.
1142
+ * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1143
+ * @returns {Function} - A function that logs formatted messages to the log file.
1144
+ */
936
1145
  async createMatterFileLogger(filePath, unlink = false) {
1146
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
937
1147
  let fileSize = 0;
938
1148
  if (unlink) {
939
1149
  try {
@@ -982,12 +1192,21 @@ export class Matterbridge extends EventEmitter {
982
1192
  }
983
1193
  };
984
1194
  }
1195
+ /**
1196
+ * Restarts the process by exiting the current instance and loading a new instance.
1197
+ */
985
1198
  async restartProcess() {
986
1199
  await this.cleanup('restarting...', true);
987
1200
  }
1201
+ /**
1202
+ * Shut down the process by exiting the current process.
1203
+ */
988
1204
  async shutdownProcess() {
989
1205
  await this.cleanup('shutting down...', false);
990
1206
  }
1207
+ /**
1208
+ * Update matterbridge and and shut down the process.
1209
+ */
991
1210
  async updateProcess() {
992
1211
  this.log.info('Updating matterbridge...');
993
1212
  try {
@@ -1000,52 +1219,73 @@ export class Matterbridge extends EventEmitter {
1000
1219
  this.frontend.wssSendRestartRequired();
1001
1220
  await this.cleanup('updating...', false);
1002
1221
  }
1222
+ /**
1223
+ * Unregister all devices and shut down the process.
1224
+ */
1003
1225
  async unregisterAndShutdownProcess() {
1004
1226
  this.log.info('Unregistering all devices and shutting down...');
1005
1227
  for (const plugin of this.plugins) {
1006
1228
  await this.removeAllBridgedEndpoints(plugin.name, 250);
1007
1229
  }
1008
1230
  this.log.debug('Waiting for the MessageExchange to finish...');
1009
- await new Promise((resolve) => setTimeout(resolve, 1000));
1231
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
1010
1232
  this.log.debug('Cleaning up and shutting down...');
1011
1233
  await this.cleanup('unregistered all devices and shutting down...', false);
1012
1234
  }
1235
+ /**
1236
+ * Reset commissioning and shut down the process.
1237
+ */
1013
1238
  async shutdownProcessAndReset() {
1014
1239
  await this.cleanup('shutting down with reset...', false);
1015
1240
  }
1241
+ /**
1242
+ * Factory reset and shut down the process.
1243
+ */
1016
1244
  async shutdownProcessAndFactoryReset() {
1017
1245
  await this.cleanup('shutting down with factory reset...', false);
1018
1246
  }
1247
+ /**
1248
+ * Cleans up the Matterbridge instance.
1249
+ * @param message - The cleanup message.
1250
+ * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1251
+ * @returns A promise that resolves when the cleanup is completed.
1252
+ */
1019
1253
  async cleanup(message, restart = false) {
1020
1254
  if (this.initialized && !this.hasCleanupStarted) {
1021
1255
  this.emit('cleanup_started');
1022
1256
  this.hasCleanupStarted = true;
1023
1257
  this.log.info(message);
1258
+ // Clear the start matter interval
1024
1259
  if (this.startMatterInterval) {
1025
1260
  clearInterval(this.startMatterInterval);
1026
1261
  this.startMatterInterval = undefined;
1027
1262
  this.log.debug('Start matter interval cleared');
1028
1263
  }
1264
+ // Clear the check update timeout
1029
1265
  if (this.checkUpdateTimeout) {
1030
1266
  clearInterval(this.checkUpdateTimeout);
1031
1267
  this.checkUpdateTimeout = undefined;
1032
1268
  this.log.debug('Check update timeout cleared');
1033
1269
  }
1270
+ // Clear the check update interval
1034
1271
  if (this.checkUpdateInterval) {
1035
1272
  clearInterval(this.checkUpdateInterval);
1036
1273
  this.checkUpdateInterval = undefined;
1037
1274
  this.log.debug('Check update interval cleared');
1038
1275
  }
1276
+ // Clear the configure timeout
1039
1277
  if (this.configureTimeout) {
1040
1278
  clearTimeout(this.configureTimeout);
1041
1279
  this.configureTimeout = undefined;
1042
1280
  this.log.debug('Matterbridge configure timeout cleared');
1043
1281
  }
1282
+ // Clear the reachability timeout
1044
1283
  if (this.reachabilityTimeout) {
1045
1284
  clearTimeout(this.reachabilityTimeout);
1046
1285
  this.reachabilityTimeout = undefined;
1047
1286
  this.log.debug('Matterbridge reachability timeout cleared');
1048
1287
  }
1288
+ // Calling the shutdown method of each plugin and clear the plugins reachability timeout
1049
1289
  for (const plugin of this.plugins) {
1050
1290
  if (!plugin.enabled || plugin.error)
1051
1291
  continue;
@@ -1056,9 +1296,10 @@ export class Matterbridge extends EventEmitter {
1056
1296
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1057
1297
  }
1058
1298
  }
1299
+ // Stop matter server nodes
1059
1300
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1060
1301
  this.log.debug('Waiting for the MessageExchange to finish...');
1061
- await new Promise((resolve) => setTimeout(resolve, 1000));
1302
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
1062
1303
  if (this.bridgeMode === 'bridge') {
1063
1304
  if (this.serverNode) {
1064
1305
  await this.stopServerNode(this.serverNode);
@@ -1074,6 +1315,7 @@ export class Matterbridge extends EventEmitter {
1074
1315
  }
1075
1316
  }
1076
1317
  this.log.notice('Stopped matter server nodes');
1318
+ // Matter commisioning reset
1077
1319
  if (message === 'shutting down with reset...') {
1078
1320
  this.log.info('Resetting Matterbridge commissioning information...');
1079
1321
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1083,17 +1325,37 @@ export class Matterbridge extends EventEmitter {
1083
1325
  await this.matterbridgeContext?.clearAll();
1084
1326
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1085
1327
  }
1328
+ // Stop matter storage
1086
1329
  await this.stopMatterStorage();
1330
+ // Stop the frontend
1087
1331
  await this.frontend.stop();
1332
+ // Remove the matterfilelogger
1088
1333
  try {
1089
1334
  Logger.removeLogger('matterfilelogger');
1335
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1090
1336
  }
1091
1337
  catch (error) {
1338
+ // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1092
1339
  }
1340
+ // Serialize registeredDevices
1093
1341
  if (this.nodeStorage && this.nodeContext) {
1342
+ /*
1343
+ TODO: Implement serialization of registered devices in edge mode
1344
+ this.log.info('Saving registered devices...');
1345
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1346
+ this.devices.forEach(async (device) => {
1347
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1348
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1349
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1350
+ });
1351
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1352
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1353
+ */
1354
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1094
1355
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1095
1356
  await this.nodeContext.close();
1096
1357
  this.nodeContext = undefined;
1358
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1097
1359
  for (const plugin of this.plugins) {
1098
1360
  if (plugin.nodeContext) {
1099
1361
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1110,8 +1372,10 @@ export class Matterbridge extends EventEmitter {
1110
1372
  }
1111
1373
  this.plugins.clear();
1112
1374
  this.devices.clear();
1375
+ // Factory reset
1113
1376
  if (message === 'shutting down with factory reset...') {
1114
1377
  try {
1378
+ // Delete old matter storage file and backup
1115
1379
  const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1116
1380
  this.log.info(`Unlinking old matter storage file: ${file}`);
1117
1381
  await fs.unlink(file);
@@ -1125,6 +1389,7 @@ export class Matterbridge extends EventEmitter {
1125
1389
  }
1126
1390
  }
1127
1391
  try {
1392
+ // Delete matter node storage directory with its subdirectories and backup
1128
1393
  const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1129
1394
  this.log.info(`Removing matter node storage directory: ${dir}`);
1130
1395
  await fs.rm(dir, { recursive: true });
@@ -1138,6 +1403,7 @@ export class Matterbridge extends EventEmitter {
1138
1403
  }
1139
1404
  }
1140
1405
  try {
1406
+ // Delete node storage directory with its subdirectories and backup
1141
1407
  const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1142
1408
  this.log.info(`Removing storage directory: ${dir}`);
1143
1409
  await fs.rm(dir, { recursive: true });
@@ -1152,12 +1418,13 @@ export class Matterbridge extends EventEmitter {
1152
1418
  }
1153
1419
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1154
1420
  }
1421
+ // Deregisters the process handlers
1155
1422
  this.deregisterProcesslHandlers();
1156
1423
  if (restart) {
1157
1424
  if (message === 'updating...') {
1158
1425
  this.log.info('Cleanup completed. Updating...');
1159
1426
  Matterbridge.instance = undefined;
1160
- this.emit('update');
1427
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1161
1428
  }
1162
1429
  else if (message === 'restarting...') {
1163
1430
  this.log.info('Cleanup completed. Restarting...');
@@ -1178,6 +1445,14 @@ export class Matterbridge extends EventEmitter {
1178
1445
  this.log.debug('Cleanup already started...');
1179
1446
  }
1180
1447
  }
1448
+ /**
1449
+ * Creates and configures the server node for an accessory plugin for a given device.
1450
+ *
1451
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1452
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1453
+ * @param {boolean} [start=false] - Whether to start the server node after adding the device.
1454
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1455
+ */
1181
1456
  async createAccessoryPlugin(plugin, device, start = false) {
1182
1457
  if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1183
1458
  plugin.locked = true;
@@ -1191,6 +1466,13 @@ export class Matterbridge extends EventEmitter {
1191
1466
  await this.startServerNode(plugin.serverNode);
1192
1467
  }
1193
1468
  }
1469
+ /**
1470
+ * Creates and configures the server node for a dynamic plugin.
1471
+ *
1472
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1473
+ * @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
1474
+ * @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
1475
+ */
1194
1476
  async createDynamicPlugin(plugin, start = false) {
1195
1477
  if (!plugin.locked) {
1196
1478
  plugin.locked = true;
@@ -1203,7 +1485,13 @@ export class Matterbridge extends EventEmitter {
1203
1485
  await this.startServerNode(plugin.serverNode);
1204
1486
  }
1205
1487
  }
1488
+ /**
1489
+ * Starts the Matterbridge in bridge mode.
1490
+ * @private
1491
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1492
+ */
1206
1493
  async startBridge() {
1494
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1207
1495
  if (!this.matterStorageManager)
1208
1496
  throw new Error('No storage manager initialized');
1209
1497
  if (!this.matterbridgeContext)
@@ -1241,7 +1529,9 @@ export class Matterbridge extends EventEmitter {
1241
1529
  clearInterval(this.startMatterInterval);
1242
1530
  this.startMatterInterval = undefined;
1243
1531
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1532
+ // Start the Matter server node
1244
1533
  this.startServerNode(this.serverNode);
1534
+ // Configure the plugins
1245
1535
  this.configureTimeout = setTimeout(async () => {
1246
1536
  for (const plugin of this.plugins) {
1247
1537
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1259,6 +1549,7 @@ export class Matterbridge extends EventEmitter {
1259
1549
  }
1260
1550
  this.frontend.wssSendRefreshRequired('plugins');
1261
1551
  }, 30 * 1000);
1552
+ // Setting reachability to true
1262
1553
  this.reachabilityTimeout = setTimeout(() => {
1263
1554
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1264
1555
  if (this.aggregatorNode)
@@ -1267,6 +1558,11 @@ export class Matterbridge extends EventEmitter {
1267
1558
  }, 60 * 1000);
1268
1559
  }, 1000);
1269
1560
  }
1561
+ /**
1562
+ * Starts the Matterbridge in childbridge mode.
1563
+ * @private
1564
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1565
+ */
1270
1566
  async startChildbridge() {
1271
1567
  if (!this.matterStorageManager)
1272
1568
  throw new Error('No storage manager initialized');
@@ -1304,6 +1600,7 @@ export class Matterbridge extends EventEmitter {
1304
1600
  clearInterval(this.startMatterInterval);
1305
1601
  this.startMatterInterval = undefined;
1306
1602
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1603
+ // Configure the plugins
1307
1604
  this.configureTimeout = setTimeout(async () => {
1308
1605
  for (const plugin of this.plugins) {
1309
1606
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1340,7 +1637,9 @@ export class Matterbridge extends EventEmitter {
1340
1637
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1341
1638
  continue;
1342
1639
  }
1640
+ // Start the Matter server node
1343
1641
  this.startServerNode(plugin.serverNode);
1642
+ // Setting reachability to true
1344
1643
  plugin.reachabilityTimeout = setTimeout(() => {
1345
1644
  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}`);
1346
1645
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1350,6 +1649,11 @@ export class Matterbridge extends EventEmitter {
1350
1649
  }
1351
1650
  }, 1000);
1352
1651
  }
1652
+ /**
1653
+ * Starts the Matterbridge controller.
1654
+ * @private
1655
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1656
+ */
1353
1657
  async startController() {
1354
1658
  if (!this.matterStorageManager) {
1355
1659
  this.log.error('No storage manager initialized');
@@ -1364,8 +1668,207 @@ export class Matterbridge extends EventEmitter {
1364
1668
  return;
1365
1669
  }
1366
1670
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1671
+ /*
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
+ */
1367
1862
  }
1863
+ /** ***********************************************************************************************************************************/
1864
+ /** Matter.js methods */
1865
+ /** ***********************************************************************************************************************************/
1866
+ /**
1867
+ * Starts the matter storage process with name Matterbridge.
1868
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1869
+ */
1368
1870
  async startMatterStorage() {
1871
+ // Setup Matter storage
1369
1872
  this.log.info(`Starting matter node storage...`);
1370
1873
  this.matterStorageService = this.environment.get(StorageService);
1371
1874
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1374,13 +1877,25 @@ export class Matterbridge extends EventEmitter {
1374
1877
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
1375
1878
  this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
1376
1879
  this.log.info('Matter node storage started');
1880
+ // Backup matter storage since it is created/opened correctly
1377
1881
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1378
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
+ */
1379
1890
  async backupMatterStorage(storageName, backupName) {
1380
1891
  this.log.info('Creating matter node storage backup...');
1381
1892
  await copyDirectory(storageName, backupName);
1382
1893
  this.log.info('Created matter node storage backup');
1383
1894
  }
1895
+ /**
1896
+ * Stops the matter storage.
1897
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1898
+ */
1384
1899
  async stopMatterStorage() {
1385
1900
  this.log.info('Closing matter node storage...');
1386
1901
  await this.matterStorageManager?.close();
@@ -1389,6 +1904,19 @@ export class Matterbridge extends EventEmitter {
1389
1904
  this.matterbridgeContext = undefined;
1390
1905
  this.log.info('Matter node storage closed');
1391
1906
  }
1907
+ /**
1908
+ * Creates a server node storage context.
1909
+ *
1910
+ * @param {string} pluginName - The name of the plugin.
1911
+ * @param {string} deviceName - The name of the device.
1912
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1913
+ * @param {number} vendorId - The vendor ID.
1914
+ * @param {string} vendorName - The vendor name.
1915
+ * @param {number} productId - The product ID.
1916
+ * @param {string} productName - The product name.
1917
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1918
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1919
+ */
1392
1920
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1393
1921
  const { randomBytes } = await import('node:crypto');
1394
1922
  if (!this.matterStorageService)
@@ -1422,6 +1950,15 @@ export class Matterbridge extends EventEmitter {
1422
1950
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1423
1951
  return storageContext;
1424
1952
  }
1953
+ /**
1954
+ * Creates a server node.
1955
+ *
1956
+ * @param {StorageContext} storageContext - The storage context for the server node.
1957
+ * @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
1958
+ * @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
1959
+ * @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
1960
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1961
+ */
1425
1962
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1426
1963
  const storeId = await storageContext.get('storeId');
1427
1964
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1431,21 +1968,33 @@ export class Matterbridge extends EventEmitter {
1431
1968
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1432
1969
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1433
1970
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1971
+ /**
1972
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1973
+ */
1434
1974
  const serverNode = await ServerNode.create({
1975
+ // Required: Give the Node a unique ID which is used to store the state of this node
1435
1976
  id: storeId,
1977
+ // Provide Network relevant configuration like the port
1978
+ // Optional when operating only one device on a host, Default port is 5540
1436
1979
  network: {
1437
1980
  listeningAddressIpv4: this.ipv4address,
1438
1981
  listeningAddressIpv6: this.ipv6address,
1439
1982
  port,
1440
1983
  },
1984
+ // Provide Commissioning relevant settings
1985
+ // Optional for development/testing purposes
1441
1986
  commissioning: {
1442
1987
  passcode,
1443
1988
  discriminator,
1444
1989
  },
1990
+ // Provide Node announcement settings
1991
+ // Optional: If Ommitted some development defaults are used
1445
1992
  productDescription: {
1446
1993
  name: await storageContext.get('deviceName'),
1447
1994
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1448
1995
  },
1996
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1997
+ // Optional: If Omitted some development defaults are used
1449
1998
  basicInformation: {
1450
1999
  vendorId: VendorId(await storageContext.get('vendorId')),
1451
2000
  vendorName: await storageContext.get('vendorName'),
@@ -1463,12 +2012,13 @@ export class Matterbridge extends EventEmitter {
1463
2012
  },
1464
2013
  });
1465
2014
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
2015
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1466
2016
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1467
2017
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1468
2018
  if (this.bridgeMode === 'bridge') {
1469
2019
  this.matterbridgeFabricInformations = sanitizedFabrics;
1470
2020
  if (resetSessions)
1471
- this.matterbridgeSessionInformations = undefined;
2021
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1472
2022
  this.matterbridgePaired = true;
1473
2023
  }
1474
2024
  if (this.bridgeMode === 'childbridge') {
@@ -1476,13 +2026,19 @@ export class Matterbridge extends EventEmitter {
1476
2026
  if (plugin) {
1477
2027
  plugin.fabricInformations = sanitizedFabrics;
1478
2028
  if (resetSessions)
1479
- plugin.sessionInformations = undefined;
2029
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1480
2030
  plugin.paired = true;
1481
2031
  }
1482
2032
  }
1483
2033
  };
2034
+ /**
2035
+ * This event is triggered when the device is initially commissioned successfully.
2036
+ * This means: It is added to the first fabric.
2037
+ */
1484
2038
  serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
2039
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1485
2040
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
2041
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1486
2042
  serverNode.lifecycle.online.on(async () => {
1487
2043
  this.log.notice(`Server node for ${storeId} is online`);
1488
2044
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1551,6 +2107,7 @@ export class Matterbridge extends EventEmitter {
1551
2107
  this.frontend.wssSendRefreshRequired('settings');
1552
2108
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1553
2109
  });
2110
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1554
2111
  serverNode.lifecycle.offline.on(() => {
1555
2112
  this.log.notice(`Server node for ${storeId} is offline`);
1556
2113
  if (this.bridgeMode === 'bridge') {
@@ -1574,6 +2131,10 @@ export class Matterbridge extends EventEmitter {
1574
2131
  this.frontend.wssSendRefreshRequired('settings');
1575
2132
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1576
2133
  });
2134
+ /**
2135
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2136
+ * information is needed.
2137
+ */
1577
2138
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1578
2139
  let action = '';
1579
2140
  switch (fabricAction) {
@@ -1607,16 +2168,24 @@ export class Matterbridge extends EventEmitter {
1607
2168
  }
1608
2169
  }
1609
2170
  };
2171
+ /**
2172
+ * This event is triggered when an operative new session was opened by a Controller.
2173
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2174
+ */
1610
2175
  serverNode.events.sessions.opened.on((session) => {
1611
2176
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1612
2177
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1613
2178
  this.frontend.wssSendRefreshRequired('sessions');
1614
2179
  });
2180
+ /**
2181
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2182
+ */
1615
2183
  serverNode.events.sessions.closed.on((session) => {
1616
2184
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1617
2185
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1618
2186
  this.frontend.wssSendRefreshRequired('sessions');
1619
2187
  });
2188
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1620
2189
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1621
2190
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1622
2191
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1625,24 +2194,42 @@ export class Matterbridge extends EventEmitter {
1625
2194
  this.log.info(`Created server node for ${storeId}`);
1626
2195
  return serverNode;
1627
2196
  }
2197
+ /**
2198
+ * Starts the specified server node.
2199
+ *
2200
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2201
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2202
+ */
1628
2203
  async startServerNode(matterServerNode) {
1629
2204
  if (!matterServerNode)
1630
2205
  return;
1631
2206
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1632
2207
  await matterServerNode.start();
1633
2208
  }
2209
+ /**
2210
+ * Stops the specified server node.
2211
+ *
2212
+ * @param {ServerNode} matterServerNode - The server node to stop.
2213
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2214
+ */
1634
2215
  async stopServerNode(matterServerNode) {
1635
2216
  if (!matterServerNode)
1636
2217
  return;
1637
2218
  this.log.notice(`Closing ${matterServerNode.id} server node`);
1638
2219
  try {
1639
- await withTimeout(matterServerNode.close(), 30000);
2220
+ await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
1640
2221
  this.log.info(`Closed ${matterServerNode.id} server node`);
1641
2222
  }
1642
2223
  catch (error) {
1643
2224
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1644
2225
  }
1645
2226
  }
2227
+ /**
2228
+ * Advertises the specified server node.
2229
+ *
2230
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2231
+ * @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.
2232
+ */
1646
2233
  async advertiseServerNode(matterServerNode) {
1647
2234
  if (matterServerNode) {
1648
2235
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1651,24 +2238,45 @@ export class Matterbridge extends EventEmitter {
1651
2238
  return { qrPairingCode, manualPairingCode };
1652
2239
  }
1653
2240
  }
2241
+ /**
2242
+ * Stop advertise the specified server node.
2243
+ *
2244
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2245
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2246
+ */
1654
2247
  async stopAdvertiseServerNode(matterServerNode) {
1655
2248
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1656
2249
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1657
2250
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1658
2251
  }
1659
2252
  }
2253
+ /**
2254
+ * Creates an aggregator node with the specified storage context.
2255
+ *
2256
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2257
+ * @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2258
+ */
1660
2259
  async createAggregatorNode(storageContext) {
1661
2260
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
1662
2261
  const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1663
2262
  return aggregatorNode;
1664
2263
  }
2264
+ /**
2265
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2266
+ *
2267
+ * @param {string} pluginName - The name of the plugin.
2268
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2269
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2270
+ */
1665
2271
  async addBridgedEndpoint(pluginName, device) {
2272
+ // Check if the plugin is registered
1666
2273
  const plugin = this.plugins.get(pluginName);
1667
2274
  if (!plugin) {
1668
2275
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1669
2276
  return;
1670
2277
  }
1671
2278
  if (this.bridgeMode === 'bridge') {
2279
+ // Register and add the device to the matterbridge aggregator node
1672
2280
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1673
2281
  if (!this.aggregatorNode) {
1674
2282
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1685,6 +2293,7 @@ export class Matterbridge extends EventEmitter {
1685
2293
  }
1686
2294
  }
1687
2295
  else if (this.bridgeMode === 'childbridge') {
2296
+ // Register and add the device to the plugin server node
1688
2297
  if (plugin.type === 'AccessoryPlatform') {
1689
2298
  try {
1690
2299
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1701,10 +2310,12 @@ export class Matterbridge extends EventEmitter {
1701
2310
  return;
1702
2311
  }
1703
2312
  }
2313
+ // Register and add the device to the plugin aggregator node
1704
2314
  if (plugin.type === 'DynamicPlatform') {
1705
2315
  try {
1706
2316
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1707
2317
  await this.createDynamicPlugin(plugin);
2318
+ // Fast plugins can add another device before the server node is created
1708
2319
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1709
2320
  if (!plugin.aggregatorNode) {
1710
2321
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1724,17 +2335,28 @@ export class Matterbridge extends EventEmitter {
1724
2335
  plugin.registeredDevices++;
1725
2336
  if (plugin.addedDevices !== undefined)
1726
2337
  plugin.addedDevices++;
2338
+ // Add the device to the DeviceManager
1727
2339
  this.devices.set(device);
2340
+ // Subscribe to the reachable$Changed event
1728
2341
  await this.subscribeAttributeChanged(plugin, device);
1729
2342
  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
2343
  }
2344
+ /**
2345
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2346
+ *
2347
+ * @param {string} pluginName - The name of the plugin.
2348
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2349
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2350
+ */
1731
2351
  async removeBridgedEndpoint(pluginName, device) {
1732
2352
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2353
+ // Check if the plugin is registered
1733
2354
  const plugin = this.plugins.get(pluginName);
1734
2355
  if (!plugin) {
1735
2356
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1736
2357
  return;
1737
2358
  }
2359
+ // Register and add the device to the matterbridge aggregator node
1738
2360
  if (this.bridgeMode === 'bridge') {
1739
2361
  if (!this.aggregatorNode) {
1740
2362
  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 +2371,7 @@ export class Matterbridge extends EventEmitter {
1749
2371
  }
1750
2372
  else if (this.bridgeMode === 'childbridge') {
1751
2373
  if (plugin.type === 'AccessoryPlatform') {
2374
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1752
2375
  }
1753
2376
  else if (plugin.type === 'DynamicPlatform') {
1754
2377
  if (!plugin.aggregatorNode) {
@@ -1763,8 +2386,21 @@ export class Matterbridge extends EventEmitter {
1763
2386
  if (plugin.addedDevices !== undefined)
1764
2387
  plugin.addedDevices--;
1765
2388
  }
2389
+ // Remove the device from the DeviceManager
1766
2390
  this.devices.remove(device);
1767
2391
  }
2392
+ /**
2393
+ * Removes all bridged endpoints from the specified plugin.
2394
+ *
2395
+ * @param {string} pluginName - The name of the plugin.
2396
+ * @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2397
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2398
+ *
2399
+ * @remarks
2400
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2401
+ * It also applies a delay between each removal if specified.
2402
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2403
+ */
1768
2404
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1769
2405
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1770
2406
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1775,9 +2411,25 @@ export class Matterbridge extends EventEmitter {
1775
2411
  if (delay > 0)
1776
2412
  await new Promise((resolve) => setTimeout(resolve, 2000));
1777
2413
  }
2414
+ /**
2415
+ * Subscribes to the attribute change event for the given device and plugin.
2416
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2417
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2418
+ *
2419
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2420
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2421
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2422
+ */
1778
2423
  async subscribeAttributeChanged(plugin, device) {
1779
2424
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
1780
2425
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
2426
+ /*
2427
+ this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) subscribed to reachable$Changed`);
2428
+ setTimeout(async () => {
2429
+ this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) changed to reachable false`);
2430
+ await plugin.serverNode?.setStateOf(BasicInformationServer, { reachable: false });
2431
+ }, 60000).unref();
2432
+ */
1781
2433
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1782
2434
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
1783
2435
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
@@ -1790,6 +2442,12 @@ export class Matterbridge extends EventEmitter {
1790
2442
  });
1791
2443
  }
1792
2444
  }
2445
+ /**
2446
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2447
+ *
2448
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2449
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2450
+ */
1793
2451
  sanitizeFabricInformations(fabricInfo) {
1794
2452
  return fabricInfo.map((info) => {
1795
2453
  return {
@@ -1803,6 +2461,12 @@ export class Matterbridge extends EventEmitter {
1803
2461
  };
1804
2462
  });
1805
2463
  }
2464
+ /**
2465
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2466
+ *
2467
+ * @param {SessionInformation[]} sessionInfo - The array of session information objects.
2468
+ * @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
2469
+ */
1806
2470
  sanitizeSessionInformation(sessionInfo) {
1807
2471
  return sessionInfo
1808
2472
  .filter((session) => session.isPeerActive)
@@ -1830,7 +2494,20 @@ export class Matterbridge extends EventEmitter {
1830
2494
  };
1831
2495
  });
1832
2496
  }
2497
+ /**
2498
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2499
+ * @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2500
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2501
+ */
2502
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1833
2503
  async setAggregatorReachability(aggregatorNode, reachable) {
2504
+ /*
2505
+ for (const child of aggregatorNode.parts) {
2506
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2507
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2508
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2509
+ }
2510
+ */
1834
2511
  }
1835
2512
  getVendorIdName = (vendorId) => {
1836
2513
  if (!vendorId)
@@ -1873,14 +2550,37 @@ export class Matterbridge extends EventEmitter {
1873
2550
  }
1874
2551
  return vendorName;
1875
2552
  };
2553
+ /**
2554
+ * Spawns a child process with the given command and arguments.
2555
+ * @param {string} command - The command to execute.
2556
+ * @param {string[]} args - The arguments to pass to the command (default: []).
2557
+ * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2558
+ */
1876
2559
  async spawnCommand(command, args = []) {
1877
2560
  const { spawn } = await import('node:child_process');
2561
+ /*
2562
+ npm > npm.cmd on windows
2563
+ cmd.exe ['dir'] on windows
2564
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2565
+ process.on('unhandledRejection', (reason, promise) => {
2566
+ this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2567
+ });
2568
+
2569
+ spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2570
+ spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2571
+ debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2572
+ debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2573
+ */
1878
2574
  const cmdLine = command + ' ' + args.join(' ');
1879
2575
  if (process.platform === 'win32' && command === 'npm') {
2576
+ // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
1880
2577
  const argstring = 'npm ' + args.join(' ');
1881
2578
  args.splice(0, args.length, '/c', argstring);
1882
2579
  command = 'cmd.exe';
1883
2580
  }
2581
+ // Decide when using sudo on linux
2582
+ // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2583
+ // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
1884
2584
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
1885
2585
  args.unshift(command);
1886
2586
  command = 'sudo';
@@ -1939,3 +2639,4 @@ export class Matterbridge extends EventEmitter {
1939
2639
  });
1940
2640
  }
1941
2641
  }
2642
+ //# sourceMappingURL=matterbridge.js.map