matterbridge 3.1.4-dev-20250717-d36e252 → 3.1.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 (206) hide show
  1. package/dist/cli.d.ts +26 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +91 -2
  4. package/dist/cli.js.map +1 -0
  5. package/dist/cliEmitter.d.ts +34 -0
  6. package/dist/cliEmitter.d.ts.map +1 -0
  7. package/dist/cliEmitter.js +30 -0
  8. package/dist/cliEmitter.js.map +1 -0
  9. package/dist/clusters/export.d.ts +2 -0
  10. package/dist/clusters/export.d.ts.map +1 -0
  11. package/dist/clusters/export.js +2 -0
  12. package/dist/clusters/export.js.map +1 -0
  13. package/dist/defaultConfigSchema.d.ts +28 -0
  14. package/dist/defaultConfigSchema.d.ts.map +1 -0
  15. package/dist/defaultConfigSchema.js +24 -0
  16. package/dist/defaultConfigSchema.js.map +1 -0
  17. package/dist/deviceManager.d.ts +112 -0
  18. package/dist/deviceManager.d.ts.map +1 -0
  19. package/dist/deviceManager.js +94 -1
  20. package/dist/deviceManager.js.map +1 -0
  21. package/dist/devices/batteryStorage.d.ts +48 -0
  22. package/dist/devices/batteryStorage.d.ts.map +1 -0
  23. package/dist/devices/batteryStorage.js +48 -1
  24. package/dist/devices/batteryStorage.js.map +1 -0
  25. package/dist/devices/evse.d.ts +75 -0
  26. package/dist/devices/evse.d.ts.map +1 -0
  27. package/dist/devices/evse.js +74 -10
  28. package/dist/devices/evse.js.map +1 -0
  29. package/dist/devices/export.d.ts +9 -0
  30. package/dist/devices/export.d.ts.map +1 -0
  31. package/dist/devices/export.js +2 -0
  32. package/dist/devices/export.js.map +1 -0
  33. package/dist/devices/heatPump.d.ts +47 -0
  34. package/dist/devices/heatPump.d.ts.map +1 -0
  35. package/dist/devices/heatPump.js +50 -2
  36. package/dist/devices/heatPump.js.map +1 -0
  37. package/dist/devices/laundryDryer.d.ts +87 -0
  38. package/dist/devices/laundryDryer.d.ts.map +1 -0
  39. package/dist/devices/laundryDryer.js +83 -6
  40. package/dist/devices/laundryDryer.js.map +1 -0
  41. package/dist/devices/laundryWasher.d.ts +242 -0
  42. package/dist/devices/laundryWasher.d.ts.map +1 -0
  43. package/dist/devices/laundryWasher.js +91 -7
  44. package/dist/devices/laundryWasher.js.map +1 -0
  45. package/dist/devices/roboticVacuumCleaner.d.ts +110 -0
  46. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  47. package/dist/devices/roboticVacuumCleaner.js +89 -6
  48. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  49. package/dist/devices/solarPower.d.ts +40 -0
  50. package/dist/devices/solarPower.d.ts.map +1 -0
  51. package/dist/devices/solarPower.js +38 -0
  52. package/dist/devices/solarPower.js.map +1 -0
  53. package/dist/devices/waterHeater.d.ts +111 -0
  54. package/dist/devices/waterHeater.d.ts.map +1 -0
  55. package/dist/devices/waterHeater.js +82 -2
  56. package/dist/devices/waterHeater.js.map +1 -0
  57. package/dist/frontend.d.ts +304 -0
  58. package/dist/frontend.d.ts.map +1 -0
  59. package/dist/frontend.js +429 -21
  60. package/dist/frontend.js.map +1 -0
  61. package/dist/globalMatterbridge.d.ts +59 -0
  62. package/dist/globalMatterbridge.d.ts.map +1 -0
  63. package/dist/globalMatterbridge.js +47 -0
  64. package/dist/globalMatterbridge.js.map +1 -0
  65. package/dist/helpers.d.ts +48 -0
  66. package/dist/helpers.d.ts.map +1 -0
  67. package/dist/helpers.js +53 -0
  68. package/dist/helpers.js.map +1 -0
  69. package/dist/index.d.ts +33 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +30 -1
  72. package/dist/index.js.map +1 -0
  73. package/dist/logger/export.d.ts +2 -0
  74. package/dist/logger/export.d.ts.map +1 -0
  75. package/dist/logger/export.js +1 -0
  76. package/dist/logger/export.js.map +1 -0
  77. package/dist/matter/behaviors.d.ts +2 -0
  78. package/dist/matter/behaviors.d.ts.map +1 -0
  79. package/dist/matter/behaviors.js +2 -0
  80. package/dist/matter/behaviors.js.map +1 -0
  81. package/dist/matter/clusters.d.ts +2 -0
  82. package/dist/matter/clusters.d.ts.map +1 -0
  83. package/dist/matter/clusters.js +2 -0
  84. package/dist/matter/clusters.js.map +1 -0
  85. package/dist/matter/devices.d.ts +2 -0
  86. package/dist/matter/devices.d.ts.map +1 -0
  87. package/dist/matter/devices.js +2 -0
  88. package/dist/matter/devices.js.map +1 -0
  89. package/dist/matter/endpoints.d.ts +2 -0
  90. package/dist/matter/endpoints.d.ts.map +1 -0
  91. package/dist/matter/endpoints.js +2 -0
  92. package/dist/matter/endpoints.js.map +1 -0
  93. package/dist/matter/export.d.ts +5 -0
  94. package/dist/matter/export.d.ts.map +1 -0
  95. package/dist/matter/export.js +3 -0
  96. package/dist/matter/export.js.map +1 -0
  97. package/dist/matter/types.d.ts +3 -0
  98. package/dist/matter/types.d.ts.map +1 -0
  99. package/dist/matter/types.js +3 -0
  100. package/dist/matter/types.js.map +1 -0
  101. package/dist/matterbridge.d.ts +444 -0
  102. package/dist/matterbridge.d.ts.map +1 -0
  103. package/dist/matterbridge.js +785 -51
  104. package/dist/matterbridge.js.map +1 -0
  105. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  106. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  107. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  108. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  109. package/dist/matterbridgeBehaviors.d.ts +1340 -0
  110. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  111. package/dist/matterbridgeBehaviors.js +61 -1
  112. package/dist/matterbridgeBehaviors.js.map +1 -0
  113. package/dist/matterbridgeDeviceTypes.d.ts +709 -0
  114. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  115. package/dist/matterbridgeDeviceTypes.js +579 -15
  116. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  117. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  118. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  119. package/dist/matterbridgeDynamicPlatform.js +36 -0
  120. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  121. package/dist/matterbridgeEndpoint.d.ts +1250 -0
  122. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  123. package/dist/matterbridgeEndpoint.js +1106 -42
  124. package/dist/matterbridgeEndpoint.js.map +1 -0
  125. package/dist/matterbridgeEndpointHelpers.d.ts +3198 -0
  126. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  127. package/dist/matterbridgeEndpointHelpers.js +322 -12
  128. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  129. package/dist/matterbridgePlatform.d.ts +310 -0
  130. package/dist/matterbridgePlatform.d.ts.map +1 -0
  131. package/dist/matterbridgePlatform.js +233 -0
  132. package/dist/matterbridgePlatform.js.map +1 -0
  133. package/dist/matterbridgeTypes.d.ts +195 -0
  134. package/dist/matterbridgeTypes.d.ts.map +1 -0
  135. package/dist/matterbridgeTypes.js +25 -0
  136. package/dist/matterbridgeTypes.js.map +1 -0
  137. package/dist/pluginManager.d.ts +291 -0
  138. package/dist/pluginManager.d.ts.map +1 -0
  139. package/dist/pluginManager.js +269 -3
  140. package/dist/pluginManager.js.map +1 -0
  141. package/dist/shelly.d.ts +174 -0
  142. package/dist/shelly.d.ts.map +1 -0
  143. package/dist/shelly.js +168 -7
  144. package/dist/shelly.js.map +1 -0
  145. package/dist/storage/export.d.ts +2 -0
  146. package/dist/storage/export.d.ts.map +1 -0
  147. package/dist/storage/export.js +1 -0
  148. package/dist/storage/export.js.map +1 -0
  149. package/dist/update.d.ts +59 -0
  150. package/dist/update.d.ts.map +1 -0
  151. package/dist/update.js +54 -0
  152. package/dist/update.js.map +1 -0
  153. package/dist/utils/colorUtils.d.ts +117 -0
  154. package/dist/utils/colorUtils.d.ts.map +1 -0
  155. package/dist/utils/colorUtils.js +263 -2
  156. package/dist/utils/colorUtils.js.map +1 -0
  157. package/dist/utils/commandLine.d.ts +59 -0
  158. package/dist/utils/commandLine.d.ts.map +1 -0
  159. package/dist/utils/commandLine.js +54 -0
  160. package/dist/utils/commandLine.js.map +1 -0
  161. package/dist/utils/copyDirectory.d.ts +33 -0
  162. package/dist/utils/copyDirectory.d.ts.map +1 -0
  163. package/dist/utils/copyDirectory.js +38 -1
  164. package/dist/utils/copyDirectory.js.map +1 -0
  165. package/dist/utils/createDirectory.d.ts +34 -0
  166. package/dist/utils/createDirectory.d.ts.map +1 -0
  167. package/dist/utils/createDirectory.js +33 -0
  168. package/dist/utils/createDirectory.js.map +1 -0
  169. package/dist/utils/createZip.d.ts +39 -0
  170. package/dist/utils/createZip.d.ts.map +1 -0
  171. package/dist/utils/createZip.js +47 -2
  172. package/dist/utils/createZip.js.map +1 -0
  173. package/dist/utils/deepCopy.d.ts +32 -0
  174. package/dist/utils/deepCopy.d.ts.map +1 -0
  175. package/dist/utils/deepCopy.js +39 -0
  176. package/dist/utils/deepCopy.js.map +1 -0
  177. package/dist/utils/deepEqual.d.ts +54 -0
  178. package/dist/utils/deepEqual.d.ts.map +1 -0
  179. package/dist/utils/deepEqual.js +72 -1
  180. package/dist/utils/deepEqual.js.map +1 -0
  181. package/dist/utils/export.d.ts +12 -0
  182. package/dist/utils/export.d.ts.map +1 -0
  183. package/dist/utils/export.js +1 -0
  184. package/dist/utils/export.js.map +1 -0
  185. package/dist/utils/hex.d.ts +49 -0
  186. package/dist/utils/hex.d.ts.map +1 -0
  187. package/dist/utils/hex.js +58 -0
  188. package/dist/utils/hex.js.map +1 -0
  189. package/dist/utils/isvalid.d.ts +103 -0
  190. package/dist/utils/isvalid.d.ts.map +1 -0
  191. package/dist/utils/isvalid.js +101 -0
  192. package/dist/utils/isvalid.js.map +1 -0
  193. package/dist/utils/network.d.ts +74 -0
  194. package/dist/utils/network.d.ts.map +1 -0
  195. package/dist/utils/network.js +81 -5
  196. package/dist/utils/network.js.map +1 -0
  197. package/dist/utils/spawn.d.ts +11 -0
  198. package/dist/utils/spawn.d.ts.map +1 -0
  199. package/dist/utils/spawn.js +18 -0
  200. package/dist/utils/spawn.js.map +1 -0
  201. package/dist/utils/wait.d.ts +56 -0
  202. package/dist/utils/wait.d.ts.map +1 -0
  203. package/dist/utils/wait.js +62 -9
  204. package/dist/utils/wait.js.map +1 -0
  205. package/npm-shrinkwrap.json +2 -2
  206. package/package.json +2 -1
