matterbridge 3.1.3-dev-20250714-03e05f2 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +303 -0
  58. package/dist/frontend.d.ts.map +1 -0
  59. package/dist/frontend.js +423 -18
  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 +784 -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 +76 -0
  194. package/dist/utils/network.d.ts.map +1 -0
  195. package/dist/utils/network.js +83 -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 } 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) {
@@ -412,6 +537,7 @@ export class Matterbridge extends EventEmitter {
412
537
  });
413
538
  }
414
539
  }
540
+ // Set the interface to use for matter server node mdnsInterface
415
541
  if (hasParameter('mdnsinterface')) {
416
542
  this.mdnsInterface = getParameter('mdnsinterface');
417
543
  }
@@ -420,6 +546,7 @@ export class Matterbridge extends EventEmitter {
420
546
  if (this.mdnsInterface === '')
421
547
  this.mdnsInterface = undefined;
422
548
  }
549
+ // Validate mdnsInterface
423
550
  if (this.mdnsInterface) {
424
551
  if (!availableInterfaces.includes(this.mdnsInterface)) {
425
552
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -432,6 +559,7 @@ export class Matterbridge extends EventEmitter {
432
559
  }
433
560
  if (this.mdnsInterface)
434
561
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
562
+ // Set the listeningAddressIpv4 for the matter commissioning server
435
563
  if (hasParameter('ipv4address')) {
436
564
  this.ipv4address = getParameter('ipv4address');
437
565
  }
@@ -440,6 +568,7 @@ export class Matterbridge extends EventEmitter {
440
568
  if (this.ipv4address === '')
441
569
  this.ipv4address = undefined;
442
570
  }
571
+ // Validate ipv4address
443
572
  if (this.ipv4address) {
444
573
  let isValid = false;
445
574
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -455,6 +584,7 @@ export class Matterbridge extends EventEmitter {
455
584
  await this.nodeContext.remove('matteripv4address');
456
585
  }
457
586
  }
587
+ // Set the listeningAddressIpv6 for the matter commissioning server
458
588
  if (hasParameter('ipv6address')) {
459
589
  this.ipv6address = getParameter('ipv6address');
460
590
  }
@@ -463,6 +593,7 @@ export class Matterbridge extends EventEmitter {
463
593
  if (this.ipv6address === '')
464
594
  this.ipv6address = undefined;
465
595
  }
596
+ // Validate ipv6address
466
597
  if (this.ipv6address) {
467
598
  let isValid = false;
468
599
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -471,6 +602,7 @@ export class Matterbridge extends EventEmitter {
471
602
  isValid = true;
472
603
  break;
473
604
  }
605
+ /* istanbul ignore next */
474
606
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
475
607
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
476
608
  isValid = true;
@@ -483,6 +615,7 @@ export class Matterbridge extends EventEmitter {
483
615
  await this.nodeContext.remove('matteripv6address');
484
616
  }
485
617
  }
618
+ // Initialize the virtual mode
486
619
  if (hasParameter('novirtual')) {
487
620
  this.matterbridgeInformation.virtualMode = 'disabled';
488
621
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -491,14 +624,19 @@ export class Matterbridge extends EventEmitter {
491
624
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
492
625
  }
493
626
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
627
+ // Initialize PluginManager
494
628
  this.plugins = new PluginManager(this);
495
629
  await this.plugins.loadFromStorage();
496
630
  this.plugins.logLevel = this.log.logLevel;
631
+ // Initialize DeviceManager
497
632
  this.devices = new DeviceManager(this);
498
633
  this.devices.logLevel = this.log.logLevel;
634
+ // Get the plugins from node storage and create the plugins node storage contexts
499
635
  for (const plugin of this.plugins) {
500
636
  const packageJson = await this.plugins.parse(plugin);
501
637
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
638
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
639
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
502
640
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
503
641
  try {
504
642
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -521,6 +659,7 @@ export class Matterbridge extends EventEmitter {
521
659
  await plugin.nodeContext.set('description', plugin.description);
522
660
  await plugin.nodeContext.set('author', plugin.author);
523
661
  }
662
+ // Log system info and create .matterbridge directory
524
663
  await this.logNodeAndSystemInfo();
525
664
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
526
665
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -528,6 +667,7 @@ export class Matterbridge extends EventEmitter {
528
667
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
529
668
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
530
669
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
670
+ // Check node version and throw error
531
671
  const minNodeVersion = 18;
532
672
  const nodeVersion = process.versions.node;
533
673
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -535,10 +675,18 @@ export class Matterbridge extends EventEmitter {
535
675
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
536
676
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
537
677
  }
678
+ // Parse command line
538
679
  await this.parseCommandLine();
680
+ // Emit the initialize_completed event
539
681
  this.emit('initialize_completed');
540
682
  this.initialized = true;
541
683
  }
684
+ /**
685
+ * Parses the command line arguments and performs the corresponding actions.
686
+ *
687
+ * @private
688
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
689
+ */
542
690
  async parseCommandLine() {
543
691
  if (hasParameter('help')) {
544
692
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -599,6 +747,19 @@ export class Matterbridge extends EventEmitter {
599
747
  }
600
748
  index++;
601
749
  }
750
+ /*
751
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
752
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
753
+ serializedRegisteredDevices?.forEach((device, index) => {
754
+ if (index !== serializedRegisteredDevices.length - 1) {
755
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
756
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
757
+ } else {
758
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
759
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
760
+ }
761
+ });
762
+ */
602
763
  this.shutdown = true;
603
764
  return;
604
765
  }
@@ -649,6 +810,7 @@ export class Matterbridge extends EventEmitter {
649
810
  this.shutdown = true;
650
811
  return;
651
812
  }
813
+ // Start the matter storage and create the matterbridge context
652
814
  try {
653
815
  await this.startMatterStorage();
654
816
  }
@@ -656,18 +818,21 @@ export class Matterbridge extends EventEmitter {
656
818
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
657
819
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
658
820
  }
821
+ // Clear the matterbridge context if the reset parameter is set
659
822
  if (hasParameter('reset') && getParameter('reset') === undefined) {
660
823
  this.initialized = true;
661
824
  await this.shutdownProcessAndReset();
662
825
  this.shutdown = true;
663
826
  return;
664
827
  }
828
+ // Clear matterbridge plugin context if the reset parameter is set
665
829
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
666
830
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
667
831
  const plugin = this.plugins.get(getParameter('reset'));
668
832
  if (plugin) {
669
833
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
670
834
  if (!matterStorageManager) {
835
+ /* istanbul ignore next */
671
836
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
672
837
  }
673
838
  else {
@@ -686,32 +851,39 @@ export class Matterbridge extends EventEmitter {
686
851
  this.shutdown = true;
687
852
  return;
688
853
  }
854
+ // Initialize frontend
689
855
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
690
856
  await this.frontend.start(getIntParameter('frontend'));
857
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
691
858
  clearTimeout(this.checkUpdateTimeout);
692
859
  this.checkUpdateTimeout = setTimeout(async () => {
693
860
  const { checkUpdates } = await import('./update.js');
694
861
  checkUpdates(this);
695
862
  }, 30 * 1000).unref();
863
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
696
864
  clearInterval(this.checkUpdateInterval);
697
865
  this.checkUpdateInterval = setInterval(async () => {
698
866
  const { checkUpdates } = await import('./update.js');
699
867
  checkUpdates(this);
700
868
  }, 12 * 60 * 60 * 1000).unref();
869
+ // Start the matterbridge in mode test
701
870
  if (hasParameter('test')) {
702
871
  this.bridgeMode = 'bridge';
703
872
  MatterbridgeEndpoint.bridgeMode = 'bridge';
704
873
  return;
705
874
  }
875
+ // Start the matterbridge in mode controller
706
876
  if (hasParameter('controller')) {
707
877
  this.bridgeMode = 'controller';
708
878
  await this.startController();
709
879
  return;
710
880
  }
881
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
711
882
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
712
883
  this.log.info('Setting default matterbridge start mode to bridge');
713
884
  await this.nodeContext?.set('bridgeMode', 'bridge');
714
885
  }
886
+ // Start matterbridge in bridge mode
715
887
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
716
888
  this.bridgeMode = 'bridge';
717
889
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -719,6 +891,7 @@ export class Matterbridge extends EventEmitter {
719
891
  await this.startBridge();
720
892
  return;
721
893
  }
894
+ // Start matterbridge in childbridge mode
722
895
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
723
896
  this.bridgeMode = 'childbridge';
724
897
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -727,10 +900,20 @@ export class Matterbridge extends EventEmitter {
727
900
  return;
728
901
  }
729
902
  }
903
+ /**
904
+ * Asynchronously loads and starts the registered plugins.
905
+ *
906
+ * This method is responsible for initializing and starting all enabled plugins.
907
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
908
+ *
909
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
910
+ */
730
911
  async startPlugins() {
912
+ // Check, load and start the plugins
731
913
  for (const plugin of this.plugins) {
732
914
  plugin.configJson = await this.plugins.loadConfig(plugin);
733
915
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
916
+ // Check if the plugin is available
734
917
  if (!(await this.plugins.resolve(plugin.path))) {
735
918
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
736
919
  plugin.enabled = false;
@@ -748,10 +931,14 @@ export class Matterbridge extends EventEmitter {
748
931
  plugin.configured = false;
749
932
  plugin.registeredDevices = undefined;
750
933
  plugin.addedDevices = undefined;
751
- this.plugins.load(plugin, true, 'Matterbridge is starting');
934
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
752
935
  }
753
936
  this.frontend.wssSendRefreshRequired('plugins');
754
937
  }
938
+ /**
939
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
940
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
941
+ */
755
942
  registerProcessHandlers() {
756
943
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
757
944
  process.removeAllListeners('uncaughtException');
@@ -778,6 +965,9 @@ export class Matterbridge extends EventEmitter {
778
965
  };
779
966
  process.on('SIGTERM', this.sigtermHandler);
780
967
  }
968
+ /**
969
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
970
+ */
781
971
  deregisterProcessHandlers() {
782
972
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
783
973
  if (this.exceptionHandler)
@@ -794,12 +984,17 @@ export class Matterbridge extends EventEmitter {
794
984
  process.off('SIGTERM', this.sigtermHandler);
795
985
  this.sigtermHandler = undefined;
796
986
  }
987
+ /**
988
+ * Logs the node and system information.
989
+ */
797
990
  async logNodeAndSystemInfo() {
991
+ // IP address information
798
992
  const networkInterfaces = os.networkInterfaces();
799
993
  this.systemInformation.interfaceName = '';
800
994
  this.systemInformation.ipv4Address = '';
801
995
  this.systemInformation.ipv6Address = '';
802
996
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
997
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
803
998
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
804
999
  continue;
805
1000
  if (!interfaceDetails) {
@@ -825,19 +1020,22 @@ export class Matterbridge extends EventEmitter {
825
1020
  break;
826
1021
  }
827
1022
  }
1023
+ // Node information
828
1024
  this.systemInformation.nodeVersion = process.versions.node;
829
1025
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
830
1026
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
831
1027
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1028
+ // Host system information
832
1029
  this.systemInformation.hostname = os.hostname();
833
1030
  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';
1031
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1032
+ this.systemInformation.osRelease = os.release(); // Kernel version
1033
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1034
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
1035
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1036
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1037
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
1038
+ // Log the system information
841
1039
  this.log.debug('Host System Information:');
842
1040
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
843
1041
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -853,14 +1051,17 @@ export class Matterbridge extends EventEmitter {
853
1051
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
854
1052
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
855
1053
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1054
+ // Log directories
856
1055
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
857
1056
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
858
1057
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
859
1058
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
860
1059
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1060
+ // Global node_modules directory
861
1061
  if (this.nodeContext)
862
1062
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
863
1063
  if (this.globalModulesDirectory === '') {
1064
+ // First run of Matterbridge so the node storage is empty
864
1065
  try {
865
1066
  const { getGlobalNodeModules } = await import('./utils/network.js');
866
1067
  this.execRunningCount++;
@@ -875,50 +1076,81 @@ export class Matterbridge extends EventEmitter {
875
1076
  }
876
1077
  else
877
1078
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1079
+ /* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
1080
+ else {
1081
+ this.getGlobalNodeModules()
1082
+ .then(async (globalModulesDirectory) => {
1083
+ this.globalModulesDirectory = globalModulesDirectory;
1084
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
1085
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1086
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
1087
+ })
1088
+ .catch((error) => {
1089
+ this.log.error(`Error getting global node_modules directory: ${error}`);
1090
+ });
1091
+ }*/
1092
+ // Matterbridge version
878
1093
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
879
1094
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
880
1095
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
881
1096
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1097
+ // Matterbridge latest version (will be set in the checkUpdate function)
882
1098
  if (this.nodeContext)
883
1099
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
884
1100
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1101
+ // Matterbridge dev version (will be set in the checkUpdate function)
885
1102
  if (this.nodeContext)
886
1103
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
887
1104
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1105
+ // Current working directory
888
1106
  const currentDir = process.cwd();
889
1107
  this.log.debug(`Current Working Directory: ${currentDir}`);
1108
+ // Command line arguments (excluding 'node' and the script name)
890
1109
  const cmdArgs = process.argv.slice(2).join(' ');
891
1110
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
892
1111
  }
1112
+ /**
1113
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1114
+ *
1115
+ * @returns {Function} The MatterLogger function.
1116
+ */
893
1117
  createMatterLogger() {
894
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1118
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
895
1119
  return (level, formattedLog) => {
896
1120
  const logger = formattedLog.slice(44, 44 + 20).trim();
897
1121
  const message = formattedLog.slice(65);
898
1122
  matterLogger.logName = logger;
899
1123
  switch (level) {
900
1124
  case MatterLogLevel.DEBUG:
901
- matterLogger.log("debug", message);
1125
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
902
1126
  break;
903
1127
  case MatterLogLevel.INFO:
904
- matterLogger.log("info", message);
1128
+ matterLogger.log("info" /* LogLevel.INFO */, message);
905
1129
  break;
906
1130
  case MatterLogLevel.NOTICE:
907
- matterLogger.log("notice", message);
1131
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
908
1132
  break;
909
1133
  case MatterLogLevel.WARN:
910
- matterLogger.log("warn", message);
1134
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
911
1135
  break;
912
1136
  case MatterLogLevel.ERROR:
913
- matterLogger.log("error", message);
1137
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
914
1138
  break;
915
1139
  case MatterLogLevel.FATAL:
916
- matterLogger.log("fatal", message);
1140
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
917
1141
  break;
918
1142
  }
919
1143
  };
920
1144
  }
1145
+ /**
1146
+ * Creates a Matter File Logger.
1147
+ *
1148
+ * @param {string} filePath - The path to the log file.
1149
+ * @param {boolean} [unlink] - Whether to unlink the log file before creating a new one.
1150
+ * @returns {Function} - A function that logs formatted messages to the log file.
1151
+ */
921
1152
  async createMatterFileLogger(filePath, unlink = false) {
1153
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
922
1154
  let fileSize = 0;
923
1155
  if (unlink) {
924
1156
  try {
@@ -929,10 +1161,12 @@ export class Matterbridge extends EventEmitter {
929
1161
  }
930
1162
  }
931
1163
  return async (level, formattedLog) => {
1164
+ /* istanbul ignore if */
932
1165
  if (fileSize > 100000000) {
933
- return;
1166
+ return; // Stop logging if the file size is greater than 100MB
934
1167
  }
935
1168
  fileSize += formattedLog.length;
1169
+ /* istanbul ignore if */
936
1170
  if (fileSize > 100000000) {
937
1171
  await fs.appendFile(filePath, `Logging on file has been stopped because the file size is greater than 100MB.` + os.EOL);
938
1172
  return;
@@ -965,12 +1199,21 @@ export class Matterbridge extends EventEmitter {
965
1199
  }
966
1200
  };
967
1201
  }
1202
+ /**
1203
+ * Restarts the process by exiting the current instance and loading a new instance.
1204
+ */
968
1205
  async restartProcess() {
969
1206
  await this.cleanup('restarting...', true);
970
1207
  }
1208
+ /**
1209
+ * Shut down the process.
1210
+ */
971
1211
  async shutdownProcess() {
972
1212
  await this.cleanup('shutting down...', false);
973
1213
  }
1214
+ /**
1215
+ * Update matterbridge and shut down the process.
1216
+ */
974
1217
  async updateProcess() {
975
1218
  this.log.info('Updating matterbridge...');
976
1219
  try {
@@ -984,52 +1227,75 @@ export class Matterbridge extends EventEmitter {
984
1227
  this.frontend.wssSendRestartRequired();
985
1228
  await this.cleanup('updating...', false);
986
1229
  }
1230
+ /**
1231
+ * Unregister all devices and shut down the process.
1232
+ */
987
1233
  async unregisterAndShutdownProcess() {
988
1234
  this.log.info('Unregistering all devices and shutting down...');
989
1235
  for (const plugin of this.plugins) {
990
1236
  await this.removeAllBridgedEndpoints(plugin.name, 250);
991
1237
  }
992
1238
  this.log.debug('Waiting for the MessageExchange to finish...');
993
- await new Promise((resolve) => setTimeout(resolve, 1000));
1239
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
994
1240
  this.log.debug('Cleaning up and shutting down...');
995
1241
  await this.cleanup('unregistered all devices and shutting down...', false);
996
1242
  }
1243
+ /**
1244
+ * Reset commissioning and shut down the process.
1245
+ */
997
1246
  async shutdownProcessAndReset() {
998
1247
  await this.cleanup('shutting down with reset...', false);
999
1248
  }
1249
+ /**
1250
+ * Factory reset and shut down the process.
1251
+ */
1000
1252
  async shutdownProcessAndFactoryReset() {
1001
1253
  await this.cleanup('shutting down with factory reset...', false);
1002
1254
  }
1255
+ /**
1256
+ * Cleans up the Matterbridge instance.
1257
+ *
1258
+ * @param {string} message - The cleanup message.
1259
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1260
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1261
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1262
+ */
1003
1263
  async cleanup(message, restart = false, timeout = 1000) {
1004
1264
  if (this.initialized && !this.hasCleanupStarted) {
1005
1265
  this.emit('cleanup_started');
1006
1266
  this.hasCleanupStarted = true;
1007
1267
  this.log.info(message);
1268
+ // Clear the start matter interval
1008
1269
  if (this.startMatterInterval) {
1009
1270
  clearInterval(this.startMatterInterval);
1010
1271
  this.startMatterInterval = undefined;
1011
1272
  this.log.debug('Start matter interval cleared');
1012
1273
  }
1274
+ // Clear the check update timeout
1013
1275
  if (this.checkUpdateTimeout) {
1014
1276
  clearTimeout(this.checkUpdateTimeout);
1015
1277
  this.checkUpdateTimeout = undefined;
1016
1278
  this.log.debug('Check update timeout cleared');
1017
1279
  }
1280
+ // Clear the check update interval
1018
1281
  if (this.checkUpdateInterval) {
1019
1282
  clearInterval(this.checkUpdateInterval);
1020
1283
  this.checkUpdateInterval = undefined;
1021
1284
  this.log.debug('Check update interval cleared');
1022
1285
  }
1286
+ // Clear the configure timeout
1023
1287
  if (this.configureTimeout) {
1024
1288
  clearTimeout(this.configureTimeout);
1025
1289
  this.configureTimeout = undefined;
1026
1290
  this.log.debug('Matterbridge configure timeout cleared');
1027
1291
  }
1292
+ // Clear the reachability timeout
1028
1293
  if (this.reachabilityTimeout) {
1029
1294
  clearTimeout(this.reachabilityTimeout);
1030
1295
  this.reachabilityTimeout = undefined;
1031
1296
  this.log.debug('Matterbridge reachability timeout cleared');
1032
1297
  }
1298
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
1033
1299
  for (const plugin of this.plugins) {
1034
1300
  if (!plugin.enabled || plugin.error)
1035
1301
  continue;
@@ -1040,9 +1306,10 @@ export class Matterbridge extends EventEmitter {
1040
1306
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1041
1307
  }
1042
1308
  }
1309
+ // Stop matter server nodes
1043
1310
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1044
1311
  this.log.debug('Waiting for the MessageExchange to finish...');
1045
- await new Promise((resolve) => setTimeout(resolve, timeout));
1312
+ await new Promise((resolve) => setTimeout(resolve, timeout)); // Wait for MessageExchange to finish
1046
1313
  if (this.bridgeMode === 'bridge') {
1047
1314
  if (this.serverNode) {
1048
1315
  await this.stopServerNode(this.serverNode);
@@ -1064,6 +1331,7 @@ export class Matterbridge extends EventEmitter {
1064
1331
  }
1065
1332
  }
1066
1333
  this.log.notice('Stopped matter server nodes');
1334
+ // Matter commisioning reset
1067
1335
  if (message === 'shutting down with reset...') {
1068
1336
  this.log.info('Resetting Matterbridge commissioning information...');
1069
1337
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1073,18 +1341,36 @@ export class Matterbridge extends EventEmitter {
1073
1341
  await this.matterbridgeContext?.clearAll();
1074
1342
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1075
1343
  }
1344
+ // Stop matter storage
1076
1345
  await this.stopMatterStorage();
1346
+ // Stop the frontend
1077
1347
  await this.frontend.stop();
1348
+ // Remove the matterfilelogger
1078
1349
  try {
1079
1350
  Logger.removeLogger('matterfilelogger');
1080
1351
  }
1081
1352
  catch (error) {
1082
1353
  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
1354
  }
1355
+ // Close the matterbridge node storage and context
1084
1356
  if (this.nodeStorage && this.nodeContext) {
1357
+ /*
1358
+ TODO: Implement serialization of registered devices in edge mode
1359
+ this.log.info('Saving registered devices...');
1360
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1361
+ this.devices.forEach(async (device) => {
1362
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1363
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1364
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1365
+ });
1366
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1367
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1368
+ */
1369
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1085
1370
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1086
1371
  await this.nodeContext.close();
1087
1372
  this.nodeContext = undefined;
1373
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1088
1374
  for (const plugin of this.plugins) {
1089
1375
  if (plugin.nodeContext) {
1090
1376
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1101,8 +1387,10 @@ export class Matterbridge extends EventEmitter {
1101
1387
  }
1102
1388
  this.plugins.clear();
1103
1389
  this.devices.clear();
1390
+ // Factory reset
1104
1391
  if (message === 'shutting down with factory reset...') {
1105
1392
  try {
1393
+ // Delete matter storage directory with its subdirectories and backup
1106
1394
  const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
1107
1395
  this.log.info(`Removing matter storage directory: ${dir}`);
1108
1396
  await fs.rm(dir, { recursive: true });
@@ -1116,6 +1404,7 @@ export class Matterbridge extends EventEmitter {
1116
1404
  }
1117
1405
  }
1118
1406
  try {
1407
+ // Delete matterbridge storage directory with its subdirectories and backup
1119
1408
  const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
1120
1409
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1121
1410
  await fs.rm(dir, { recursive: true });
@@ -1130,12 +1419,13 @@ export class Matterbridge extends EventEmitter {
1130
1419
  }
1131
1420
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1132
1421
  }
1422
+ // Deregisters the process handlers
1133
1423
  this.deregisterProcessHandlers();
1134
1424
  if (restart) {
1135
1425
  if (message === 'updating...') {
1136
1426
  this.log.info('Cleanup completed. Updating...');
1137
1427
  Matterbridge.instance = undefined;
1138
- this.emit('update');
1428
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1139
1429
  }
1140
1430
  else if (message === 'restarting...') {
1141
1431
  this.log.info('Cleanup completed. Restarting...');
@@ -1156,6 +1446,13 @@ export class Matterbridge extends EventEmitter {
1156
1446
  this.log.debug('Cleanup already started...');
1157
1447
  }
1158
1448
  }
1449
+ /**
1450
+ * Creates and configures the server node for a single not bridged device.
1451
+ *
1452
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1453
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1454
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1455
+ */
1159
1456
  async createDeviceServerNode(plugin, device) {
1160
1457
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1161
1458
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1166,6 +1463,14 @@ export class Matterbridge extends EventEmitter {
1166
1463
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1167
1464
  }
1168
1465
  }
1466
+ /**
1467
+ * Creates and configures the server node for an accessory plugin for a given device.
1468
+ *
1469
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1470
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1471
+ * @param {boolean} [start] - Whether to start the server node after adding the device. Default is `false`.
1472
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1473
+ */
1169
1474
  async createAccessoryPlugin(plugin, device, start = false) {
1170
1475
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1171
1476
  plugin.locked = true;
@@ -1179,6 +1484,12 @@ export class Matterbridge extends EventEmitter {
1179
1484
  await this.startServerNode(plugin.serverNode);
1180
1485
  }
1181
1486
  }
1487
+ /**
1488
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
1489
+ *
1490
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1491
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
1492
+ */
1182
1493
  async createDynamicPlugin(plugin) {
1183
1494
  if (!plugin.locked) {
1184
1495
  plugin.locked = true;
@@ -1189,7 +1500,14 @@ export class Matterbridge extends EventEmitter {
1189
1500
  await plugin.serverNode.add(plugin.aggregatorNode);
1190
1501
  }
1191
1502
  }
1503
+ /**
1504
+ * Starts the Matterbridge in bridge mode.
1505
+ *
1506
+ * @private
1507
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1508
+ */
1192
1509
  async startBridge() {
1510
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1193
1511
  if (!this.matterStorageManager)
1194
1512
  throw new Error('No storage manager initialized');
1195
1513
  if (!this.matterbridgeContext)
@@ -1228,13 +1546,16 @@ export class Matterbridge extends EventEmitter {
1228
1546
  clearInterval(this.startMatterInterval);
1229
1547
  this.startMatterInterval = undefined;
1230
1548
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1231
- this.startServerNode(this.serverNode);
1549
+ // Start the Matter server node
1550
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1551
+ // Start the Matter server node of single devices in mode 'server'
1232
1552
  for (const device of this.devices.array()) {
1233
1553
  if (device.mode === 'server' && device.serverNode) {
1234
1554
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1235
- this.startServerNode(device.serverNode);
1555
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1236
1556
  }
1237
1557
  }
1558
+ // Configure the plugins
1238
1559
  this.configureTimeout = setTimeout(async () => {
1239
1560
  for (const plugin of this.plugins) {
1240
1561
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1252,16 +1573,24 @@ export class Matterbridge extends EventEmitter {
1252
1573
  }
1253
1574
  this.frontend.wssSendRefreshRequired('plugins');
1254
1575
  }, 30 * 1000).unref();
1576
+ // Setting reachability to true
1255
1577
  this.reachabilityTimeout = setTimeout(() => {
1256
1578
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1257
1579
  if (this.aggregatorNode)
1258
1580
  this.setAggregatorReachability(this.aggregatorNode, true);
1259
1581
  this.frontend.wssSendRefreshRequired('reachability');
1260
1582
  }, 60 * 1000).unref();
1583
+ // Logger.get('LogServerNode').info(this.serverNode);
1261
1584
  this.emit('bridge_started');
1262
1585
  this.log.notice('Matterbridge bridge started successfully');
1263
1586
  }, 1000);
1264
1587
  }
1588
+ /**
1589
+ * Starts the Matterbridge in childbridge mode.
1590
+ *
1591
+ * @private
1592
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1593
+ */
1265
1594
  async startChildbridge() {
1266
1595
  if (!this.matterStorageManager)
1267
1596
  throw new Error('No storage manager initialized');
@@ -1298,8 +1627,9 @@ export class Matterbridge extends EventEmitter {
1298
1627
  return;
1299
1628
  clearInterval(this.startMatterInterval);
1300
1629
  this.startMatterInterval = undefined;
1301
- await new Promise((resolve) => setTimeout(resolve, 1000));
1630
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second to ensure all plugins server nodes are ready
1302
1631
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1632
+ // Configure the plugins
1303
1633
  this.configureTimeout = setTimeout(async () => {
1304
1634
  for (const plugin of this.plugins) {
1305
1635
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1336,7 +1666,9 @@ export class Matterbridge extends EventEmitter {
1336
1666
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1337
1667
  continue;
1338
1668
  }
1339
- this.startServerNode(plugin.serverNode);
1669
+ // Start the Matter server node
1670
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1671
+ // Setting reachability to true
1340
1672
  plugin.reachabilityTimeout = setTimeout(() => {
1341
1673
  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
1674
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1344,19 +1676,241 @@ export class Matterbridge extends EventEmitter {
1344
1676
  this.frontend.wssSendRefreshRequired('reachability');
1345
1677
  }, 60 * 1000).unref();
1346
1678
  }
1679
+ // Start the Matter server node of single devices in mode 'server'
1347
1680
  for (const device of this.devices.array()) {
1348
1681
  if (device.mode === 'server' && device.serverNode) {
1349
1682
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1350
- this.startServerNode(device.serverNode);
1683
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1351
1684
  }
1352
1685
  }
1686
+ // Logger.get('LogServerNode').info(this.serverNode);
1353
1687
  this.emit('childbridge_started');
1354
1688
  this.log.notice('Matterbridge childbridge started successfully');
1355
1689
  }, 1000);
1356
1690
  }
1691
+ /**
1692
+ * Starts the Matterbridge controller.
1693
+ *
1694
+ * @private
1695
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1696
+ */
1357
1697
  async startController() {
1698
+ /*
1699
+ if (!this.matterStorageManager) {
1700
+ this.log.error('No storage manager initialized');
1701
+ await this.cleanup('No storage manager initialized');
1702
+ return;
1703
+ }
1704
+ this.log.info('Creating context: mattercontrollerContext');
1705
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1706
+ if (!this.controllerContext) {
1707
+ this.log.error('No storage context mattercontrollerContext initialized');
1708
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1709
+ return;
1710
+ }
1711
+
1712
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1713
+ this.matterServer = await this.createMatterServer(this.storageManager);
1714
+ this.log.info('Creating matter commissioning controller');
1715
+ this.commissioningController = new CommissioningController({
1716
+ autoConnect: false,
1717
+ });
1718
+ this.log.info('Adding matter commissioning controller to matter server');
1719
+ await this.matterServer.addCommissioningController(this.commissioningController);
1720
+
1721
+ this.log.info('Starting matter server');
1722
+ await this.matterServer.start();
1723
+ this.log.info('Matter server started');
1724
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1725
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1726
+ regulatoryCountryCode: 'XX',
1727
+ };
1728
+ const commissioningController = new CommissioningController({
1729
+ environment: {
1730
+ environment,
1731
+ id: uniqueId,
1732
+ },
1733
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1734
+ adminFabricLabel,
1735
+ });
1736
+
1737
+ if (hasParameter('pairingcode')) {
1738
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1739
+ const pairingCode = getParameter('pairingcode');
1740
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1741
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1742
+
1743
+ let longDiscriminator, setupPin, shortDiscriminator;
1744
+ if (pairingCode !== undefined) {
1745
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1746
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1747
+ longDiscriminator = undefined;
1748
+ setupPin = pairingCodeCodec.passcode;
1749
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1750
+ } else {
1751
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1752
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1753
+ setupPin = this.controllerContext.get('pin', 20202021);
1754
+ }
1755
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1756
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1757
+ }
1758
+
1759
+ const options = {
1760
+ commissioning: commissioningOptions,
1761
+ discovery: {
1762
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1763
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1764
+ },
1765
+ passcode: setupPin,
1766
+ } as NodeCommissioningOptions;
1767
+ this.log.info('Commissioning with options:', options);
1768
+ const nodeId = await this.commissioningController.commissionNode(options);
1769
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1770
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1771
+ } // (hasParameter('pairingcode'))
1772
+
1773
+ if (hasParameter('unpairall')) {
1774
+ this.log.info('***Commissioning controller unpairing all nodes...');
1775
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1776
+ for (const nodeId of nodeIds) {
1777
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1778
+ await this.commissioningController.removeNode(nodeId);
1779
+ }
1780
+ return;
1781
+ }
1782
+
1783
+ if (hasParameter('discover')) {
1784
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1785
+ // console.log(discover);
1786
+ }
1787
+
1788
+ if (!this.commissioningController.isCommissioned()) {
1789
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1790
+ return;
1791
+ }
1792
+
1793
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1794
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1795
+ for (const nodeId of nodeIds) {
1796
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1797
+
1798
+ const node = await this.commissioningController.connectNode(nodeId, {
1799
+ autoSubscribe: false,
1800
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1801
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1802
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1803
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1804
+ stateInformationCallback: (peerNodeId, info) => {
1805
+ switch (info) {
1806
+ case NodeStateInformation.Connected:
1807
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1808
+ break;
1809
+ case NodeStateInformation.Disconnected:
1810
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1811
+ break;
1812
+ case NodeStateInformation.Reconnecting:
1813
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1814
+ break;
1815
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1816
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1817
+ break;
1818
+ case NodeStateInformation.StructureChanged:
1819
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1820
+ break;
1821
+ case NodeStateInformation.Decommissioned:
1822
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1823
+ break;
1824
+ default:
1825
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1826
+ break;
1827
+ }
1828
+ },
1829
+ });
1830
+
1831
+ node.logStructure();
1832
+
1833
+ // Get the interaction client
1834
+ this.log.info('Getting the interaction client');
1835
+ const interactionClient = await node.getInteractionClient();
1836
+ let cluster;
1837
+ let attributes;
1838
+
1839
+ // Log BasicInformationCluster
1840
+ cluster = BasicInformationCluster;
1841
+ attributes = await interactionClient.getMultipleAttributes({
1842
+ attributes: [{ clusterId: cluster.id }],
1843
+ });
1844
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1845
+ attributes.forEach((attribute) => {
1846
+ this.log.info(
1847
+ `- 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}`,
1848
+ );
1849
+ });
1850
+
1851
+ // Log PowerSourceCluster
1852
+ cluster = PowerSourceCluster;
1853
+ attributes = await interactionClient.getMultipleAttributes({
1854
+ attributes: [{ clusterId: cluster.id }],
1855
+ });
1856
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1857
+ attributes.forEach((attribute) => {
1858
+ this.log.info(
1859
+ `- 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}`,
1860
+ );
1861
+ });
1862
+
1863
+ // Log ThreadNetworkDiagnostics
1864
+ cluster = ThreadNetworkDiagnosticsCluster;
1865
+ attributes = await interactionClient.getMultipleAttributes({
1866
+ attributes: [{ clusterId: cluster.id }],
1867
+ });
1868
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1869
+ attributes.forEach((attribute) => {
1870
+ this.log.info(
1871
+ `- 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}`,
1872
+ );
1873
+ });
1874
+
1875
+ // Log SwitchCluster
1876
+ cluster = SwitchCluster;
1877
+ attributes = await interactionClient.getMultipleAttributes({
1878
+ attributes: [{ clusterId: cluster.id }],
1879
+ });
1880
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1881
+ attributes.forEach((attribute) => {
1882
+ this.log.info(
1883
+ `- 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}`,
1884
+ );
1885
+ });
1886
+
1887
+ this.log.info('Subscribing to all attributes and events');
1888
+ await node.subscribeAllAttributesAndEvents({
1889
+ ignoreInitialTriggers: false,
1890
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1891
+ this.log.info(
1892
+ `***${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}`,
1893
+ ),
1894
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1895
+ this.log.info(
1896
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1897
+ );
1898
+ },
1899
+ });
1900
+ this.log.info('Subscribed to all attributes and events');
1901
+ }
1902
+ */
1358
1903
  }
1904
+ /** */
1905
+ /** Matter.js methods */
1906
+ /** */
1907
+ /**
1908
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1909
+ *
1910
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1911
+ */
1359
1912
  async startMatterStorage() {
1913
+ // Setup Matter storage
1360
1914
  this.log.info(`Starting matter node storage...`);
1361
1915
  this.matterStorageService = this.environment.get(StorageService);
1362
1916
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1365,8 +1919,17 @@ export class Matterbridge extends EventEmitter {
1365
1919
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
1366
1920
  this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
1367
1921
  this.log.info('Matter node storage started');
1922
+ // Backup matter storage since it is created/opened correctly
1368
1923
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1369
1924
  }
1925
+ /**
1926
+ * Makes a backup copy of the specified matter storage directory.
1927
+ *
1928
+ * @param {string} storageName - The name of the storage directory to be backed up.
1929
+ * @param {string} backupName - The name of the backup directory to be created.
1930
+ * @private
1931
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1932
+ */
1370
1933
  async backupMatterStorage(storageName, backupName) {
1371
1934
  this.log.info('Creating matter node storage backup...');
1372
1935
  try {
@@ -1377,6 +1940,11 @@ export class Matterbridge extends EventEmitter {
1377
1940
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1378
1941
  }
1379
1942
  }
1943
+ /**
1944
+ * Stops the matter storage.
1945
+ *
1946
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1947
+ */
1380
1948
  async stopMatterStorage() {
1381
1949
  this.log.info('Closing matter node storage...');
1382
1950
  await this.matterStorageManager?.close();
@@ -1385,6 +1953,19 @@ export class Matterbridge extends EventEmitter {
1385
1953
  this.matterbridgeContext = undefined;
1386
1954
  this.log.info('Matter node storage closed');
1387
1955
  }
1956
+ /**
1957
+ * Creates a server node storage context.
1958
+ *
1959
+ * @param {string} pluginName - The name of the plugin.
1960
+ * @param {string} deviceName - The name of the device.
1961
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1962
+ * @param {number} vendorId - The vendor ID.
1963
+ * @param {string} vendorName - The vendor name.
1964
+ * @param {number} productId - The product ID.
1965
+ * @param {string} productName - The product name.
1966
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1967
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1968
+ */
1388
1969
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1389
1970
  const { randomBytes } = await import('node:crypto');
1390
1971
  if (!this.matterStorageService)
@@ -1418,6 +1999,15 @@ export class Matterbridge extends EventEmitter {
1418
1999
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1419
2000
  return storageContext;
1420
2001
  }
2002
+ /**
2003
+ * Creates a server node.
2004
+ *
2005
+ * @param {StorageContext} storageContext - The storage context for the server node.
2006
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
2007
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
2008
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
2009
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
2010
+ */
1421
2011
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1422
2012
  const storeId = await storageContext.get('storeId');
1423
2013
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1427,24 +2017,37 @@ export class Matterbridge extends EventEmitter {
1427
2017
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1428
2018
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1429
2019
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2020
+ /**
2021
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
2022
+ */
1430
2023
  const serverNode = await ServerNode.create({
2024
+ // Required: Give the Node a unique ID which is used to store the state of this node
1431
2025
  id: storeId,
2026
+ // Provide Network relevant configuration like the port
2027
+ // Optional when operating only one device on a host, Default port is 5540
1432
2028
  network: {
1433
2029
  listeningAddressIpv4: this.ipv4address,
1434
2030
  listeningAddressIpv6: this.ipv6address,
1435
2031
  port,
1436
2032
  },
2033
+ // Provide the certificate for the device
1437
2034
  operationalCredentials: {
1438
2035
  certification: this.certification,
1439
2036
  },
2037
+ // Provide Commissioning relevant settings
2038
+ // Optional for development/testing purposes
1440
2039
  commissioning: {
1441
2040
  passcode,
1442
2041
  discriminator,
1443
2042
  },
2043
+ // Provide Node announcement settings
2044
+ // Optional: If Ommitted some development defaults are used
1444
2045
  productDescription: {
1445
2046
  name: await storageContext.get('deviceName'),
1446
2047
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1447
2048
  },
2049
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
2050
+ // Optional: If Omitted some development defaults are used
1448
2051
  basicInformation: {
1449
2052
  vendorId: VendorId(await storageContext.get('vendorId')),
1450
2053
  vendorName: await storageContext.get('vendorName'),
@@ -1461,14 +2064,20 @@ export class Matterbridge extends EventEmitter {
1461
2064
  reachable: true,
1462
2065
  },
1463
2066
  });
2067
+ /**
2068
+ * This event is triggered when the device is initially commissioned successfully.
2069
+ * This means: It is added to the first fabric.
2070
+ */
1464
2071
  serverNode.lifecycle.commissioned.on(() => {
1465
2072
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1466
2073
  clearTimeout(this.endAdvertiseTimeout);
1467
2074
  });
2075
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1468
2076
  serverNode.lifecycle.decommissioned.on(() => {
1469
2077
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1470
2078
  clearTimeout(this.endAdvertiseTimeout);
1471
2079
  });
2080
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1472
2081
  serverNode.lifecycle.online.on(async () => {
1473
2082
  this.log.notice(`Server node for ${storeId} is online`);
1474
2083
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1476,6 +2085,7 @@ export class Matterbridge extends EventEmitter {
1476
2085
  const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
1477
2086
  this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
1478
2087
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
2088
+ // Set a timeout to show that advertising stops after 15 minutes if not commissioned
1479
2089
  this.startEndAdvertiseTimer(serverNode);
1480
2090
  }
1481
2091
  else {
@@ -1486,14 +2096,19 @@ export class Matterbridge extends EventEmitter {
1486
2096
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1487
2097
  this.emit('online', storeId);
1488
2098
  });
2099
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1489
2100
  serverNode.lifecycle.offline.on(() => {
1490
2101
  this.log.notice(`Server node for ${storeId} is offline`);
1491
- this.matterbridgeInformation.matterbridgeEndAdvertise = true;
2102
+ this.matterbridgeInformation.matterbridgeEndAdvertise = true; // Set the end advertise flag to true, so the frontend won't show the QR code anymore
1492
2103
  this.frontend.wssSendRefreshRequired('plugins');
1493
2104
  this.frontend.wssSendRefreshRequired('settings');
1494
2105
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1495
2106
  this.emit('offline', storeId);
1496
2107
  });
2108
+ /**
2109
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2110
+ * information is needed.
2111
+ */
1497
2112
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1498
2113
  let action = '';
1499
2114
  switch (fabricAction) {
@@ -1510,14 +2125,22 @@ export class Matterbridge extends EventEmitter {
1510
2125
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1511
2126
  this.frontend.wssSendRefreshRequired('fabrics');
1512
2127
  });
2128
+ /**
2129
+ * This event is triggered when an operative new session was opened by a Controller.
2130
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2131
+ */
1513
2132
  serverNode.events.sessions.opened.on((session) => {
1514
2133
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1515
2134
  this.frontend.wssSendRefreshRequired('sessions');
1516
2135
  });
2136
+ /**
2137
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2138
+ */
1517
2139
  serverNode.events.sessions.closed.on((session) => {
1518
2140
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1519
2141
  this.frontend.wssSendRefreshRequired('sessions');
1520
2142
  });
2143
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1521
2144
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1522
2145
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1523
2146
  this.frontend.wssSendRefreshRequired('sessions');
@@ -1525,6 +2148,11 @@ export class Matterbridge extends EventEmitter {
1525
2148
  this.log.info(`Created server node for ${storeId}`);
1526
2149
  return serverNode;
1527
2150
  }
2151
+ /**
2152
+ * Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
2153
+ *
2154
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2155
+ */
1528
2156
  startEndAdvertiseTimer(matterServerNode) {
1529
2157
  if (this.endAdvertiseTimeout) {
1530
2158
  this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
@@ -1543,12 +2171,25 @@ export class Matterbridge extends EventEmitter {
1543
2171
  this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
1544
2172
  }, 15 * 60 * 1000).unref();
1545
2173
  }
2174
+ /**
2175
+ * Starts the specified server node.
2176
+ *
2177
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2178
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2179
+ */
1546
2180
  async startServerNode(matterServerNode) {
1547
2181
  if (!matterServerNode)
1548
2182
  return;
1549
2183
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1550
2184
  await matterServerNode.start();
1551
2185
  }
2186
+ /**
2187
+ * Stops the specified server node.
2188
+ *
2189
+ * @param {ServerNode} matterServerNode - The server node to stop.
2190
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2191
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2192
+ */
1552
2193
  async stopServerNode(matterServerNode, timeout = 30000) {
1553
2194
  if (!matterServerNode)
1554
2195
  return;
@@ -1561,6 +2202,12 @@ export class Matterbridge extends EventEmitter {
1561
2202
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1562
2203
  }
1563
2204
  }
2205
+ /**
2206
+ * Advertises the specified server node.
2207
+ *
2208
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2209
+ * @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.
2210
+ */
1564
2211
  async advertiseServerNode(matterServerNode) {
1565
2212
  if (matterServerNode) {
1566
2213
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1569,19 +2216,39 @@ export class Matterbridge extends EventEmitter {
1569
2216
  return { qrPairingCode, manualPairingCode };
1570
2217
  }
1571
2218
  }
2219
+ /**
2220
+ * Stop advertise the specified server node.
2221
+ *
2222
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2223
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2224
+ */
1572
2225
  async stopAdvertiseServerNode(matterServerNode) {
1573
2226
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1574
2227
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1575
2228
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1576
2229
  }
1577
2230
  }
2231
+ /**
2232
+ * Creates an aggregator node with the specified storage context.
2233
+ *
2234
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2235
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2236
+ */
1578
2237
  async createAggregatorNode(storageContext) {
1579
2238
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1580
2239
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1581
2240
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1582
2241
  return aggregatorNode;
1583
2242
  }
2243
+ /**
2244
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2245
+ *
2246
+ * @param {string} pluginName - The name of the plugin.
2247
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2248
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2249
+ */
1584
2250
  async addBridgedEndpoint(pluginName, device) {
2251
+ // Check if the plugin is registered
1585
2252
  const plugin = this.plugins.get(pluginName);
1586
2253
  if (!plugin) {
1587
2254
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1601,6 +2268,7 @@ export class Matterbridge extends EventEmitter {
1601
2268
  }
1602
2269
  else if (this.bridgeMode === 'bridge') {
1603
2270
  if (device.mode === 'matter') {
2271
+ // Register and add the device to the matterbridge server node
1604
2272
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1605
2273
  if (!this.serverNode) {
1606
2274
  this.log.error('Server node not found for Matterbridge');
@@ -1617,6 +2285,7 @@ export class Matterbridge extends EventEmitter {
1617
2285
  }
1618
2286
  }
1619
2287
  else {
2288
+ // Register and add the device to the matterbridge aggregator node
1620
2289
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1621
2290
  if (!this.aggregatorNode) {
1622
2291
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1634,6 +2303,7 @@ export class Matterbridge extends EventEmitter {
1634
2303
  }
1635
2304
  }
1636
2305
  else if (this.bridgeMode === 'childbridge') {
2306
+ // Register and add the device to the plugin server node
1637
2307
  if (plugin.type === 'AccessoryPlatform') {
1638
2308
  try {
1639
2309
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1657,10 +2327,12 @@ export class Matterbridge extends EventEmitter {
1657
2327
  return;
1658
2328
  }
1659
2329
  }
2330
+ // Register and add the device to the plugin aggregator node
1660
2331
  if (plugin.type === 'DynamicPlatform') {
1661
2332
  try {
1662
2333
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1663
2334
  await this.createDynamicPlugin(plugin);
2335
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1664
2336
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1665
2337
  if (!plugin.aggregatorNode) {
1666
2338
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1683,17 +2355,28 @@ export class Matterbridge extends EventEmitter {
1683
2355
  plugin.registeredDevices++;
1684
2356
  if (plugin.addedDevices !== undefined)
1685
2357
  plugin.addedDevices++;
2358
+ // Add the device to the DeviceManager
1686
2359
  this.devices.set(device);
2360
+ // Subscribe to the reachable$Changed event
1687
2361
  await this.subscribeAttributeChanged(plugin, device);
1688
2362
  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
2363
  }
2364
+ /**
2365
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2366
+ *
2367
+ * @param {string} pluginName - The name of the plugin.
2368
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2369
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2370
+ */
1690
2371
  async removeBridgedEndpoint(pluginName, device) {
1691
2372
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2373
+ // Check if the plugin is registered
1692
2374
  const plugin = this.plugins.get(pluginName);
1693
2375
  if (!plugin) {
1694
2376
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1695
2377
  return;
1696
2378
  }
2379
+ // Register and add the device to the matterbridge aggregator node
1697
2380
  if (this.bridgeMode === 'bridge') {
1698
2381
  if (!this.aggregatorNode) {
1699
2382
  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 +2391,7 @@ export class Matterbridge extends EventEmitter {
1708
2391
  }
1709
2392
  else if (this.bridgeMode === 'childbridge') {
1710
2393
  if (plugin.type === 'AccessoryPlatform') {
2394
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1711
2395
  }
1712
2396
  else if (plugin.type === 'DynamicPlatform') {
1713
2397
  if (!plugin.aggregatorNode) {
@@ -1722,8 +2406,21 @@ export class Matterbridge extends EventEmitter {
1722
2406
  if (plugin.addedDevices !== undefined)
1723
2407
  plugin.addedDevices--;
1724
2408
  }
2409
+ // Remove the device from the DeviceManager
1725
2410
  this.devices.remove(device);
1726
2411
  }
2412
+ /**
2413
+ * Removes all bridged endpoints from the specified plugin.
2414
+ *
2415
+ * @param {string} pluginName - The name of the plugin.
2416
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2417
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2418
+ *
2419
+ * @remarks
2420
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2421
+ * It also applies a delay between each removal if specified.
2422
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2423
+ */
1727
2424
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1728
2425
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1729
2426
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1734,6 +2431,15 @@ export class Matterbridge extends EventEmitter {
1734
2431
  if (delay > 0)
1735
2432
  await new Promise((resolve) => setTimeout(resolve, 2000));
1736
2433
  }
2434
+ /**
2435
+ * Subscribes to the attribute change event for the given device and plugin.
2436
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2437
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2438
+ *
2439
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2440
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2441
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2442
+ */
1737
2443
  async subscribeAttributeChanged(plugin, device) {
1738
2444
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
1739
2445
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
@@ -1749,6 +2455,12 @@ export class Matterbridge extends EventEmitter {
1749
2455
  });
1750
2456
  }
1751
2457
  }
2458
+ /**
2459
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2460
+ *
2461
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2462
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2463
+ */
1752
2464
  sanitizeFabricInformations(fabricInfo) {
1753
2465
  return fabricInfo.map((info) => {
1754
2466
  return {
@@ -1762,6 +2474,12 @@ export class Matterbridge extends EventEmitter {
1762
2474
  };
1763
2475
  });
1764
2476
  }
2477
+ /**
2478
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2479
+ *
2480
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2481
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2482
+ */
1765
2483
  sanitizeSessionInformation(sessions) {
1766
2484
  return sessions
1767
2485
  .filter((session) => session.isPeerActive)
@@ -1788,7 +2506,21 @@ export class Matterbridge extends EventEmitter {
1788
2506
  };
1789
2507
  });
1790
2508
  }
2509
+ /**
2510
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2511
+ *
2512
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2513
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2514
+ */
2515
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1791
2516
  async setAggregatorReachability(aggregatorNode, reachable) {
2517
+ /*
2518
+ for (const child of aggregatorNode.parts) {
2519
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2520
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2521
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2522
+ }
2523
+ */
1792
2524
  }
1793
2525
  getVendorIdName = (vendorId) => {
1794
2526
  if (!vendorId)
@@ -1828,10 +2560,11 @@ export class Matterbridge extends EventEmitter {
1828
2560
  case 0x1488:
1829
2561
  vendorName = '(ShortcutLabsFlic)';
1830
2562
  break;
1831
- case 65521:
2563
+ case 65521: // 0xFFF1
1832
2564
  vendorName = '(MatterTest)';
1833
2565
  break;
1834
2566
  }
1835
2567
  return vendorName;
1836
2568
  };
1837
2569
  }
2570
+ //# sourceMappingURL=matterbridge.js.map