matterbridge 2.2.4-dev.2 → 2.2.4

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