@@ -1,15 +1,43 @@
1
+ /**
2
+ * This file contains the class Matterbridge.
3
+ *
4
+ * @file matterbridge.ts
5
+ * @author Luca Liguori
6
+ * @created 2023-12-29
7
+ * @version 1.6.0
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2023, 2024, 2025 Luca Liguori.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ // Node.js modules
1
25
  import os from 'node:os';
2
26
  import path from 'node:path';
3
27
  import { promises as fs } from 'node:fs';
4
28
  import EventEmitter from 'node:events';
5
29
  import { inspect } from 'node:util';
30
+ // AnsiLogger module
6
31
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE } from 'node-ansi-logger';
32
+ // NodeStorage module
7
33
  import { NodeStorageManager } from 'node-persist-manager';
34
+ // @matter
8
35
  import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
9
36
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
10
37
  import { AggregatorEndpoint } from '@matter/main/endpoints';
11
38
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
12
39
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
40
+ // Matterbridge
13
41
  import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
14
42
  import { dev, plg, typ } from './matterbridgeTypes.js';
15
43
  import { PluginManager } from './pluginManager.js';
@@ -18,6 +46,9 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
18
46
  import { bridge } from './matterbridgeDeviceTypes.js';
19
47
  import { Frontend } from './frontend.js';
20
48
  import { addVirtualDevices } from './helpers.js';
49
+ /**
50
+ * Represents the Matterbridge application.
51
+ */
21
52
  export class Matterbridge extends EventEmitter {
22
53
  systemInformation = {
23
54
  interfaceName: '',
@@ -66,7 +97,7 @@ export class Matterbridge extends EventEmitter {
66
97
  shellySysUpdate: false,
67
98
  shellyMainUpdate: false,
68
99
  profile: getParameter('profile'),
69
- loggerLevel: "info",
100
+ loggerLevel: "info" /* LogLevel.INFO */,
70
101
  fileLogger: false,
71
102
  matterLoggerLevel: MatterLogLevel.INFO,
72
103
  matterFileLogger: false,
@@ -94,15 +125,18 @@ export class Matterbridge extends EventEmitter {
94
125
  shutdown = false;
95
126
  edge = true;
96
127
  failCountLimit = hasParameter('shelly') ? 600 : 120;
97
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
128
+ // Matterbridge log files
129
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
98
130
  matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
99
131
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
100
132
  plugins;
101
133
  devices;
102
134
  frontend = new Frontend(this);
135
+ // Matterbridge storage
103
136
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
104
137
  nodeStorage;
105
138
  nodeContext;
139
+ // Cleanup
106
140
  hasCleanupStarted = false;
107
141
  initialized = false;
108
142
  execRunningCount = 0;
@@ -116,19 +150,23 @@ export class Matterbridge extends EventEmitter {
116
150
  sigtermHandler;
117
151
  exceptionHandler;
118
152
  rejectionHandler;
153
+ // Matter environment
119
154
  environment = Environment.default;
155
+ // Matter storage
120
156
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
121
157
  matterStorageService;
122
158
  matterStorageManager;
123
159
  matterbridgeContext;
124
160
  controllerContext;
125
- mdnsInterface;
126
- ipv4address;
127
- ipv6address;
128
- port;
129
- passcode;
130
- discriminator;
131
- certification;
161
+ // Matter parameters
162
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
163
+ ipv4address; // matter server node listeningAddressIpv4
164
+ ipv6address; // matter server node listeningAddressIpv6
165
+ port; // first server node port
166
+ passcode; // first server node passcode
167
+ discriminator; // first server node discriminator
168
+ certification; // device certification
169
+ // Matter nodes
132
170
  serverNode;
133
171
  aggregatorNode;
134
172
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
@@ -136,15 +174,31 @@ export class Matterbridge extends EventEmitter {
136
174
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
137
175
  aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
138
176
  static instance;
177
+ // We load asyncronously so is private
139
178
  constructor() {
140
179
  super();
141
180
  }
181
+ /**
182
+ * Retrieves the list of Matterbridge devices.
183
+ *
184
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
185
+ */
142
186
  getDevices() {
143
187
  return this.devices.array();
144
188
  }
189
+ /**
190
+ * Retrieves the list of registered plugins.
191
+ *
192
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
193
+ */
145
194
  getPlugins() {
146
195
  return this.plugins.array();
147
196
  }
197
+ /**
198
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
199
+ *
200
+ * @param {LogLevel} logLevel The logger logLevel to set.
201
+ */
148
202
  async setLogLevel(logLevel) {
149
203
  if (this.log)
150
204
  this.log.logLevel = logLevel;
@@ -158,19 +212,31 @@ export class Matterbridge extends EventEmitter {
158
212
  for (const plugin of this.plugins) {
159
213
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
160
214
  continue;
161
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
162
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
163
- }
164
- let callbackLogLevel = "notice";
165
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
166
- callbackLogLevel = "info";
167
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
168
- callbackLogLevel = "debug";
215
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
216
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
217
+ }
218
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
219
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
220
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
221
+ callbackLogLevel = "info" /* LogLevel.INFO */;
222
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
223
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
169
224
  AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
170
225
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
171
226
  }
227
+ //* ************************************************************************************************************************************ */
228
+ // loadInstance() and cleanup() methods */
229
+ //* ************************************************************************************************************************************ */
230
+ /**
231
+ * Loads an instance of the Matterbridge class.
232
+ * If an instance already exists, return that instance.
233
+ *
234
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
235
+ * @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
236
+ */
172
237
  static async loadInstance(initialize = false) {
173
238
  if (!Matterbridge.instance) {
239
+ // eslint-disable-next-line no-console
174
240
  if (hasParameter('debug'))
175
241
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
176
242
  Matterbridge.instance = new Matterbridge();
@@ -179,8 +245,17 @@ export class Matterbridge extends EventEmitter {
179
245
  }
180
246
  return Matterbridge.instance;
181
247
  }
248
+ /**
249
+ * Call cleanup() and dispose MdnsService.
250
+ *
251
+ * @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
252
+ * @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
253
+ *
254
+ * @deprecated This method is deprecated and is ONLY used for jest tests.
255
+ */
182
256
  async destroyInstance(timeout = 1000, pause = 250) {
183
257
  this.log.info(`Destroy instance...`);
258
+ // Save server nodes to close
184
259
  const servers = [];
185
260
  if (this.bridgeMode === 'bridge') {
186
261
  if (this.serverNode)
@@ -198,76 +273,109 @@ export class Matterbridge extends EventEmitter {
198
273
  servers.push(device.serverNode);
199
274
  }
200
275
  }
276
+ // Let any already‐queued microtasks run first
201
277
  await Promise.resolve();
278
+ // Wait for the cleanup to finish
202
279
  await new Promise((resolve) => {
203
280
  setTimeout(resolve, pause);
204
281
  });
282
+ // Cleanup
205
283
  await this.cleanup('destroying instance...', false, timeout);
284
+ // Close servers mdns service
206
285
  this.log.info(`Dispose ${servers.length} MdnsService...`);
207
286
  for (const server of servers) {
208
287
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
209
288
  this.log.info(`Closed ${server.id} MdnsService`);
210
289
  }
290
+ // Let any already‐queued microtasks run first
211
291
  await Promise.resolve();
292
+ // Wait for the cleanup to finish
212
293
  await new Promise((resolve) => {
213
294
  setTimeout(resolve, pause);
214
295
  });
215
296
  }
297
+ /**
298
+ * Initializes the Matterbridge application.
299
+ *
300
+ * @remarks
301
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
302
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
303
+ * node version, registers signal handlers, initializes storage, and parses the command line.
304
+ *
305
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
306
+ */
216
307
  async initialize() {
308
+ // Emit the initialize_started event
217
309
  this.emit('initialize_started');
310
+ // Set the restart mode
218
311
  if (hasParameter('service'))
219
312
  this.restartMode = 'service';
220
313
  if (hasParameter('docker'))
221
314
  this.restartMode = 'docker';
315
+ // Set the matterbridge home directory
222
316
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
223
317
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
224
318
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
319
+ // Set the matterbridge directory
225
320
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
226
321
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
227
322
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
228
323
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
229
324
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
325
+ // Set the matterbridge plugin directory
230
326
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
231
327
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
232
328
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
329
+ // Set the matterbridge cert directory
233
330
  this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
234
331
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
235
332
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
333
+ // Set the matterbridge root directory
236
334
  const { fileURLToPath } = await import('node:url');
237
335
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
238
336
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
239
337
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
338
+ // Setup the matter environment
240
339
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
241
340
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
242
341
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
243
342
  this.environment.vars.set('runtime.signals', false);
244
343
  this.environment.vars.set('runtime.exitcode', false);
344
+ // Register process handlers
245
345
  this.registerProcessHandlers();
346
+ // Initialize nodeStorage and nodeContext
246
347
  try {
247
348
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
248
349
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
249
350
  this.log.debug('Creating node storage context for matterbridge');
250
351
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
352
+ // TODO: Remove this code when node-persist-manager is updated
353
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
251
354
  const keys = (await this.nodeStorage?.storage.keys());
252
355
  for (const key of keys) {
253
356
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
357
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
358
  await this.nodeStorage?.storage.get(key);
255
359
  }
256
360
  const storages = await this.nodeStorage.getStorageNames();
257
361
  for (const storage of storages) {
258
362
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
259
363
  const nodeContext = await this.nodeStorage?.createStorage(storage);
364
+ // TODO: Remove this code when node-persist-manager is updated
365
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
260
366
  const keys = (await nodeContext?.storage.keys());
261
367
  keys.forEach(async (key) => {
262
368
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
263
369
  await nodeContext?.get(key);
264
370
  });
265
371
  }
372
+ // Creating a backup of the node storage since it is not corrupted
266
373
  this.log.debug('Creating node storage backup...');
267
374
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
268
375
  this.log.debug('Created node storage backup');
269
376
  }
270
377
  catch (error) {
378
+ // Restoring the backup of the node storage since it is corrupted
271
379
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
272
380
  if (hasParameter('norestore')) {
273
381
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -281,14 +389,20 @@ export class Matterbridge extends EventEmitter {
281
389
  if (!this.nodeStorage || !this.nodeContext) {
282
390
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
283
391
  }
392
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
284
393
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
394
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
285
395
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
396
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
286
397
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
398
+ // Certificate management
287
399
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
288
400
  try {
401
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
289
402
  await fs.access(pairingFilePath, fs.constants.R_OK);
290
403
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
291
404
  const pairingFileJson = JSON.parse(pairingFileContent);
405
+ // Set the vendorId, vendorName, productId and productName if they are present in the pairing file
292
406
  if (isValidNumber(pairingFileJson.vendorId))
293
407
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
294
408
  if (isValidString(pairingFileJson.vendorName, 3))
@@ -297,11 +411,14 @@ export class Matterbridge extends EventEmitter {
297
411
  this.aggregatorProductId = pairingFileJson.productId;
298
412
  if (isValidString(pairingFileJson.productName, 3))
299
413
  this.aggregatorProductName = pairingFileJson.productName;
414
+ // Override the passcode and discriminator if they are present in the pairing file
300
415
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
301
416
  this.passcode = pairingFileJson.passcode;
302
417
  this.discriminator = pairingFileJson.discriminator;
303
418
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
304
419
  }
420
+ // Set the certification if it is present in the pairing file
421
+ /* istanbul ignore next if */
305
422
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
306
423
  const hexStringToUint8Array = (hexString) => {
307
424
  const matches = hexString.match(/.{1,2}/g);
@@ -319,41 +436,44 @@ export class Matterbridge extends EventEmitter {
319
436
  catch (error) {
320
437
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
321
438
  }
439
+ // Store the passcode, discriminator and port in the node context
322
440
  await this.nodeContext.set('matterport', this.port);
323
441
  await this.nodeContext.set('matterpasscode', this.passcode);
324
442
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
325
443
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
444
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
326
445
  if (hasParameter('logger')) {
327
446
  const level = getParameter('logger');
328
447
  if (level === 'debug') {
329
- this.log.logLevel = "debug";
448
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
330
449
  }
331
450
  else if (level === 'info') {
332
- this.log.logLevel = "info";
451
+ this.log.logLevel = "info" /* LogLevel.INFO */;
333
452
  }
334
453
  else if (level === 'notice') {
335
- this.log.logLevel = "notice";
454
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
336
455
  }
337
456
  else if (level === 'warn') {
338
- this.log.logLevel = "warn";
457
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
339
458
  }
340
459
  else if (level === 'error') {
341
- this.log.logLevel = "error";
460
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
342
461
  }
343
462
  else if (level === 'fatal') {
344
- this.log.logLevel = "fatal";
463
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
345
464
  }
346
465
  else {
347
466
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
348
- this.log.logLevel = "info";
467
+ this.log.logLevel = "info" /* LogLevel.INFO */;
349
468
  }
350
469
  }
351
470
  else {
352
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
471
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
353
472
  }
354
473
  this.frontend.logLevel = this.log.logLevel;
355
474
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
356
475
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
476
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
357
477
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
358
478
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
359
479
  this.matterbridgeInformation.fileLogger = true;
@@ -362,6 +482,7 @@ export class Matterbridge extends EventEmitter {
362
482
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
363
483
  if (this.profile !== undefined)
364
484
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
485
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
365
486
  if (hasParameter('matterlogger')) {
366
487
  const level = getParameter('matterlogger');
367
488
  if (level === 'debug') {
@@ -392,7 +513,9 @@ export class Matterbridge extends EventEmitter {
392
513
  }
393
514
  Logger.format = MatterLogFormat.ANSI;
394
515
  Logger.setLogger('default', this.createMatterLogger());
516
+ // Logger.destinations.default.write = this.createMatterLogger();
395
517
  this.matterbridgeInformation.matterLoggerLevel = Logger.level;
518
+ // Create the file logger for matter.js (context: matterFileLog)
396
519
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
397
520
  this.matterbridgeInformation.matterFileLogger = true;
398
521
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -401,7 +524,9 @@ export class Matterbridge extends EventEmitter {
401
524
  });
402
525
  }
403
526
  this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
527
+ // Log network interfaces
404
528
  const networkInterfaces = os.networkInterfaces();
529
+ // console.log(`Network interfaces:`, networkInterfaces);
405
530
  const availableAddresses = Object.entries(networkInterfaces);
406
531
  const availableInterfaces = Object.keys(networkInterfaces);
407
532
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -413,6 +538,7 @@ export class Matterbridge extends EventEmitter {
413
538
  });
414
539
  }
415
540
  }
541
+ // Set the interface to use for matter server node mdnsInterface
416
542
  if (hasParameter('mdnsinterface')) {
417
543
  this.mdnsInterface = getParameter('mdnsinterface');
418
544
  }
@@ -421,6 +547,7 @@ export class Matterbridge extends EventEmitter {
421
547
  if (this.mdnsInterface === '')
422
548
  this.mdnsInterface = undefined;
423
549
  }
550
+ // Validate mdnsInterface
424
551
  if (this.mdnsInterface) {
425
552
  if (!availableInterfaces.includes(this.mdnsInterface)) {
426
553
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -433,6 +560,7 @@ export class Matterbridge extends EventEmitter {
433
560
  }
434
561
  if (this.mdnsInterface)
435
562
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
563
+ // Set the listeningAddressIpv4 for the matter commissioning server
436
564
  if (hasParameter('ipv4address')) {
437
565
  this.ipv4address = getParameter('ipv4address');
438
566
  }
@@ -441,6 +569,7 @@ export class Matterbridge extends EventEmitter {
441
569
  if (this.ipv4address === '')
442
570
  this.ipv4address = undefined;
443
571
  }
572
+ // Validate ipv4address
444
573
  if (this.ipv4address) {
445
574
  let isValid = false;
446
575
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -456,6 +585,7 @@ export class Matterbridge extends EventEmitter {
456
585
  await this.nodeContext.remove('matteripv4address');
457
586
  }
458
587
  }
588
+ // Set the listeningAddressIpv6 for the matter commissioning server
459
589
  if (hasParameter('ipv6address')) {
460
590
  this.ipv6address = getParameter('ipv6address');
461
591
  }
@@ -464,6 +594,7 @@ export class Matterbridge extends EventEmitter {
464
594
  if (this.ipv6address === '')
465
595
  this.ipv6address = undefined;
466
596
  }
597
+ // Validate ipv6address
467
598
  if (this.ipv6address) {
468
599
  let isValid = false;
469
600
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -472,6 +603,7 @@ export class Matterbridge extends EventEmitter {
472
603
  isValid = true;
473
604
  break;
474
605
  }
606
+ /* istanbul ignore next */
475
607
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
476
608
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
477
609
  isValid = true;
@@ -484,6 +616,7 @@ export class Matterbridge extends EventEmitter {
484
616
  await this.nodeContext.remove('matteripv6address');
485
617
  }
486
618
  }
619
+ // Initialize the virtual mode
487
620
  if (hasParameter('novirtual')) {
488
621
  this.matterbridgeInformation.virtualMode = 'disabled';
489
622
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -492,14 +625,19 @@ export class Matterbridge extends EventEmitter {
492
625
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
493
626
  }
494
627
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
628
+ // Initialize PluginManager
495
629
  this.plugins = new PluginManager(this);
496
630
  await this.plugins.loadFromStorage();
497
631
  this.plugins.logLevel = this.log.logLevel;
632
+ // Initialize DeviceManager
498
633
  this.devices = new DeviceManager(this);
499
634
  this.devices.logLevel = this.log.logLevel;
635
+ // Get the plugins from node storage and create the plugins node storage contexts
500
636
  for (const plugin of this.plugins) {
501
637
  const packageJson = await this.plugins.parse(plugin);
502
638
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
639
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
640
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
503
641
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
504
642
  try {
505
643
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -522,6 +660,7 @@ export class Matterbridge extends EventEmitter {
522
660
  await plugin.nodeContext.set('description', plugin.description);
523
661
  await plugin.nodeContext.set('author', plugin.author);
524
662
  }
663
+ // Log system info and create .matterbridge directory
525
664
  await this.logNodeAndSystemInfo();
526
665
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
527
666
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -529,6 +668,7 @@ export class Matterbridge extends EventEmitter {
529
668
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
530
669
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
531
670
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
671
+ // Check node version and throw error
532
672
  const minNodeVersion = 18;
533
673
  const nodeVersion = process.versions.node;
534
674
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -536,10 +676,18 @@ export class Matterbridge extends EventEmitter {
536
676
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
537
677
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
538
678
  }
679
+ // Parse command line
539
680
  await this.parseCommandLine();
681
+ // Emit the initialize_completed event
540
682
  this.emit('initialize_completed');
541
683
  this.initialized = true;
542
684
  }
685
+ /**
686
+ * Parses the command line arguments and performs the corresponding actions.
687
+ *
688
+ * @private
689
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
690
+ */
543
691
  async parseCommandLine() {
544
692
  if (hasParameter('help')) {
545
693
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -600,6 +748,19 @@ export class Matterbridge extends EventEmitter {
600
748
  }
601
749
  index++;
602
750
  }
751
+ /*
752
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
753
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
754
+ serializedRegisteredDevices?.forEach((device, index) => {
755
+ if (index !== serializedRegisteredDevices.length - 1) {
756
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
757
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
758
+ } else {
759
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
760
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
761
+ }
762
+ });
763
+ */
603
764
  this.shutdown = true;
604
765
  return;
605
766
  }
@@ -615,6 +776,7 @@ export class Matterbridge extends EventEmitter {
615
776
  }
616
777
  if (hasParameter('loginterfaces')) {
617
778
  const { logInterfaces } = await import('./utils/network.js');
779
+ // this.log.info(`${plg}Matterbridge${nf} network interfaces log`);
618
780
  logInterfaces();
619
781
  this.shutdown = true;
620
782
  return;
@@ -649,6 +811,7 @@ export class Matterbridge extends EventEmitter {
649
811
  this.shutdown = true;
650
812
  return;
651
813
  }
814
+ // Start the matter storage and create the matterbridge context
652
815
  try {
653
816
  await this.startMatterStorage();
654
817
  }
@@ -656,18 +819,21 @@ export class Matterbridge extends EventEmitter {
656
819
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
657
820
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
658
821
  }
822
+ // Clear the matterbridge context if the reset parameter is set
659
823
  if (hasParameter('reset') && getParameter('reset') === undefined) {
660
824
  this.initialized = true;
661
825
  await this.shutdownProcessAndReset();
662
826
  this.shutdown = true;
663
827
  return;
664
828
  }
829
+ // Clear matterbridge plugin context if the reset parameter is set
665
830
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
666
831
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
667
832
  const plugin = this.plugins.get(getParameter('reset'));
668
833
  if (plugin) {
669
834
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
670
835
  if (!matterStorageManager) {
836
+ /* istanbul ignore next */
671
837
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
672
838
  }
673
839
  else {
@@ -686,32 +852,39 @@ export class Matterbridge extends EventEmitter {
686
852
  this.shutdown = true;
687
853
  return;
688
854
  }
855
+ // Initialize frontend
689
856
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
690
857
  await this.frontend.start(getIntParameter('frontend'));
858
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
691
859
  clearTimeout(this.checkUpdateTimeout);
692
860
  this.checkUpdateTimeout = setTimeout(async () => {
693
861
  const { checkUpdates } = await import('./update.js');
694
862
  checkUpdates(this);
695
863
  }, 30 * 1000).unref();
864
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
696
865
  clearInterval(this.checkUpdateInterval);
697
866
  this.checkUpdateInterval = setInterval(async () => {
698
867
  const { checkUpdates } = await import('./update.js');
699
868
  checkUpdates(this);
700
869
  }, 12 * 60 * 60 * 1000).unref();
870
+ // Start the matterbridge in mode test
701
871
  if (hasParameter('test')) {
702
872
  this.bridgeMode = 'bridge';
703
873
  MatterbridgeEndpoint.bridgeMode = 'bridge';
704
874
  return;
705
875
  }
876
+ // Start the matterbridge in mode controller
706
877
  if (hasParameter('controller')) {
707
878
  this.bridgeMode = 'controller';
708
879
  await this.startController();
709
880
  return;
710
881
  }
882
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
711
883
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
712
884
  this.log.info('Setting default matterbridge start mode to bridge');
713
885
  await this.nodeContext?.set('bridgeMode', 'bridge');
714
886
  }
887
+ // Start matterbridge in bridge mode
715
888
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
716
889
  this.bridgeMode = 'bridge';
717
890
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -719,6 +892,7 @@ export class Matterbridge extends EventEmitter {
719
892
  await this.startBridge();
720
893
  return;
721
894
  }
895
+ // Start matterbridge in childbridge mode
722
896
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
723
897
  this.bridgeMode = 'childbridge';
724
898
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -727,10 +901,20 @@ export class Matterbridge extends EventEmitter {
727
901
  return;
728
902
  }
729
903
  }
904
+ /**
905
+ * Asynchronously loads and starts the registered plugins.
906
+ *
907
+ * This method is responsible for initializing and starting all enabled plugins.
908
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
909
+ *
910
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
911
+ */
730
912
  async startPlugins() {
913
+ // Check, load and start the plugins
731
914
  for (const plugin of this.plugins) {
732
915
  plugin.configJson = await this.plugins.loadConfig(plugin);
733
916
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
917
+ // Check if the plugin is available
734
918
  if (!(await this.plugins.resolve(plugin.path))) {
735
919
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
736
920
  plugin.enabled = false;
@@ -748,10 +932,14 @@ export class Matterbridge extends EventEmitter {
748
932
  plugin.configured = false;
749
933
  plugin.registeredDevices = undefined;
750
934
  plugin.addedDevices = undefined;
751
- this.plugins.load(plugin, true, 'Matterbridge is starting');
935
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
752
936
  }
753
937
  this.frontend.wssSendRefreshRequired('plugins');
754
938
  }
939
+ /**
940
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
941
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
942
+ */
755
943
  registerProcessHandlers() {
756
944
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
757
945
  process.removeAllListeners('uncaughtException');
@@ -778,6 +966,9 @@ export class Matterbridge extends EventEmitter {
778
966
  };
779
967
  process.on('SIGTERM', this.sigtermHandler);
780
968
  }
969
+ /**
970
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
971
+ */
781
972
  deregisterProcessHandlers() {
782
973
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
783
974
  if (this.exceptionHandler)
@@ -794,12 +985,17 @@ export class Matterbridge extends EventEmitter {
794
985
  process.off('SIGTERM', this.sigtermHandler);
795
986
  this.sigtermHandler = undefined;
796
987
  }
988
+ /**
989
+ * Logs the node and system information.
990
+ */
797
991
  async logNodeAndSystemInfo() {
992
+ // IP address information
798
993
  const networkInterfaces = os.networkInterfaces();
799
994
  this.systemInformation.interfaceName = '';
800
995
  this.systemInformation.ipv4Address = '';
801
996
  this.systemInformation.ipv6Address = '';
802
997
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
998
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
803
999
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
804
1000
  continue;
805
1001
  if (!interfaceDetails) {
@@ -825,19 +1021,22 @@ export class Matterbridge extends EventEmitter {
825
1021
  break;
826
1022
  }
827
1023
  }
1024
+ // Node information
828
1025
  this.systemInformation.nodeVersion = process.versions.node;
829
1026
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
830
1027
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
831
1028
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1029
+ // Host system information
832
1030
  this.systemInformation.hostname = os.hostname();
833
1031
  this.systemInformation.user = os.userInfo().username;
834
- this.systemInformation.osType = os.type();
835
- this.systemInformation.osRelease = os.release();
836
- this.systemInformation.osPlatform = os.platform();
837
- this.systemInformation.osArch = os.arch();
838
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
839
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
840
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
1032
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1033
+ this.systemInformation.osRelease = os.release(); // Kernel version
1034
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1035
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
1036
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1037
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1038
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
1039
+ // Log the system information
841
1040
  this.log.debug('Host System Information:');
842
1041
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
843
1042
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -853,14 +1052,17 @@ export class Matterbridge extends EventEmitter {
853
1052
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
854
1053
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
855
1054
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1055
+ // Log directories
856
1056
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
857
1057
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
858
1058
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
859
1059
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
860
1060
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1061
+ // Global node_modules directory
861
1062
  if (this.nodeContext)
862
1063
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
863
1064
  if (this.globalModulesDirectory === '') {
1065
+ // First run of Matterbridge so the node storage is empty
864
1066
  try {
865
1067
  const { getGlobalNodeModules } = await import('./utils/network.js');
866
1068
  this.execRunningCount++;
@@ -875,50 +1077,81 @@ export class Matterbridge extends EventEmitter {
875
1077
  }
876
1078
  else
877
1079
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1080
+ /* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
1081
+ else {
1082
+ this.getGlobalNodeModules()
1083
+ .then(async (globalModulesDirectory) => {
1084
+ this.globalModulesDirectory = globalModulesDirectory;
1085
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
1086
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1087
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
1088
+ })
1089
+ .catch((error) => {
1090
+ this.log.error(`Error getting global node_modules directory: ${error}`);
1091
+ });
1092
+ }*/
1093
+ // Matterbridge version
878
1094
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
879
1095
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
880
1096
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
881
1097
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1098
+ // Matterbridge latest version (will be set in the checkUpdate function)
882
1099
  if (this.nodeContext)
883
1100
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
884
1101
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1102
+ // Matterbridge dev version (will be set in the checkUpdate function)
885
1103
  if (this.nodeContext)
886
1104
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
887
1105
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1106
+ // Current working directory
888
1107
  const currentDir = process.cwd();
889
1108
  this.log.debug(`Current Working Directory: ${currentDir}`);
1109
+ // Command line arguments (excluding 'node' and the script name)
890
1110
  const cmdArgs = process.argv.slice(2).join(' ');
891
1111
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
892
1112
  }
1113
+ /**
1114
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1115
+ *
1116
+ * @returns {Function} The MatterLogger function.
1117
+ */
893
1118
  createMatterLogger() {
894
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1119
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
895
1120
  return (level, formattedLog) => {
896
1121
  const logger = formattedLog.slice(44, 44 + 20).trim();
897
1122
  const message = formattedLog.slice(65);
898
1123
  matterLogger.logName = logger;
899
1124
  switch (level) {
900
1125
  case MatterLogLevel.DEBUG:
901
- matterLogger.log("debug", message);
1126
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
902
1127
  break;
903
1128
  case MatterLogLevel.INFO:
904
- matterLogger.log("info", message);
1129
+ matterLogger.log("info" /* LogLevel.INFO */, message);
905
1130
  break;
906
1131
  case MatterLogLevel.NOTICE:
907
- matterLogger.log("notice", message);
1132
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
908
1133
  break;
909
1134
  case MatterLogLevel.WARN:
910
- matterLogger.log("warn", message);
1135
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
911
1136
  break;
912
1137
  case MatterLogLevel.ERROR:
913
- matterLogger.log("error", message);
1138
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
914
1139
  break;
915
1140
  case MatterLogLevel.FATAL:
916
- matterLogger.log("fatal", message);
1141
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
917
1142
  break;
918
1143
  }
919
1144
  };
920
1145
  }
1146
+ /**
1147
+ * Creates a Matter File Logger.
1148
+ *
1149
+ * @param {string} filePath - The path to the log file.
1150
+ * @param {boolean} [unlink] - Whether to unlink the log file before creating a new one.
1151
+ * @returns {Function} - A function that logs formatted messages to the log file.
1152
+ */
921
1153
  async createMatterFileLogger(filePath, unlink = false) {
1154
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
922
1155
  let fileSize = 0;
923
1156
  if (unlink) {
924
1157
  try {
@@ -929,10 +1162,12 @@ export class Matterbridge extends EventEmitter {
929
1162
  }
930
1163
  }
931
1164
  return async (level, formattedLog) => {
1165
+ /* istanbul ignore if */
932
1166
  if (fileSize > 100000000) {
933
- return;
1167
+ return; // Stop logging if the file size is greater than 100MB
934
1168
  }
935
1169
  fileSize += formattedLog.length;
1170
+ /* istanbul ignore if */
936
1171
  if (fileSize > 100000000) {
937
1172
  await fs.appendFile(filePath, `Logging on file has been stopped because the file size is greater than 100MB.` + os.EOL);
938
1173
  return;
@@ -965,12 +1200,21 @@ export class Matterbridge extends EventEmitter {
965
1200
  }
966
1201
  };
967
1202
  }
1203
+ /**
1204
+ * Restarts the process by exiting the current instance and loading a new instance.
1205
+ */
968
1206
  async restartProcess() {
969
1207
  await this.cleanup('restarting...', true);
970
1208
  }
1209
+ /**
1210
+ * Shut down the process.
1211
+ */
971
1212
  async shutdownProcess() {
972
1213
  await this.cleanup('shutting down...', false);
973
1214
  }
1215
+ /**
1216
+ * Update matterbridge and shut down the process.
1217
+ */
974
1218
  async updateProcess() {
975
1219
  this.log.info('Updating matterbridge...');
976
1220
  try {
@@ -984,52 +1228,75 @@ export class Matterbridge extends EventEmitter {
984
1228
  this.frontend.wssSendRestartRequired();
985
1229
  await this.cleanup('updating...', false);
986
1230
  }
1231
+ /**
1232
+ * Unregister all devices and shut down the process.
1233
+ */
987
1234
  async unregisterAndShutdownProcess() {
988
1235
  this.log.info('Unregistering all devices and shutting down...');
989
1236
  for (const plugin of this.plugins) {
990
1237
  await this.removeAllBridgedEndpoints(plugin.name, 250);
991
1238
  }
992
1239
  this.log.debug('Waiting for the MessageExchange to finish...');
993
- await new Promise((resolve) => setTimeout(resolve, 1000));
1240
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
994
1241
  this.log.debug('Cleaning up and shutting down...');
995
1242
  await this.cleanup('unregistered all devices and shutting down...', false);
996
1243
  }
1244
+ /**
1245
+ * Reset commissioning and shut down the process.
1246
+ */
997
1247
  async shutdownProcessAndReset() {
998
1248
  await this.cleanup('shutting down with reset...', false);
999
1249
  }
1250
+ /**
1251
+ * Factory reset and shut down the process.
1252
+ */
1000
1253
  async shutdownProcessAndFactoryReset() {
1001
1254
  await this.cleanup('shutting down with factory reset...', false);
1002
1255
  }
1256
+ /**
1257
+ * Cleans up the Matterbridge instance.
1258
+ *
1259
+ * @param {string} message - The cleanup message.
1260
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1261
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1262
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1263
+ */
1003
1264
  async cleanup(message, restart = false, timeout = 1000) {
1004
1265
  if (this.initialized && !this.hasCleanupStarted) {
1005
1266
  this.emit('cleanup_started');
1006
1267
  this.hasCleanupStarted = true;
1007
1268
  this.log.info(message);
1269
+ // Clear the start matter interval
1008
1270
  if (this.startMatterInterval) {
1009
1271
  clearInterval(this.startMatterInterval);
1010
1272
  this.startMatterInterval = undefined;
1011
1273
  this.log.debug('Start matter interval cleared');
1012
1274
  }
1275
+ // Clear the check update timeout
1013
1276
  if (this.checkUpdateTimeout) {
1014
1277
  clearTimeout(this.checkUpdateTimeout);
1015
1278
  this.checkUpdateTimeout = undefined;
1016
1279
  this.log.debug('Check update timeout cleared');
1017
1280
  }
1281
+ // Clear the check update interval
1018
1282
  if (this.checkUpdateInterval) {
1019
1283
  clearInterval(this.checkUpdateInterval);
1020
1284
  this.checkUpdateInterval = undefined;
1021
1285
  this.log.debug('Check update interval cleared');
1022
1286
  }
1287
+ // Clear the configure timeout
1023
1288
  if (this.configureTimeout) {
1024
1289
  clearTimeout(this.configureTimeout);
1025
1290
  this.configureTimeout = undefined;
1026
1291
  this.log.debug('Matterbridge configure timeout cleared');
1027
1292
  }
1293
+ // Clear the reachability timeout
1028
1294
  if (this.reachabilityTimeout) {
1029
1295
  clearTimeout(this.reachabilityTimeout);
1030
1296
  this.reachabilityTimeout = undefined;
1031
1297
  this.log.debug('Matterbridge reachability timeout cleared');
1032
1298
  }
1299
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
1033
1300
  for (const plugin of this.plugins) {
1034
1301
  if (!plugin.enabled || plugin.error)
1035
1302
  continue;
@@ -1040,9 +1307,10 @@ export class Matterbridge extends EventEmitter {
1040
1307
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1041
1308
  }
1042
1309
  }
1310
+ // Stop matter server nodes
1043
1311
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1044
1312
  this.log.debug('Waiting for the MessageExchange to finish...');
1045
- await new Promise((resolve) => setTimeout(resolve, timeout));
1313
+ await new Promise((resolve) => setTimeout(resolve, timeout)); // Wait for MessageExchange to finish
1046
1314
  if (this.bridgeMode === 'bridge') {
1047
1315
  if (this.serverNode) {
1048
1316
  await this.stopServerNode(this.serverNode);
@@ -1064,6 +1332,7 @@ export class Matterbridge extends EventEmitter {
1064
1332
  }
1065
1333
  }
1066
1334
  this.log.notice('Stopped matter server nodes');
1335
+ // Matter commisioning reset
1067
1336
  if (message === 'shutting down with reset...') {
1068
1337
  this.log.info('Resetting Matterbridge commissioning information...');
1069
1338
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1073,18 +1342,36 @@ export class Matterbridge extends EventEmitter {
1073
1342
  await this.matterbridgeContext?.clearAll();
1074
1343
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1075
1344
  }
1345
+ // Stop matter storage
1076
1346
  await this.stopMatterStorage();
1347
+ // Stop the frontend
1077
1348
  await this.frontend.stop();
1349
+ // Remove the matterfilelogger
1078
1350
  try {
1079
1351
  Logger.removeLogger('matterfilelogger');
1080
1352
  }
1081
1353
  catch (error) {
1082
1354
  this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
1083
1355
  }
1356
+ // Close the matterbridge node storage and context
1084
1357
  if (this.nodeStorage && this.nodeContext) {
1358
+ /*
1359
+ TODO: Implement serialization of registered devices in edge mode
1360
+ this.log.info('Saving registered devices...');
1361
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1362
+ this.devices.forEach(async (device) => {
1363
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1364
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1365
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1366
+ });
1367
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1368
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1369
+ */
1370
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1085
1371
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1086
1372
  await this.nodeContext.close();
1087
1373
  this.nodeContext = undefined;
1374
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1088
1375
  for (const plugin of this.plugins) {
1089
1376
  if (plugin.nodeContext) {
1090
1377
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1101,8 +1388,10 @@ export class Matterbridge extends EventEmitter {
1101
1388
  }
1102
1389
  this.plugins.clear();
1103
1390
  this.devices.clear();
1391
+ // Factory reset
1104
1392
  if (message === 'shutting down with factory reset...') {
1105
1393
  try {
1394
+ // Delete matter storage directory with its subdirectories and backup
1106
1395
  const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
1107
1396
  this.log.info(`Removing matter storage directory: ${dir}`);
1108
1397
  await fs.rm(dir, { recursive: true });
@@ -1116,6 +1405,7 @@ export class Matterbridge extends EventEmitter {
1116
1405
  }
1117
1406
  }
1118
1407
  try {
1408
+ // Delete matterbridge storage directory with its subdirectories and backup
1119
1409
  const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
1120
1410
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1121
1411
  await fs.rm(dir, { recursive: true });
@@ -1130,12 +1420,13 @@ export class Matterbridge extends EventEmitter {
1130
1420
  }
1131
1421
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1132
1422
  }
1423
+ // Deregisters the process handlers
1133
1424
  this.deregisterProcessHandlers();
1134
1425
  if (restart) {
1135
1426
  if (message === 'updating...') {
1136
1427
  this.log.info('Cleanup completed. Updating...');
1137
1428
  Matterbridge.instance = undefined;
1138
- this.emit('update');
1429
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1139
1430
  }
1140
1431
  else if (message === 'restarting...') {
1141
1432
  this.log.info('Cleanup completed. Restarting...');
@@ -1156,6 +1447,13 @@ export class Matterbridge extends EventEmitter {
1156
1447
  this.log.debug('Cleanup already started...');
1157
1448
  }
1158
1449
  }
1450
+ /**
1451
+ * Creates and configures the server node for a single not bridged device.
1452
+ *
1453
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1454
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1455
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1456
+ */
1159
1457
  async createDeviceServerNode(plugin, device) {
1160
1458
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1161
1459
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1166,6 +1464,14 @@ export class Matterbridge extends EventEmitter {
1166
1464
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1167
1465
  }
1168
1466
  }
1467
+ /**
1468
+ * Creates and configures the server node for an accessory plugin for a given device.
1469
+ *
1470
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1471
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1472
+ * @param {boolean} [start] - Whether to start the server node after adding the device. Default is `false`.
1473
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1474
+ */
1169
1475
  async createAccessoryPlugin(plugin, device, start = false) {
1170
1476
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1171
1477
  plugin.locked = true;
@@ -1179,6 +1485,12 @@ export class Matterbridge extends EventEmitter {
1179
1485
  await this.startServerNode(plugin.serverNode);
1180
1486
  }
1181
1487
  }
1488
+ /**
1489
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
1490
+ *
1491
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1492
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
1493
+ */
1182
1494
  async createDynamicPlugin(plugin) {
1183
1495
  if (!plugin.locked) {
1184
1496
  plugin.locked = true;
@@ -1189,7 +1501,14 @@ export class Matterbridge extends EventEmitter {
1189
1501
  await plugin.serverNode.add(plugin.aggregatorNode);
1190
1502
  }
1191
1503
  }
1504
+ /**
1505
+ * Starts the Matterbridge in bridge mode.
1506
+ *
1507
+ * @private
1508
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1509
+ */
1192
1510
  async startBridge() {
1511
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1193
1512
  if (!this.matterStorageManager)
1194
1513
  throw new Error('No storage manager initialized');
1195
1514
  if (!this.matterbridgeContext)
@@ -1228,13 +1547,16 @@ export class Matterbridge extends EventEmitter {
1228
1547
  clearInterval(this.startMatterInterval);
1229
1548
  this.startMatterInterval = undefined;
1230
1549
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1231
- this.startServerNode(this.serverNode);
1550
+ // Start the Matter server node
1551
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1552
+ // Start the Matter server node of single devices in mode 'server'
1232
1553
  for (const device of this.devices.array()) {
1233
1554
  if (device.mode === 'server' && device.serverNode) {
1234
1555
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1235
- this.startServerNode(device.serverNode);
1556
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1236
1557
  }
1237
1558
  }
1559
+ // Configure the plugins
1238
1560
  this.configureTimeout = setTimeout(async () => {
1239
1561
  for (const plugin of this.plugins) {
1240
1562
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1252,16 +1574,24 @@ export class Matterbridge extends EventEmitter {
1252
1574
  }
1253
1575
  this.frontend.wssSendRefreshRequired('plugins');
1254
1576
  }, 30 * 1000).unref();
1577
+ // Setting reachability to true
1255
1578
  this.reachabilityTimeout = setTimeout(() => {
1256
1579
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1257
1580
  if (this.aggregatorNode)
1258
1581
  this.setAggregatorReachability(this.aggregatorNode, true);
1259
1582
  this.frontend.wssSendRefreshRequired('reachability');
1260
1583
  }, 60 * 1000).unref();
1584
+ // Logger.get('LogServerNode').info(this.serverNode);
1261
1585
  this.emit('bridge_started');
1262
1586
  this.log.notice('Matterbridge bridge started successfully');
1263
1587
  }, 1000);
1264
1588
  }
1589
+ /**
1590
+ * Starts the Matterbridge in childbridge mode.
1591
+ *
1592
+ * @private
1593
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1594
+ */
1265
1595
  async startChildbridge() {
1266
1596
  if (!this.matterStorageManager)
1267
1597
  throw new Error('No storage manager initialized');
@@ -1298,8 +1628,9 @@ export class Matterbridge extends EventEmitter {
1298
1628
  return;
1299
1629
  clearInterval(this.startMatterInterval);
1300
1630
  this.startMatterInterval = undefined;
1301
- await new Promise((resolve) => setTimeout(resolve, 1000));
1631
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second to ensure all plugins server nodes are ready
1302
1632
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1633
+ // Configure the plugins
1303
1634
  this.configureTimeout = setTimeout(async () => {
1304
1635
  for (const plugin of this.plugins) {
1305
1636
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1336,7 +1667,9 @@ export class Matterbridge extends EventEmitter {
1336
1667
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1337
1668
  continue;
1338
1669
  }
1339
- this.startServerNode(plugin.serverNode);
1670
+ // Start the Matter server node
1671
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1672
+ // Setting reachability to true
1340
1673
  plugin.reachabilityTimeout = setTimeout(() => {
1341
1674
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggregator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
1342
1675
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1344,19 +1677,241 @@ export class Matterbridge extends EventEmitter {
1344
1677
  this.frontend.wssSendRefreshRequired('reachability');
1345
1678
  }, 60 * 1000).unref();
1346
1679
  }
1680
+ // Start the Matter server node of single devices in mode 'server'
1347
1681
  for (const device of this.devices.array()) {
1348
1682
  if (device.mode === 'server' && device.serverNode) {
1349
1683
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1350
- this.startServerNode(device.serverNode);
1684
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1351
1685
  }
1352
1686
  }
1687
+ // Logger.get('LogServerNode').info(this.serverNode);
1353
1688
  this.emit('childbridge_started');
1354
1689
  this.log.notice('Matterbridge childbridge started successfully');
1355
1690
  }, 1000);
1356
1691
  }
1692
+ /**
1693
+ * Starts the Matterbridge controller.
1694
+ *
1695
+ * @private
1696
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1697
+ */
1357
1698
  async startController() {
1699
+ /*
1700
+ if (!this.matterStorageManager) {
1701
+ this.log.error('No storage manager initialized');
1702
+ await this.cleanup('No storage manager initialized');
1703
+ return;
1704
+ }
1705
+ this.log.info('Creating context: mattercontrollerContext');
1706
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1707
+ if (!this.controllerContext) {
1708
+ this.log.error('No storage context mattercontrollerContext initialized');
1709
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1710
+ return;
1711
+ }
1712
+
1713
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1714
+ this.matterServer = await this.createMatterServer(this.storageManager);
1715
+ this.log.info('Creating matter commissioning controller');
1716
+ this.commissioningController = new CommissioningController({
1717
+ autoConnect: false,
1718
+ });
1719
+ this.log.info('Adding matter commissioning controller to matter server');
1720
+ await this.matterServer.addCommissioningController(this.commissioningController);
1721
+
1722
+ this.log.info('Starting matter server');
1723
+ await this.matterServer.start();
1724
+ this.log.info('Matter server started');
1725
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1726
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1727
+ regulatoryCountryCode: 'XX',
1728
+ };
1729
+ const commissioningController = new CommissioningController({
1730
+ environment: {
1731
+ environment,
1732
+ id: uniqueId,
1733
+ },
1734
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1735
+ adminFabricLabel,
1736
+ });
1737
+
1738
+ if (hasParameter('pairingcode')) {
1739
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1740
+ const pairingCode = getParameter('pairingcode');
1741
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1742
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1743
+
1744
+ let longDiscriminator, setupPin, shortDiscriminator;
1745
+ if (pairingCode !== undefined) {
1746
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1747
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1748
+ longDiscriminator = undefined;
1749
+ setupPin = pairingCodeCodec.passcode;
1750
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1751
+ } else {
1752
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1753
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1754
+ setupPin = this.controllerContext.get('pin', 20202021);
1755
+ }
1756
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1757
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1758
+ }
1759
+
1760
+ const options = {
1761
+ commissioning: commissioningOptions,
1762
+ discovery: {
1763
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1764
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1765
+ },
1766
+ passcode: setupPin,
1767
+ } as NodeCommissioningOptions;
1768
+ this.log.info('Commissioning with options:', options);
1769
+ const nodeId = await this.commissioningController.commissionNode(options);
1770
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1771
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1772
+ } // (hasParameter('pairingcode'))
1773
+
1774
+ if (hasParameter('unpairall')) {
1775
+ this.log.info('***Commissioning controller unpairing all nodes...');
1776
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1777
+ for (const nodeId of nodeIds) {
1778
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1779
+ await this.commissioningController.removeNode(nodeId);
1780
+ }
1781
+ return;
1782
+ }
1783
+
1784
+ if (hasParameter('discover')) {
1785
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1786
+ // console.log(discover);
1787
+ }
1788
+
1789
+ if (!this.commissioningController.isCommissioned()) {
1790
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1791
+ return;
1792
+ }
1793
+
1794
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1795
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1796
+ for (const nodeId of nodeIds) {
1797
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1798
+
1799
+ const node = await this.commissioningController.connectNode(nodeId, {
1800
+ autoSubscribe: false,
1801
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1802
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1803
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1804
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1805
+ stateInformationCallback: (peerNodeId, info) => {
1806
+ switch (info) {
1807
+ case NodeStateInformation.Connected:
1808
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1809
+ break;
1810
+ case NodeStateInformation.Disconnected:
1811
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1812
+ break;
1813
+ case NodeStateInformation.Reconnecting:
1814
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1815
+ break;
1816
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1817
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1818
+ break;
1819
+ case NodeStateInformation.StructureChanged:
1820
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1821
+ break;
1822
+ case NodeStateInformation.Decommissioned:
1823
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1824
+ break;
1825
+ default:
1826
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1827
+ break;
1828
+ }
1829
+ },
1830
+ });
1831
+
1832
+ node.logStructure();
1833
+
1834
+ // Get the interaction client
1835
+ this.log.info('Getting the interaction client');
1836
+ const interactionClient = await node.getInteractionClient();
1837
+ let cluster;
1838
+ let attributes;
1839
+
1840
+ // Log BasicInformationCluster
1841
+ cluster = BasicInformationCluster;
1842
+ attributes = await interactionClient.getMultipleAttributes({
1843
+ attributes: [{ clusterId: cluster.id }],
1844
+ });
1845
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1846
+ attributes.forEach((attribute) => {
1847
+ this.log.info(
1848
+ `- 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}`,
1849
+ );
1850
+ });
1851
+
1852
+ // Log PowerSourceCluster
1853
+ cluster = PowerSourceCluster;
1854
+ attributes = await interactionClient.getMultipleAttributes({
1855
+ attributes: [{ clusterId: cluster.id }],
1856
+ });
1857
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1858
+ attributes.forEach((attribute) => {
1859
+ this.log.info(
1860
+ `- 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}`,
1861
+ );
1862
+ });
1863
+
1864
+ // Log ThreadNetworkDiagnostics
1865
+ cluster = ThreadNetworkDiagnosticsCluster;
1866
+ attributes = await interactionClient.getMultipleAttributes({
1867
+ attributes: [{ clusterId: cluster.id }],
1868
+ });
1869
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1870
+ attributes.forEach((attribute) => {
1871
+ this.log.info(
1872
+ `- 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}`,
1873
+ );
1874
+ });
1875
+
1876
+ // Log SwitchCluster
1877
+ cluster = SwitchCluster;
1878
+ attributes = await interactionClient.getMultipleAttributes({
1879
+ attributes: [{ clusterId: cluster.id }],
1880
+ });
1881
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1882
+ attributes.forEach((attribute) => {
1883
+ this.log.info(
1884
+ `- 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}`,
1885
+ );
1886
+ });
1887
+
1888
+ this.log.info('Subscribing to all attributes and events');
1889
+ await node.subscribeAllAttributesAndEvents({
1890
+ ignoreInitialTriggers: false,
1891
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1892
+ this.log.info(
1893
+ `***${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}`,
1894
+ ),
1895
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1896
+ this.log.info(
1897
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1898
+ );
1899
+ },
1900
+ });
1901
+ this.log.info('Subscribed to all attributes and events');
1902
+ }
1903
+ */
1358
1904
  }
1905
+ /** */
1906
+ /** Matter.js methods */
1907
+ /** */
1908
+ /**
1909
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1910
+ *
1911
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1912
+ */
1359
1913
  async startMatterStorage() {
1914
+ // Setup Matter storage
1360
1915
  this.log.info(`Starting matter node storage...`);
1361
1916
  this.matterStorageService = this.environment.get(StorageService);
1362
1917
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1365,8 +1920,17 @@ export class Matterbridge extends EventEmitter {
1365
1920
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
1366
1921
  this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
1367
1922
  this.log.info('Matter node storage started');
1923
+ // Backup matter storage since it is created/opened correctly
1368
1924
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1369
1925
  }
1926
+ /**
1927
+ * Makes a backup copy of the specified matter storage directory.
1928
+ *
1929
+ * @param {string} storageName - The name of the storage directory to be backed up.
1930
+ * @param {string} backupName - The name of the backup directory to be created.
1931
+ * @private
1932
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1933
+ */
1370
1934
  async backupMatterStorage(storageName, backupName) {
1371
1935
  this.log.info('Creating matter node storage backup...');
1372
1936
  try {
@@ -1377,6 +1941,11 @@ export class Matterbridge extends EventEmitter {
1377
1941
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1378
1942
  }
1379
1943
  }
1944
+ /**
1945
+ * Stops the matter storage.
1946
+ *
1947
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1948
+ */
1380
1949
  async stopMatterStorage() {
1381
1950
  this.log.info('Closing matter node storage...');
1382
1951
  await this.matterStorageManager?.close();
@@ -1385,6 +1954,19 @@ export class Matterbridge extends EventEmitter {
1385
1954
  this.matterbridgeContext = undefined;
1386
1955
  this.log.info('Matter node storage closed');
1387
1956
  }
1957
+ /**
1958
+ * Creates a server node storage context.
1959
+ *
1960
+ * @param {string} pluginName - The name of the plugin.
1961
+ * @param {string} deviceName - The name of the device.
1962
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1963
+ * @param {number} vendorId - The vendor ID.
1964
+ * @param {string} vendorName - The vendor name.
1965
+ * @param {number} productId - The product ID.
1966
+ * @param {string} productName - The product name.
1967
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1968
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1969
+ */
1388
1970
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1389
1971
  const { randomBytes } = await import('node:crypto');
1390
1972
  if (!this.matterStorageService)
@@ -1418,6 +2000,15 @@ export class Matterbridge extends EventEmitter {
1418
2000
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1419
2001
  return storageContext;
1420
2002
  }
2003
+ /**
2004
+ * Creates a server node.
2005
+ *
2006
+ * @param {StorageContext} storageContext - The storage context for the server node.
2007
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
2008
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
2009
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
2010
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
2011
+ */
1421
2012
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1422
2013
  const storeId = await storageContext.get('storeId');
1423
2014
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1427,24 +2018,37 @@ export class Matterbridge extends EventEmitter {
1427
2018
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1428
2019
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1429
2020
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2021
+ /**
2022
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
2023
+ */
1430
2024
  const serverNode = await ServerNode.create({
2025
+ // Required: Give the Node a unique ID which is used to store the state of this node
1431
2026
  id: storeId,
2027
+ // Provide Network relevant configuration like the port
2028
+ // Optional when operating only one device on a host, Default port is 5540
1432
2029
  network: {
1433
2030
  listeningAddressIpv4: this.ipv4address,
1434
2031
  listeningAddressIpv6: this.ipv6address,
1435
2032
  port,
1436
2033
  },
2034
+ // Provide the certificate for the device
1437
2035
  operationalCredentials: {
1438
2036
  certification: this.certification,
1439
2037
  },
2038
+ // Provide Commissioning relevant settings
2039
+ // Optional for development/testing purposes
1440
2040
  commissioning: {
1441
2041
  passcode,
1442
2042
  discriminator,
1443
2043
  },
2044
+ // Provide Node announcement settings
2045
+ // Optional: If Ommitted some development defaults are used
1444
2046
  productDescription: {
1445
2047
  name: await storageContext.get('deviceName'),
1446
2048
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1447
2049
  },
2050
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
2051
+ // Optional: If Omitted some development defaults are used
1448
2052
  basicInformation: {
1449
2053
  vendorId: VendorId(await storageContext.get('vendorId')),
1450
2054
  vendorName: await storageContext.get('vendorName'),
@@ -1461,14 +2065,20 @@ export class Matterbridge extends EventEmitter {
1461
2065
  reachable: true,
1462
2066
  },
1463
2067
  });
2068
+ /**
2069
+ * This event is triggered when the device is initially commissioned successfully.
2070
+ * This means: It is added to the first fabric.
2071
+ */
1464
2072
  serverNode.lifecycle.commissioned.on(() => {
1465
2073
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1466
2074
  clearTimeout(this.endAdvertiseTimeout);
1467
2075
  });
2076
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1468
2077
  serverNode.lifecycle.decommissioned.on(() => {
1469
2078
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1470
2079
  clearTimeout(this.endAdvertiseTimeout);
1471
2080
  });
2081
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1472
2082
  serverNode.lifecycle.online.on(async () => {
1473
2083
  this.log.notice(`Server node for ${storeId} is online`);
1474
2084
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1476,6 +2086,7 @@ export class Matterbridge extends EventEmitter {
1476
2086
  const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
1477
2087
  this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
1478
2088
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
2089
+ // Set a timeout to show that advertising stops after 15 minutes if not commissioned
1479
2090
  this.startEndAdvertiseTimer(serverNode);
1480
2091
  }
1481
2092
  else {
@@ -1486,14 +2097,19 @@ export class Matterbridge extends EventEmitter {
1486
2097
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1487
2098
  this.emit('online', storeId);
1488
2099
  });
2100
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1489
2101
  serverNode.lifecycle.offline.on(() => {
1490
2102
  this.log.notice(`Server node for ${storeId} is offline`);
1491
- this.matterbridgeInformation.matterbridgeEndAdvertise = true;
2103
+ this.matterbridgeInformation.matterbridgeEndAdvertise = true; // Set the end advertise flag to true, so the frontend won't show the QR code anymore
1492
2104
  this.frontend.wssSendRefreshRequired('plugins');
1493
2105
  this.frontend.wssSendRefreshRequired('settings');
1494
2106
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1495
2107
  this.emit('offline', storeId);
1496
2108
  });
2109
+ /**
2110
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2111
+ * information is needed.
2112
+ */
1497
2113
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1498
2114
  let action = '';
1499
2115
  switch (fabricAction) {
@@ -1510,14 +2126,22 @@ export class Matterbridge extends EventEmitter {
1510
2126
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1511
2127
  this.frontend.wssSendRefreshRequired('fabrics');
1512
2128
  });
2129
+ /**
2130
+ * This event is triggered when an operative new session was opened by a Controller.
2131
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2132
+ */
1513
2133
  serverNode.events.sessions.opened.on((session) => {
1514
2134
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1515
2135
  this.frontend.wssSendRefreshRequired('sessions');
1516
2136
  });
2137
+ /**
2138
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2139
+ */
1517
2140
  serverNode.events.sessions.closed.on((session) => {
1518
2141
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1519
2142
  this.frontend.wssSendRefreshRequired('sessions');
1520
2143
  });
2144
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1521
2145
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1522
2146
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1523
2147
  this.frontend.wssSendRefreshRequired('sessions');
@@ -1525,6 +2149,11 @@ export class Matterbridge extends EventEmitter {
1525
2149
  this.log.info(`Created server node for ${storeId}`);
1526
2150
  return serverNode;
1527
2151
  }
2152
+ /**
2153
+ * Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
2154
+ *
2155
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2156
+ */
1528
2157
  startEndAdvertiseTimer(matterServerNode) {
1529
2158
  if (this.endAdvertiseTimeout) {
1530
2159
  this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
@@ -1543,12 +2172,25 @@ export class Matterbridge extends EventEmitter {
1543
2172
  this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
1544
2173
  }, 15 * 60 * 1000).unref();
1545
2174
  }
2175
+ /**
2176
+ * Starts the specified server node.
2177
+ *
2178
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2179
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2180
+ */
1546
2181
  async startServerNode(matterServerNode) {
1547
2182
  if (!matterServerNode)
1548
2183
  return;
1549
2184
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1550
2185
  await matterServerNode.start();
1551
2186
  }
2187
+ /**
2188
+ * Stops the specified server node.
2189
+ *
2190
+ * @param {ServerNode} matterServerNode - The server node to stop.
2191
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2192
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2193
+ */
1552
2194
  async stopServerNode(matterServerNode, timeout = 30000) {
1553
2195
  if (!matterServerNode)
1554
2196
  return;
@@ -1561,6 +2203,12 @@ export class Matterbridge extends EventEmitter {
1561
2203
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1562
2204
  }
1563
2205
  }
2206
+ /**
2207
+ * Advertises the specified server node.
2208
+ *
2209
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2210
+ * @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.
2211
+ */
1564
2212
  async advertiseServerNode(matterServerNode) {
1565
2213
  if (matterServerNode) {
1566
2214
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1569,19 +2217,39 @@ export class Matterbridge extends EventEmitter {
1569
2217
  return { qrPairingCode, manualPairingCode };
1570
2218
  }
1571
2219
  }
2220
+ /**
2221
+ * Stop advertise the specified server node.
2222
+ *
2223
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2224
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2225
+ */
1572
2226
  async stopAdvertiseServerNode(matterServerNode) {
1573
2227
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1574
2228
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1575
2229
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1576
2230
  }
1577
2231
  }
2232
+ /**
2233
+ * Creates an aggregator node with the specified storage context.
2234
+ *
2235
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2236
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2237
+ */
1578
2238
  async createAggregatorNode(storageContext) {
1579
2239
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1580
2240
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1581
2241
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1582
2242
  return aggregatorNode;
1583
2243
  }
2244
+ /**
2245
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2246
+ *
2247
+ * @param {string} pluginName - The name of the plugin.
2248
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2249
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2250
+ */
1584
2251
  async addBridgedEndpoint(pluginName, device) {
2252
+ // Check if the plugin is registered
1585
2253
  const plugin = this.plugins.get(pluginName);
1586
2254
  if (!plugin) {
1587
2255
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1601,6 +2269,7 @@ export class Matterbridge extends EventEmitter {
1601
2269
  }
1602
2270
  else if (this.bridgeMode === 'bridge') {
1603
2271
  if (device.mode === 'matter') {
2272
+ // Register and add the device to the matterbridge server node
1604
2273
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1605
2274
  if (!this.serverNode) {
1606
2275
  this.log.error('Server node not found for Matterbridge');
@@ -1617,6 +2286,7 @@ export class Matterbridge extends EventEmitter {
1617
2286
  }
1618
2287
  }
1619
2288
  else {
2289
+ // Register and add the device to the matterbridge aggregator node
1620
2290
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1621
2291
  if (!this.aggregatorNode) {
1622
2292
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1634,6 +2304,7 @@ export class Matterbridge extends EventEmitter {
1634
2304
  }
1635
2305
  }
1636
2306
  else if (this.bridgeMode === 'childbridge') {
2307
+ // Register and add the device to the plugin server node
1637
2308
  if (plugin.type === 'AccessoryPlatform') {
1638
2309
  try {
1639
2310
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1657,10 +2328,12 @@ export class Matterbridge extends EventEmitter {
1657
2328
  return;
1658
2329
  }
1659
2330
  }
2331
+ // Register and add the device to the plugin aggregator node
1660
2332
  if (plugin.type === 'DynamicPlatform') {
1661
2333
  try {
1662
2334
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1663
2335
  await this.createDynamicPlugin(plugin);
2336
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1664
2337
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1665
2338
  if (!plugin.aggregatorNode) {
1666
2339
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1683,17 +2356,28 @@ export class Matterbridge extends EventEmitter {
1683
2356
  plugin.registeredDevices++;
1684
2357
  if (plugin.addedDevices !== undefined)
1685
2358
  plugin.addedDevices++;
2359
+ // Add the device to the DeviceManager
1686
2360
  this.devices.set(device);
2361
+ // Subscribe to the reachable$Changed event
1687
2362
  await this.subscribeAttributeChanged(plugin, device);
1688
2363
  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}`);
1689
2364
  }
2365
+ /**
2366
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2367
+ *
2368
+ * @param {string} pluginName - The name of the plugin.
2369
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2370
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2371
+ */
1690
2372
  async removeBridgedEndpoint(pluginName, device) {
1691
2373
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2374
+ // Check if the plugin is registered
1692
2375
  const plugin = this.plugins.get(pluginName);
1693
2376
  if (!plugin) {
1694
2377
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1695
2378
  return;
1696
2379
  }
2380
+ // Register and add the device to the matterbridge aggregator node
1697
2381
  if (this.bridgeMode === 'bridge') {
1698
2382
  if (!this.aggregatorNode) {
1699
2383
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1708,6 +2392,7 @@ export class Matterbridge extends EventEmitter {
1708
2392
  }
1709
2393
  else if (this.bridgeMode === 'childbridge') {
1710
2394
  if (plugin.type === 'AccessoryPlatform') {
2395
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1711
2396
  }
1712
2397
  else if (plugin.type === 'DynamicPlatform') {
1713
2398
  if (!plugin.aggregatorNode) {
@@ -1722,8 +2407,21 @@ export class Matterbridge extends EventEmitter {
1722
2407
  if (plugin.addedDevices !== undefined)
1723
2408
  plugin.addedDevices--;
1724
2409
  }
2410
+ // Remove the device from the DeviceManager
1725
2411
  this.devices.remove(device);
1726
2412
  }
2413
+ /**
2414
+ * Removes all bridged endpoints from the specified plugin.
2415
+ *
2416
+ * @param {string} pluginName - The name of the plugin.
2417
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2418
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2419
+ *
2420
+ * @remarks
2421
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2422
+ * It also applies a delay between each removal if specified.
2423
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2424
+ */
1727
2425
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1728
2426
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1729
2427
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1734,6 +2432,15 @@ export class Matterbridge extends EventEmitter {
1734
2432
  if (delay > 0)
1735
2433
  await new Promise((resolve) => setTimeout(resolve, 2000));
1736
2434
  }
2435
+ /**
2436
+ * Subscribes to the attribute change event for the given device and plugin.
2437
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2438
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2439
+ *
2440
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2441
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2442
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2443
+ */
1737
2444
  async subscribeAttributeChanged(plugin, device) {
1738
2445
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
1739
2446
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
@@ -1749,6 +2456,12 @@ export class Matterbridge extends EventEmitter {
1749
2456
  });
1750
2457
  }
1751
2458
  }
2459
+ /**
2460
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2461
+ *
2462
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2463
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2464
+ */
1752
2465
  sanitizeFabricInformations(fabricInfo) {
1753
2466
  return fabricInfo.map((info) => {
1754
2467
  return {
@@ -1762,6 +2475,12 @@ export class Matterbridge extends EventEmitter {
1762
2475
  };
1763
2476
  });
1764
2477
  }
2478
+ /**
2479
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2480
+ *
2481
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2482
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2483
+ */
1765
2484
  sanitizeSessionInformation(sessions) {
1766
2485
  return sessions
1767
2486
  .filter((session) => session.isPeerActive)
@@ -1788,7 +2507,21 @@ export class Matterbridge extends EventEmitter {
1788
2507
  };
1789
2508
  });
1790
2509
  }
2510
+ /**
2511
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2512
+ *
2513
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2514
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2515
+ */
2516
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1791
2517
  async setAggregatorReachability(aggregatorNode, reachable) {
2518
+ /*
2519
+ for (const child of aggregatorNode.parts) {
2520
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2521
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2522
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2523
+ }
2524
+ */
1792
2525
  }
1793
2526
  getVendorIdName = (vendorId) => {
1794
2527
  if (!vendorId)
@@ -1828,10 +2561,11 @@ export class Matterbridge extends EventEmitter {
1828
2561
  case 0x1488:
1829
2562
  vendorName = '(ShortcutLabsFlic)';
1830
2563
  break;
1831
- case 65521:
2564
+ case 65521: // 0xFFF1
1832
2565
  vendorName = '(MatterTest)';
1833
2566
  break;
1834
2567
  }
1835
2568
  return vendorName;
1836
2569
  };
1837
2570
  }
2571
+ //# sourceMappingURL=matterbridge.js.map