matterbridge 3.1.2-dev-20250707-800ff6d → 3.1.2

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 (207) hide show
  1. package/CHANGELOG.md +0 -1
  2. package/dist/cli.d.ts +26 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +91 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cliEmitter.d.ts +34 -0
  7. package/dist/cliEmitter.d.ts.map +1 -0
  8. package/dist/cliEmitter.js +30 -0
  9. package/dist/cliEmitter.js.map +1 -0
  10. package/dist/clusters/export.d.ts +2 -0
  11. package/dist/clusters/export.d.ts.map +1 -0
  12. package/dist/clusters/export.js +2 -0
  13. package/dist/clusters/export.js.map +1 -0
  14. package/dist/defaultConfigSchema.d.ts +28 -0
  15. package/dist/defaultConfigSchema.d.ts.map +1 -0
  16. package/dist/defaultConfigSchema.js +24 -0
  17. package/dist/defaultConfigSchema.js.map +1 -0
  18. package/dist/deviceManager.d.ts +112 -0
  19. package/dist/deviceManager.d.ts.map +1 -0
  20. package/dist/deviceManager.js +94 -1
  21. package/dist/deviceManager.js.map +1 -0
  22. package/dist/devices/batteryStorage.d.ts +48 -0
  23. package/dist/devices/batteryStorage.d.ts.map +1 -0
  24. package/dist/devices/batteryStorage.js +48 -1
  25. package/dist/devices/batteryStorage.js.map +1 -0
  26. package/dist/devices/evse.d.ts +75 -0
  27. package/dist/devices/evse.d.ts.map +1 -0
  28. package/dist/devices/evse.js +74 -10
  29. package/dist/devices/evse.js.map +1 -0
  30. package/dist/devices/export.d.ts +9 -0
  31. package/dist/devices/export.d.ts.map +1 -0
  32. package/dist/devices/export.js +2 -0
  33. package/dist/devices/export.js.map +1 -0
  34. package/dist/devices/heatPump.d.ts +47 -0
  35. package/dist/devices/heatPump.d.ts.map +1 -0
  36. package/dist/devices/heatPump.js +50 -2
  37. package/dist/devices/heatPump.js.map +1 -0
  38. package/dist/devices/laundryDryer.d.ts +87 -0
  39. package/dist/devices/laundryDryer.d.ts.map +1 -0
  40. package/dist/devices/laundryDryer.js +83 -6
  41. package/dist/devices/laundryDryer.js.map +1 -0
  42. package/dist/devices/laundryWasher.d.ts +242 -0
  43. package/dist/devices/laundryWasher.d.ts.map +1 -0
  44. package/dist/devices/laundryWasher.js +91 -7
  45. package/dist/devices/laundryWasher.js.map +1 -0
  46. package/dist/devices/roboticVacuumCleaner.d.ts +110 -0
  47. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  48. package/dist/devices/roboticVacuumCleaner.js +89 -6
  49. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  50. package/dist/devices/solarPower.d.ts +40 -0
  51. package/dist/devices/solarPower.d.ts.map +1 -0
  52. package/dist/devices/solarPower.js +38 -0
  53. package/dist/devices/solarPower.js.map +1 -0
  54. package/dist/devices/waterHeater.d.ts +111 -0
  55. package/dist/devices/waterHeater.d.ts.map +1 -0
  56. package/dist/devices/waterHeater.js +82 -2
  57. package/dist/devices/waterHeater.js.map +1 -0
  58. package/dist/frontend.d.ts +303 -0
  59. package/dist/frontend.d.ts.map +1 -0
  60. package/dist/frontend.js +417 -16
  61. package/dist/frontend.js.map +1 -0
  62. package/dist/globalMatterbridge.d.ts +59 -0
  63. package/dist/globalMatterbridge.d.ts.map +1 -0
  64. package/dist/globalMatterbridge.js +47 -0
  65. package/dist/globalMatterbridge.js.map +1 -0
  66. package/dist/helpers.d.ts +48 -0
  67. package/dist/helpers.d.ts.map +1 -0
  68. package/dist/helpers.js +53 -0
  69. package/dist/helpers.js.map +1 -0
  70. package/dist/index.d.ts +33 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +30 -1
  73. package/dist/index.js.map +1 -0
  74. package/dist/logger/export.d.ts +2 -0
  75. package/dist/logger/export.d.ts.map +1 -0
  76. package/dist/logger/export.js +1 -0
  77. package/dist/logger/export.js.map +1 -0
  78. package/dist/matter/behaviors.d.ts +2 -0
  79. package/dist/matter/behaviors.d.ts.map +1 -0
  80. package/dist/matter/behaviors.js +2 -0
  81. package/dist/matter/behaviors.js.map +1 -0
  82. package/dist/matter/clusters.d.ts +2 -0
  83. package/dist/matter/clusters.d.ts.map +1 -0
  84. package/dist/matter/clusters.js +2 -0
  85. package/dist/matter/clusters.js.map +1 -0
  86. package/dist/matter/devices.d.ts +2 -0
  87. package/dist/matter/devices.d.ts.map +1 -0
  88. package/dist/matter/devices.js +2 -0
  89. package/dist/matter/devices.js.map +1 -0
  90. package/dist/matter/endpoints.d.ts +2 -0
  91. package/dist/matter/endpoints.d.ts.map +1 -0
  92. package/dist/matter/endpoints.js +2 -0
  93. package/dist/matter/endpoints.js.map +1 -0
  94. package/dist/matter/export.d.ts +5 -0
  95. package/dist/matter/export.d.ts.map +1 -0
  96. package/dist/matter/export.js +3 -0
  97. package/dist/matter/export.js.map +1 -0
  98. package/dist/matter/types.d.ts +3 -0
  99. package/dist/matter/types.d.ts.map +1 -0
  100. package/dist/matter/types.js +3 -0
  101. package/dist/matter/types.js.map +1 -0
  102. package/dist/matterbridge.d.ts +450 -0
  103. package/dist/matterbridge.d.ts.map +1 -0
  104. package/dist/matterbridge.js +802 -50
  105. package/dist/matterbridge.js.map +1 -0
  106. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  107. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  108. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  109. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  110. package/dist/matterbridgeBehaviors.d.ts +1340 -0
  111. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  112. package/dist/matterbridgeBehaviors.js +61 -1
  113. package/dist/matterbridgeBehaviors.js.map +1 -0
  114. package/dist/matterbridgeDeviceTypes.d.ts +709 -0
  115. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  116. package/dist/matterbridgeDeviceTypes.js +579 -15
  117. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  118. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  119. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  120. package/dist/matterbridgeDynamicPlatform.js +36 -0
  121. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  122. package/dist/matterbridgeEndpoint.d.ts +1196 -0
  123. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  124. package/dist/matterbridgeEndpoint.js +1053 -42
  125. package/dist/matterbridgeEndpoint.js.map +1 -0
  126. package/dist/matterbridgeEndpointHelpers.d.ts +3198 -0
  127. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  128. package/dist/matterbridgeEndpointHelpers.js +322 -12
  129. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  130. package/dist/matterbridgePlatform.d.ts +310 -0
  131. package/dist/matterbridgePlatform.d.ts.map +1 -0
  132. package/dist/matterbridgePlatform.js +233 -0
  133. package/dist/matterbridgePlatform.js.map +1 -0
  134. package/dist/matterbridgeTypes.d.ts +192 -0
  135. package/dist/matterbridgeTypes.d.ts.map +1 -0
  136. package/dist/matterbridgeTypes.js +25 -0
  137. package/dist/matterbridgeTypes.js.map +1 -0
  138. package/dist/pluginManager.d.ts +291 -0
  139. package/dist/pluginManager.d.ts.map +1 -0
  140. package/dist/pluginManager.js +269 -3
  141. package/dist/pluginManager.js.map +1 -0
  142. package/dist/shelly.d.ts +174 -0
  143. package/dist/shelly.d.ts.map +1 -0
  144. package/dist/shelly.js +168 -7
  145. package/dist/shelly.js.map +1 -0
  146. package/dist/storage/export.d.ts +2 -0
  147. package/dist/storage/export.d.ts.map +1 -0
  148. package/dist/storage/export.js +1 -0
  149. package/dist/storage/export.js.map +1 -0
  150. package/dist/update.d.ts +59 -0
  151. package/dist/update.d.ts.map +1 -0
  152. package/dist/update.js +54 -0
  153. package/dist/update.js.map +1 -0
  154. package/dist/utils/colorUtils.d.ts +117 -0
  155. package/dist/utils/colorUtils.d.ts.map +1 -0
  156. package/dist/utils/colorUtils.js +263 -2
  157. package/dist/utils/colorUtils.js.map +1 -0
  158. package/dist/utils/commandLine.d.ts +59 -0
  159. package/dist/utils/commandLine.d.ts.map +1 -0
  160. package/dist/utils/commandLine.js +54 -0
  161. package/dist/utils/commandLine.js.map +1 -0
  162. package/dist/utils/copyDirectory.d.ts +33 -0
  163. package/dist/utils/copyDirectory.d.ts.map +1 -0
  164. package/dist/utils/copyDirectory.js +38 -1
  165. package/dist/utils/copyDirectory.js.map +1 -0
  166. package/dist/utils/createDirectory.d.ts +34 -0
  167. package/dist/utils/createDirectory.d.ts.map +1 -0
  168. package/dist/utils/createDirectory.js +33 -0
  169. package/dist/utils/createDirectory.js.map +1 -0
  170. package/dist/utils/createZip.d.ts +39 -0
  171. package/dist/utils/createZip.d.ts.map +1 -0
  172. package/dist/utils/createZip.js +47 -2
  173. package/dist/utils/createZip.js.map +1 -0
  174. package/dist/utils/deepCopy.d.ts +32 -0
  175. package/dist/utils/deepCopy.d.ts.map +1 -0
  176. package/dist/utils/deepCopy.js +39 -0
  177. package/dist/utils/deepCopy.js.map +1 -0
  178. package/dist/utils/deepEqual.d.ts +54 -0
  179. package/dist/utils/deepEqual.d.ts.map +1 -0
  180. package/dist/utils/deepEqual.js +72 -1
  181. package/dist/utils/deepEqual.js.map +1 -0
  182. package/dist/utils/export.d.ts +12 -0
  183. package/dist/utils/export.d.ts.map +1 -0
  184. package/dist/utils/export.js +1 -0
  185. package/dist/utils/export.js.map +1 -0
  186. package/dist/utils/hex.d.ts +49 -0
  187. package/dist/utils/hex.d.ts.map +1 -0
  188. package/dist/utils/hex.js +58 -0
  189. package/dist/utils/hex.js.map +1 -0
  190. package/dist/utils/isvalid.d.ts +103 -0
  191. package/dist/utils/isvalid.d.ts.map +1 -0
  192. package/dist/utils/isvalid.js +101 -0
  193. package/dist/utils/isvalid.js.map +1 -0
  194. package/dist/utils/network.d.ts +76 -0
  195. package/dist/utils/network.d.ts.map +1 -0
  196. package/dist/utils/network.js +83 -5
  197. package/dist/utils/network.js.map +1 -0
  198. package/dist/utils/spawn.d.ts +11 -0
  199. package/dist/utils/spawn.d.ts.map +1 -0
  200. package/dist/utils/spawn.js +18 -0
  201. package/dist/utils/spawn.js.map +1 -0
  202. package/dist/utils/wait.d.ts +56 -0
  203. package/dist/utils/wait.d.ts.map +1 -0
  204. package/dist/utils/wait.js +62 -9
  205. package/dist/utils/wait.js.map +1 -0
  206. package/npm-shrinkwrap.json +2 -2
  207. 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: '',
@@ -65,7 +96,7 @@ export class Matterbridge extends EventEmitter {
65
96
  shellySysUpdate: false,
66
97
  shellyMainUpdate: false,
67
98
  profile: getParameter('profile'),
68
- loggerLevel: "info",
99
+ loggerLevel: "info" /* LogLevel.INFO */,
69
100
  fileLogger: false,
70
101
  matterLoggerLevel: MatterLogLevel.INFO,
71
102
  matterFileLogger: false,
@@ -98,15 +129,18 @@ export class Matterbridge extends EventEmitter {
98
129
  shutdown = false;
99
130
  edge = true;
100
131
  failCountLimit = hasParameter('shelly') ? 600 : 120;
101
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
132
+ // Matterbridge log files
133
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
102
134
  matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
103
135
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
104
136
  plugins;
105
137
  devices;
106
138
  frontend = new Frontend(this);
139
+ // Matterbridge storage
107
140
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
108
141
  nodeStorage;
109
142
  nodeContext;
143
+ // Cleanup
110
144
  hasCleanupStarted = false;
111
145
  initialized = false;
112
146
  execRunningCount = 0;
@@ -120,19 +154,23 @@ export class Matterbridge extends EventEmitter {
120
154
  sigtermHandler;
121
155
  exceptionHandler;
122
156
  rejectionHandler;
157
+ // Matter environment
123
158
  environment = Environment.default;
159
+ // Matter storage
124
160
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
125
161
  matterStorageService;
126
162
  matterStorageManager;
127
163
  matterbridgeContext;
128
164
  controllerContext;
129
- mdnsInterface;
130
- ipv4address;
131
- ipv6address;
132
- port;
133
- passcode;
134
- discriminator;
135
- certification;
165
+ // Matter parameters
166
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
167
+ ipv4address; // matter server node listeningAddressIpv4
168
+ ipv6address; // matter server node listeningAddressIpv6
169
+ port; // first server node port
170
+ passcode; // first server node passcode
171
+ discriminator; // first server node discriminator
172
+ certification; // device certification
173
+ // Matter nodes
136
174
  serverNode;
137
175
  aggregatorNode;
138
176
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
@@ -140,15 +178,31 @@ export class Matterbridge extends EventEmitter {
140
178
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
141
179
  aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
142
180
  static instance;
181
+ // We load asyncronously so is private
143
182
  constructor() {
144
183
  super();
145
184
  }
185
+ /**
186
+ * Retrieves the list of Matterbridge devices.
187
+ *
188
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
189
+ */
146
190
  getDevices() {
147
191
  return this.devices.array();
148
192
  }
193
+ /**
194
+ * Retrieves the list of registered plugins.
195
+ *
196
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
197
+ */
149
198
  getPlugins() {
150
199
  return this.plugins.array();
151
200
  }
201
+ /**
202
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
203
+ *
204
+ * @param {LogLevel} logLevel The logger logLevel to set.
205
+ */
152
206
  async setLogLevel(logLevel) {
153
207
  if (this.log)
154
208
  this.log.logLevel = logLevel;
@@ -162,19 +216,31 @@ export class Matterbridge extends EventEmitter {
162
216
  for (const plugin of this.plugins) {
163
217
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
164
218
  continue;
165
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
166
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
167
- }
168
- let callbackLogLevel = "notice";
169
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
170
- callbackLogLevel = "info";
171
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
172
- callbackLogLevel = "debug";
219
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
220
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
221
+ }
222
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
223
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
224
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
225
+ callbackLogLevel = "info" /* LogLevel.INFO */;
226
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
227
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
173
228
  AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
174
229
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
175
230
  }
231
+ //* ************************************************************************************************************************************ */
232
+ // loadInstance() and cleanup() methods */
233
+ //* ************************************************************************************************************************************ */
234
+ /**
235
+ * Loads an instance of the Matterbridge class.
236
+ * If an instance already exists, return that instance.
237
+ *
238
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
239
+ * @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
240
+ */
176
241
  static async loadInstance(initialize = false) {
177
242
  if (!Matterbridge.instance) {
243
+ // eslint-disable-next-line no-console
178
244
  if (hasParameter('debug'))
179
245
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
180
246
  Matterbridge.instance = new Matterbridge();
@@ -183,8 +249,17 @@ export class Matterbridge extends EventEmitter {
183
249
  }
184
250
  return Matterbridge.instance;
185
251
  }
252
+ /**
253
+ * Call cleanup() and dispose MdnsService.
254
+ *
255
+ * @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
256
+ * @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
257
+ *
258
+ * @deprecated This method is deprecated and is ONLY used for jest tests.
259
+ */
186
260
  async destroyInstance(timeout = 1000, pause = 250) {
187
261
  this.log.info(`Destroy instance...`);
262
+ // Save server nodes to close
188
263
  const servers = [];
189
264
  if (this.bridgeMode === 'bridge') {
190
265
  if (this.serverNode)
@@ -202,76 +277,109 @@ export class Matterbridge extends EventEmitter {
202
277
  servers.push(device.serverNode);
203
278
  }
204
279
  }
280
+ // Let any already‐queued microtasks run first
205
281
  await Promise.resolve();
282
+ // Wait for the cleanup to finish
206
283
  await new Promise((resolve) => {
207
284
  setTimeout(resolve, pause);
208
285
  });
286
+ // Cleanup
209
287
  await this.cleanup('destroying instance...', false, timeout);
288
+ // Close servers mdns service
210
289
  this.log.info(`Dispose ${servers.length} MdnsService...`);
211
290
  for (const server of servers) {
212
291
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
213
292
  this.log.info(`Closed ${server.id} MdnsService`);
214
293
  }
294
+ // Let any already‐queued microtasks run first
215
295
  await Promise.resolve();
296
+ // Wait for the cleanup to finish
216
297
  await new Promise((resolve) => {
217
298
  setTimeout(resolve, pause);
218
299
  });
219
300
  }
301
+ /**
302
+ * Initializes the Matterbridge application.
303
+ *
304
+ * @remarks
305
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
306
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
307
+ * node version, registers signal handlers, initializes storage, and parses the command line.
308
+ *
309
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
310
+ */
220
311
  async initialize() {
312
+ // Emit the initialize_started event
221
313
  this.emit('initialize_started');
314
+ // Set the restart mode
222
315
  if (hasParameter('service'))
223
316
  this.restartMode = 'service';
224
317
  if (hasParameter('docker'))
225
318
  this.restartMode = 'docker';
319
+ // Set the matterbridge home directory
226
320
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
227
321
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
228
322
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
323
+ // Set the matterbridge directory
229
324
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
230
325
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
231
326
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
232
327
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
233
328
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
329
+ // Set the matterbridge plugin directory
234
330
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
235
331
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
236
332
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
333
+ // Set the matterbridge cert directory
237
334
  this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
238
335
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
239
336
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
337
+ // Set the matterbridge root directory
240
338
  const { fileURLToPath } = await import('node:url');
241
339
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
242
340
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
243
341
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
342
+ // Setup the matter environment
244
343
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
245
344
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
246
345
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
247
346
  this.environment.vars.set('runtime.signals', false);
248
347
  this.environment.vars.set('runtime.exitcode', false);
348
+ // Register process handlers
249
349
  this.registerProcessHandlers();
350
+ // Initialize nodeStorage and nodeContext
250
351
  try {
251
352
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
252
353
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
253
354
  this.log.debug('Creating node storage context for matterbridge');
254
355
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
356
+ // TODO: Remove this code when node-persist-manager is updated
357
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
255
358
  const keys = (await this.nodeStorage?.storage.keys());
256
359
  for (const key of keys) {
257
360
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
361
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
258
362
  await this.nodeStorage?.storage.get(key);
259
363
  }
260
364
  const storages = await this.nodeStorage.getStorageNames();
261
365
  for (const storage of storages) {
262
366
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
263
367
  const nodeContext = await this.nodeStorage?.createStorage(storage);
368
+ // TODO: Remove this code when node-persist-manager is updated
369
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
264
370
  const keys = (await nodeContext?.storage.keys());
265
371
  keys.forEach(async (key) => {
266
372
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
267
373
  await nodeContext?.get(key);
268
374
  });
269
375
  }
376
+ // Creating a backup of the node storage since it is not corrupted
270
377
  this.log.debug('Creating node storage backup...');
271
378
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
272
379
  this.log.debug('Created node storage backup');
273
380
  }
274
381
  catch (error) {
382
+ // Restoring the backup of the node storage since it is corrupted
275
383
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
276
384
  if (hasParameter('norestore')) {
277
385
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -285,14 +393,20 @@ export class Matterbridge extends EventEmitter {
285
393
  if (!this.nodeStorage || !this.nodeContext) {
286
394
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
287
395
  }
396
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
288
397
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
398
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
289
399
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
400
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
290
401
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
402
+ // Certificate management
291
403
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
292
404
  try {
405
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
293
406
  await fs.access(pairingFilePath, fs.constants.R_OK);
294
407
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
295
408
  const pairingFileJson = JSON.parse(pairingFileContent);
409
+ // Set the vendorId, vendorName, productId and productName if they are present in the pairing file
296
410
  if (isValidNumber(pairingFileJson.vendorId))
297
411
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
298
412
  if (isValidString(pairingFileJson.vendorName, 3))
@@ -301,50 +415,73 @@ export class Matterbridge extends EventEmitter {
301
415
  this.aggregatorProductId = pairingFileJson.productId;
302
416
  if (isValidString(pairingFileJson.productName, 3))
303
417
  this.aggregatorProductName = pairingFileJson.productName;
418
+ // Override the passcode and discriminator if they are present in the pairing file
304
419
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
305
420
  this.passcode = pairingFileJson.passcode;
306
421
  this.discriminator = pairingFileJson.discriminator;
307
422
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
308
423
  }
424
+ // Set the certification if it is present in the pairing file
425
+ /*
426
+ if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
427
+ const hexStringToUint8Array = (hexString: string) => {
428
+ const matches = hexString.match(/.{1,2}/g);
429
+ return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
430
+ };
431
+ // const hexString = Buffer.from('Test string', 'utf-8').toString('hex');
432
+ // console.log(hexString, Buffer.from(hexStringToUint8Array(hexString)).toString('utf-8'));
433
+
434
+ this.certification = {
435
+ privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
436
+ certificate: hexStringToUint8Array(pairingFileJson.certificate),
437
+ intermediateCertificate: hexStringToUint8Array(pairingFileJson.intermediateCertificate),
438
+ declaration: hexStringToUint8Array(pairingFileJson.declaration),
439
+ };
440
+ this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using privateKey, certificate, intermediateCertificate and declaration from pairing file.`);
441
+ }
442
+ */
309
443
  }
310
444
  catch (error) {
311
445
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
312
446
  }
447
+ // Store the passcode, discriminator and port in the node context
313
448
  await this.nodeContext.set('matterport', this.port);
314
449
  await this.nodeContext.set('matterpasscode', this.passcode);
315
450
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
316
451
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
452
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
317
453
  if (hasParameter('logger')) {
318
454
  const level = getParameter('logger');
319
455
  if (level === 'debug') {
320
- this.log.logLevel = "debug";
456
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
321
457
  }
322
458
  else if (level === 'info') {
323
- this.log.logLevel = "info";
459
+ this.log.logLevel = "info" /* LogLevel.INFO */;
324
460
  }
325
461
  else if (level === 'notice') {
326
- this.log.logLevel = "notice";
462
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
327
463
  }
328
464
  else if (level === 'warn') {
329
- this.log.logLevel = "warn";
465
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
330
466
  }
331
467
  else if (level === 'error') {
332
- this.log.logLevel = "error";
468
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
333
469
  }
334
470
  else if (level === 'fatal') {
335
- this.log.logLevel = "fatal";
471
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
336
472
  }
337
473
  else {
338
474
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
339
- this.log.logLevel = "info";
475
+ this.log.logLevel = "info" /* LogLevel.INFO */;
340
476
  }
341
477
  }
342
478
  else {
343
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
479
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
344
480
  }
345
481
  this.frontend.logLevel = this.log.logLevel;
346
482
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
347
483
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
484
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
348
485
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
349
486
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
350
487
  this.matterbridgeInformation.fileLogger = true;
@@ -353,6 +490,7 @@ export class Matterbridge extends EventEmitter {
353
490
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
354
491
  if (this.profile !== undefined)
355
492
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
493
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
356
494
  if (hasParameter('matterlogger')) {
357
495
  const level = getParameter('matterlogger');
358
496
  if (level === 'debug') {
@@ -383,7 +521,9 @@ export class Matterbridge extends EventEmitter {
383
521
  }
384
522
  Logger.format = MatterLogFormat.ANSI;
385
523
  Logger.setLogger('default', this.createMatterLogger());
524
+ // Logger.destinations.default.write = this.createMatterLogger();
386
525
  this.matterbridgeInformation.matterLoggerLevel = Logger.level;
526
+ // Create the file logger for matter.js (context: matterFileLog)
387
527
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
388
528
  this.matterbridgeInformation.matterFileLogger = true;
389
529
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -392,7 +532,9 @@ export class Matterbridge extends EventEmitter {
392
532
  });
393
533
  }
394
534
  this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
535
+ // Log network interfaces
395
536
  const networkInterfaces = os.networkInterfaces();
537
+ // console.log(`Network interfaces:`, networkInterfaces);
396
538
  const availableAddresses = Object.entries(networkInterfaces);
397
539
  const availableInterfaces = Object.keys(networkInterfaces);
398
540
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -403,6 +545,7 @@ export class Matterbridge extends EventEmitter {
403
545
  });
404
546
  }
405
547
  }
548
+ // Set the interface to use for matter server node mdnsInterface
406
549
  if (hasParameter('mdnsinterface')) {
407
550
  this.mdnsInterface = getParameter('mdnsinterface');
408
551
  }
@@ -411,6 +554,7 @@ export class Matterbridge extends EventEmitter {
411
554
  if (this.mdnsInterface === '')
412
555
  this.mdnsInterface = undefined;
413
556
  }
557
+ // Validate mdnsInterface
414
558
  if (this.mdnsInterface) {
415
559
  if (!availableInterfaces.includes(this.mdnsInterface)) {
416
560
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -423,6 +567,7 @@ export class Matterbridge extends EventEmitter {
423
567
  }
424
568
  if (this.mdnsInterface)
425
569
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
570
+ // Set the listeningAddressIpv4 for the matter commissioning server
426
571
  if (hasParameter('ipv4address')) {
427
572
  this.ipv4address = getParameter('ipv4address');
428
573
  }
@@ -431,6 +576,7 @@ export class Matterbridge extends EventEmitter {
431
576
  if (this.ipv4address === '')
432
577
  this.ipv4address = undefined;
433
578
  }
579
+ // Validate ipv4address
434
580
  if (this.ipv4address) {
435
581
  let isValid = false;
436
582
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -446,6 +592,7 @@ export class Matterbridge extends EventEmitter {
446
592
  await this.nodeContext.remove('matteripv4address');
447
593
  }
448
594
  }
595
+ // Set the listeningAddressIpv6 for the matter commissioning server
449
596
  if (hasParameter('ipv6address')) {
450
597
  this.ipv6address = getParameter('ipv6address');
451
598
  }
@@ -454,6 +601,7 @@ export class Matterbridge extends EventEmitter {
454
601
  if (this.ipv6address === '')
455
602
  this.ipv6address = undefined;
456
603
  }
604
+ // Validate ipv6address
457
605
  if (this.ipv6address) {
458
606
  let isValid = false;
459
607
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -462,6 +610,7 @@ export class Matterbridge extends EventEmitter {
462
610
  isValid = true;
463
611
  break;
464
612
  }
613
+ /* istanbul ignore next */
465
614
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
466
615
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
467
616
  isValid = true;
@@ -474,6 +623,7 @@ export class Matterbridge extends EventEmitter {
474
623
  await this.nodeContext.remove('matteripv6address');
475
624
  }
476
625
  }
626
+ // Initialize the virtual mode
477
627
  if (hasParameter('novirtual')) {
478
628
  this.matterbridgeInformation.virtualMode = 'disabled';
479
629
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -482,14 +632,19 @@ export class Matterbridge extends EventEmitter {
482
632
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
483
633
  }
484
634
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
635
+ // Initialize PluginManager
485
636
  this.plugins = new PluginManager(this);
486
637
  await this.plugins.loadFromStorage();
487
638
  this.plugins.logLevel = this.log.logLevel;
639
+ // Initialize DeviceManager
488
640
  this.devices = new DeviceManager(this);
489
641
  this.devices.logLevel = this.log.logLevel;
642
+ // Get the plugins from node storage and create the plugins node storage contexts
490
643
  for (const plugin of this.plugins) {
491
644
  const packageJson = await this.plugins.parse(plugin);
492
645
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
646
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
647
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
493
648
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
494
649
  try {
495
650
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -512,6 +667,7 @@ export class Matterbridge extends EventEmitter {
512
667
  await plugin.nodeContext.set('description', plugin.description);
513
668
  await plugin.nodeContext.set('author', plugin.author);
514
669
  }
670
+ // Log system info and create .matterbridge directory
515
671
  await this.logNodeAndSystemInfo();
516
672
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
517
673
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -519,6 +675,7 @@ export class Matterbridge extends EventEmitter {
519
675
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
520
676
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
521
677
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
678
+ // Check node version and throw error
522
679
  const minNodeVersion = 18;
523
680
  const nodeVersion = process.versions.node;
524
681
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -526,10 +683,18 @@ export class Matterbridge extends EventEmitter {
526
683
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
527
684
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
528
685
  }
686
+ // Parse command line
529
687
  await this.parseCommandLine();
688
+ // Emit the initialize_completed event
530
689
  this.emit('initialize_completed');
531
690
  this.initialized = true;
532
691
  }
692
+ /**
693
+ * Parses the command line arguments and performs the corresponding actions.
694
+ *
695
+ * @private
696
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
697
+ */
533
698
  async parseCommandLine() {
534
699
  if (hasParameter('help')) {
535
700
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -590,6 +755,19 @@ export class Matterbridge extends EventEmitter {
590
755
  }
591
756
  index++;
592
757
  }
758
+ /*
759
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
760
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
761
+ serializedRegisteredDevices?.forEach((device, index) => {
762
+ if (index !== serializedRegisteredDevices.length - 1) {
763
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
764
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
765
+ } else {
766
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
767
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
768
+ }
769
+ });
770
+ */
593
771
  this.shutdown = true;
594
772
  return;
595
773
  }
@@ -640,6 +818,7 @@ export class Matterbridge extends EventEmitter {
640
818
  this.shutdown = true;
641
819
  return;
642
820
  }
821
+ // Start the matter storage and create the matterbridge context
643
822
  try {
644
823
  await this.startMatterStorage();
645
824
  }
@@ -647,18 +826,21 @@ export class Matterbridge extends EventEmitter {
647
826
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
648
827
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
649
828
  }
829
+ // Clear the matterbridge context if the reset parameter is set
650
830
  if (hasParameter('reset') && getParameter('reset') === undefined) {
651
831
  this.initialized = true;
652
832
  await this.shutdownProcessAndReset();
653
833
  this.shutdown = true;
654
834
  return;
655
835
  }
836
+ // Clear matterbridge plugin context if the reset parameter is set
656
837
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
657
838
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
658
839
  const plugin = this.plugins.get(getParameter('reset'));
659
840
  if (plugin) {
660
841
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
661
842
  if (!matterStorageManager) {
843
+ /* istanbul ignore next */
662
844
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
663
845
  }
664
846
  else {
@@ -677,32 +859,39 @@ export class Matterbridge extends EventEmitter {
677
859
  this.shutdown = true;
678
860
  return;
679
861
  }
862
+ // Initialize frontend
680
863
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
681
864
  await this.frontend.start(getIntParameter('frontend'));
865
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
682
866
  clearTimeout(this.checkUpdateTimeout);
683
867
  this.checkUpdateTimeout = setTimeout(async () => {
684
868
  const { checkUpdates } = await import('./update.js');
685
869
  checkUpdates(this);
686
870
  }, 30 * 1000).unref();
871
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
687
872
  clearInterval(this.checkUpdateInterval);
688
873
  this.checkUpdateInterval = setInterval(async () => {
689
874
  const { checkUpdates } = await import('./update.js');
690
875
  checkUpdates(this);
691
876
  }, 12 * 60 * 60 * 1000).unref();
877
+ // Start the matterbridge in mode test
692
878
  if (hasParameter('test')) {
693
879
  this.bridgeMode = 'bridge';
694
880
  MatterbridgeEndpoint.bridgeMode = 'bridge';
695
881
  return;
696
882
  }
883
+ // Start the matterbridge in mode controller
697
884
  if (hasParameter('controller')) {
698
885
  this.bridgeMode = 'controller';
699
886
  await this.startController();
700
887
  return;
701
888
  }
889
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
702
890
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
703
891
  this.log.info('Setting default matterbridge start mode to bridge');
704
892
  await this.nodeContext?.set('bridgeMode', 'bridge');
705
893
  }
894
+ // Start matterbridge in bridge mode
706
895
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
707
896
  this.bridgeMode = 'bridge';
708
897
  MatterbridgeEndpoint.bridgeMode = 'bridge';
@@ -710,6 +899,7 @@ export class Matterbridge extends EventEmitter {
710
899
  await this.startBridge();
711
900
  return;
712
901
  }
902
+ // Start matterbridge in childbridge mode
713
903
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
714
904
  this.bridgeMode = 'childbridge';
715
905
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
@@ -718,10 +908,20 @@ export class Matterbridge extends EventEmitter {
718
908
  return;
719
909
  }
720
910
  }
911
+ /**
912
+ * Asynchronously loads and starts the registered plugins.
913
+ *
914
+ * This method is responsible for initializing and starting all enabled plugins.
915
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
916
+ *
917
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
918
+ */
721
919
  async startPlugins() {
920
+ // Check, load and start the plugins
722
921
  for (const plugin of this.plugins) {
723
922
  plugin.configJson = await this.plugins.loadConfig(plugin);
724
923
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
924
+ // Check if the plugin is available
725
925
  if (!(await this.plugins.resolve(plugin.path))) {
726
926
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
727
927
  plugin.enabled = false;
@@ -741,10 +941,14 @@ export class Matterbridge extends EventEmitter {
741
941
  plugin.addedDevices = undefined;
742
942
  plugin.qrPairingCode = undefined;
743
943
  plugin.manualPairingCode = undefined;
744
- this.plugins.load(plugin, true, 'Matterbridge is starting');
944
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
745
945
  }
746
946
  this.frontend.wssSendRefreshRequired('plugins');
747
947
  }
948
+ /**
949
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
950
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
951
+ */
748
952
  registerProcessHandlers() {
749
953
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
750
954
  process.removeAllListeners('uncaughtException');
@@ -771,6 +975,9 @@ export class Matterbridge extends EventEmitter {
771
975
  };
772
976
  process.on('SIGTERM', this.sigtermHandler);
773
977
  }
978
+ /**
979
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
980
+ */
774
981
  deregisterProcessHandlers() {
775
982
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
776
983
  if (this.exceptionHandler)
@@ -787,12 +994,17 @@ export class Matterbridge extends EventEmitter {
787
994
  process.off('SIGTERM', this.sigtermHandler);
788
995
  this.sigtermHandler = undefined;
789
996
  }
997
+ /**
998
+ * Logs the node and system information.
999
+ */
790
1000
  async logNodeAndSystemInfo() {
1001
+ // IP address information
791
1002
  const networkInterfaces = os.networkInterfaces();
792
1003
  this.systemInformation.interfaceName = '';
793
1004
  this.systemInformation.ipv4Address = '';
794
1005
  this.systemInformation.ipv6Address = '';
795
1006
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
1007
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
796
1008
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
797
1009
  continue;
798
1010
  if (!interfaceDetails) {
@@ -818,19 +1030,22 @@ export class Matterbridge extends EventEmitter {
818
1030
  break;
819
1031
  }
820
1032
  }
1033
+ // Node information
821
1034
  this.systemInformation.nodeVersion = process.versions.node;
822
1035
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
823
1036
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
824
1037
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1038
+ // Host system information
825
1039
  this.systemInformation.hostname = os.hostname();
826
1040
  this.systemInformation.user = os.userInfo().username;
827
- this.systemInformation.osType = os.type();
828
- this.systemInformation.osRelease = os.release();
829
- this.systemInformation.osPlatform = os.platform();
830
- this.systemInformation.osArch = os.arch();
831
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
832
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
833
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
1041
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1042
+ this.systemInformation.osRelease = os.release(); // Kernel version
1043
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1044
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
1045
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1046
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1047
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
1048
+ // Log the system information
834
1049
  this.log.debug('Host System Information:');
835
1050
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
836
1051
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -846,14 +1061,17 @@ export class Matterbridge extends EventEmitter {
846
1061
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
847
1062
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
848
1063
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1064
+ // Log directories
849
1065
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
850
1066
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
851
1067
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
852
1068
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
853
1069
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1070
+ // Global node_modules directory
854
1071
  if (this.nodeContext)
855
1072
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
856
1073
  if (this.globalModulesDirectory === '') {
1074
+ // First run of Matterbridge so the node storage is empty
857
1075
  try {
858
1076
  const { getGlobalNodeModules } = await import('./utils/network.js');
859
1077
  this.execRunningCount++;
@@ -868,50 +1086,81 @@ export class Matterbridge extends EventEmitter {
868
1086
  }
869
1087
  else
870
1088
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1089
+ /* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
1090
+ else {
1091
+ this.getGlobalNodeModules()
1092
+ .then(async (globalModulesDirectory) => {
1093
+ this.globalModulesDirectory = globalModulesDirectory;
1094
+ this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
1095
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
1096
+ await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
1097
+ })
1098
+ .catch((error) => {
1099
+ this.log.error(`Error getting global node_modules directory: ${error}`);
1100
+ });
1101
+ }*/
1102
+ // Matterbridge version
871
1103
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
872
1104
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
873
1105
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
874
1106
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1107
+ // Matterbridge latest version (will be set in the checkUpdate function)
875
1108
  if (this.nodeContext)
876
1109
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
877
1110
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1111
+ // Matterbridge dev version (will be set in the checkUpdate function)
878
1112
  if (this.nodeContext)
879
1113
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
880
1114
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1115
+ // Current working directory
881
1116
  const currentDir = process.cwd();
882
1117
  this.log.debug(`Current Working Directory: ${currentDir}`);
1118
+ // Command line arguments (excluding 'node' and the script name)
883
1119
  const cmdArgs = process.argv.slice(2).join(' ');
884
1120
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
885
1121
  }
1122
+ /**
1123
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1124
+ *
1125
+ * @returns {Function} The MatterLogger function.
1126
+ */
886
1127
  createMatterLogger() {
887
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1128
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
888
1129
  return (level, formattedLog) => {
889
1130
  const logger = formattedLog.slice(44, 44 + 20).trim();
890
1131
  const message = formattedLog.slice(65);
891
1132
  matterLogger.logName = logger;
892
1133
  switch (level) {
893
1134
  case MatterLogLevel.DEBUG:
894
- matterLogger.log("debug", message);
1135
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
895
1136
  break;
896
1137
  case MatterLogLevel.INFO:
897
- matterLogger.log("info", message);
1138
+ matterLogger.log("info" /* LogLevel.INFO */, message);
898
1139
  break;
899
1140
  case MatterLogLevel.NOTICE:
900
- matterLogger.log("notice", message);
1141
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
901
1142
  break;
902
1143
  case MatterLogLevel.WARN:
903
- matterLogger.log("warn", message);
1144
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
904
1145
  break;
905
1146
  case MatterLogLevel.ERROR:
906
- matterLogger.log("error", message);
1147
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
907
1148
  break;
908
1149
  case MatterLogLevel.FATAL:
909
- matterLogger.log("fatal", message);
1150
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
910
1151
  break;
911
1152
  }
912
1153
  };
913
1154
  }
1155
+ /**
1156
+ * Creates a Matter File Logger.
1157
+ *
1158
+ * @param {string} filePath - The path to the log file.
1159
+ * @param {boolean} [unlink] - Whether to unlink the log file before creating a new one.
1160
+ * @returns {Function} - A function that logs formatted messages to the log file.
1161
+ */
914
1162
  async createMatterFileLogger(filePath, unlink = false) {
1163
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
915
1164
  let fileSize = 0;
916
1165
  if (unlink) {
917
1166
  try {
@@ -922,10 +1171,12 @@ export class Matterbridge extends EventEmitter {
922
1171
  }
923
1172
  }
924
1173
  return async (level, formattedLog) => {
1174
+ /* istanbul ignore if */
925
1175
  if (fileSize > 100000000) {
926
- return;
1176
+ return; // Stop logging if the file size is greater than 100MB
927
1177
  }
928
1178
  fileSize += formattedLog.length;
1179
+ /* istanbul ignore if */
929
1180
  if (fileSize > 100000000) {
930
1181
  await fs.appendFile(filePath, `Logging on file has been stopped because the file size is greater than 100MB.` + os.EOL);
931
1182
  return;
@@ -958,12 +1209,21 @@ export class Matterbridge extends EventEmitter {
958
1209
  }
959
1210
  };
960
1211
  }
1212
+ /**
1213
+ * Restarts the process by exiting the current instance and loading a new instance.
1214
+ */
961
1215
  async restartProcess() {
962
1216
  await this.cleanup('restarting...', true);
963
1217
  }
1218
+ /**
1219
+ * Shut down the process.
1220
+ */
964
1221
  async shutdownProcess() {
965
1222
  await this.cleanup('shutting down...', false);
966
1223
  }
1224
+ /**
1225
+ * Update matterbridge and shut down the process.
1226
+ */
967
1227
  async updateProcess() {
968
1228
  this.log.info('Updating matterbridge...');
969
1229
  try {
@@ -977,52 +1237,75 @@ export class Matterbridge extends EventEmitter {
977
1237
  this.frontend.wssSendRestartRequired();
978
1238
  await this.cleanup('updating...', false);
979
1239
  }
1240
+ /**
1241
+ * Unregister all devices and shut down the process.
1242
+ */
980
1243
  async unregisterAndShutdownProcess() {
981
1244
  this.log.info('Unregistering all devices and shutting down...');
982
1245
  for (const plugin of this.plugins) {
983
1246
  await this.removeAllBridgedEndpoints(plugin.name, 250);
984
1247
  }
985
1248
  this.log.debug('Waiting for the MessageExchange to finish...');
986
- await new Promise((resolve) => setTimeout(resolve, 1000));
1249
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
987
1250
  this.log.debug('Cleaning up and shutting down...');
988
1251
  await this.cleanup('unregistered all devices and shutting down...', false);
989
1252
  }
1253
+ /**
1254
+ * Reset commissioning and shut down the process.
1255
+ */
990
1256
  async shutdownProcessAndReset() {
991
1257
  await this.cleanup('shutting down with reset...', false);
992
1258
  }
1259
+ /**
1260
+ * Factory reset and shut down the process.
1261
+ */
993
1262
  async shutdownProcessAndFactoryReset() {
994
1263
  await this.cleanup('shutting down with factory reset...', false);
995
1264
  }
1265
+ /**
1266
+ * Cleans up the Matterbridge instance.
1267
+ *
1268
+ * @param {string} message - The cleanup message.
1269
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1270
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1271
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1272
+ */
996
1273
  async cleanup(message, restart = false, timeout = 1000) {
997
1274
  if (this.initialized && !this.hasCleanupStarted) {
998
1275
  this.emit('cleanup_started');
999
1276
  this.hasCleanupStarted = true;
1000
1277
  this.log.info(message);
1278
+ // Clear the start matter interval
1001
1279
  if (this.startMatterInterval) {
1002
1280
  clearInterval(this.startMatterInterval);
1003
1281
  this.startMatterInterval = undefined;
1004
1282
  this.log.debug('Start matter interval cleared');
1005
1283
  }
1284
+ // Clear the check update timeout
1006
1285
  if (this.checkUpdateTimeout) {
1007
1286
  clearTimeout(this.checkUpdateTimeout);
1008
1287
  this.checkUpdateTimeout = undefined;
1009
1288
  this.log.debug('Check update timeout cleared');
1010
1289
  }
1290
+ // Clear the check update interval
1011
1291
  if (this.checkUpdateInterval) {
1012
1292
  clearInterval(this.checkUpdateInterval);
1013
1293
  this.checkUpdateInterval = undefined;
1014
1294
  this.log.debug('Check update interval cleared');
1015
1295
  }
1296
+ // Clear the configure timeout
1016
1297
  if (this.configureTimeout) {
1017
1298
  clearTimeout(this.configureTimeout);
1018
1299
  this.configureTimeout = undefined;
1019
1300
  this.log.debug('Matterbridge configure timeout cleared');
1020
1301
  }
1302
+ // Clear the reachability timeout
1021
1303
  if (this.reachabilityTimeout) {
1022
1304
  clearTimeout(this.reachabilityTimeout);
1023
1305
  this.reachabilityTimeout = undefined;
1024
1306
  this.log.debug('Matterbridge reachability timeout cleared');
1025
1307
  }
1308
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
1026
1309
  for (const plugin of this.plugins) {
1027
1310
  if (!plugin.enabled || plugin.error)
1028
1311
  continue;
@@ -1033,9 +1316,10 @@ export class Matterbridge extends EventEmitter {
1033
1316
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1034
1317
  }
1035
1318
  }
1319
+ // Stop matter server nodes
1036
1320
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1037
1321
  this.log.debug('Waiting for the MessageExchange to finish...');
1038
- await new Promise((resolve) => setTimeout(resolve, timeout));
1322
+ await new Promise((resolve) => setTimeout(resolve, timeout)); // Wait for MessageExchange to finish
1039
1323
  if (this.bridgeMode === 'bridge') {
1040
1324
  if (this.serverNode) {
1041
1325
  await this.stopServerNode(this.serverNode);
@@ -1057,6 +1341,7 @@ export class Matterbridge extends EventEmitter {
1057
1341
  }
1058
1342
  }
1059
1343
  this.log.notice('Stopped matter server nodes');
1344
+ // Matter commisioning reset
1060
1345
  if (message === 'shutting down with reset...') {
1061
1346
  this.log.info('Resetting Matterbridge commissioning information...');
1062
1347
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1066,18 +1351,36 @@ export class Matterbridge extends EventEmitter {
1066
1351
  await this.matterbridgeContext?.clearAll();
1067
1352
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1068
1353
  }
1354
+ // Stop matter storage
1069
1355
  await this.stopMatterStorage();
1356
+ // Stop the frontend
1070
1357
  await this.frontend.stop();
1358
+ // Remove the matterfilelogger
1071
1359
  try {
1072
1360
  Logger.removeLogger('matterfilelogger');
1073
1361
  }
1074
1362
  catch (error) {
1075
1363
  this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
1076
1364
  }
1365
+ // Close the matterbridge node storage and context
1077
1366
  if (this.nodeStorage && this.nodeContext) {
1367
+ /*
1368
+ TODO: Implement serialization of registered devices in edge mode
1369
+ this.log.info('Saving registered devices...');
1370
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1371
+ this.devices.forEach(async (device) => {
1372
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1373
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1374
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1375
+ });
1376
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1377
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1378
+ */
1379
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1078
1380
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1079
1381
  await this.nodeContext.close();
1080
1382
  this.nodeContext = undefined;
1383
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1081
1384
  for (const plugin of this.plugins) {
1082
1385
  if (plugin.nodeContext) {
1083
1386
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1094,8 +1397,10 @@ export class Matterbridge extends EventEmitter {
1094
1397
  }
1095
1398
  this.plugins.clear();
1096
1399
  this.devices.clear();
1400
+ // Factory reset
1097
1401
  if (message === 'shutting down with factory reset...') {
1098
1402
  try {
1403
+ // Delete matter storage directory with its subdirectories and backup
1099
1404
  const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
1100
1405
  this.log.info(`Removing matter storage directory: ${dir}`);
1101
1406
  await fs.rm(dir, { recursive: true });
@@ -1109,6 +1414,7 @@ export class Matterbridge extends EventEmitter {
1109
1414
  }
1110
1415
  }
1111
1416
  try {
1417
+ // Delete matterbridge storage directory with its subdirectories and backup
1112
1418
  const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
1113
1419
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1114
1420
  await fs.rm(dir, { recursive: true });
@@ -1123,12 +1429,13 @@ export class Matterbridge extends EventEmitter {
1123
1429
  }
1124
1430
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1125
1431
  }
1432
+ // Deregisters the process handlers
1126
1433
  this.deregisterProcessHandlers();
1127
1434
  if (restart) {
1128
1435
  if (message === 'updating...') {
1129
1436
  this.log.info('Cleanup completed. Updating...');
1130
1437
  Matterbridge.instance = undefined;
1131
- this.emit('update');
1438
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1132
1439
  }
1133
1440
  else if (message === 'restarting...') {
1134
1441
  this.log.info('Cleanup completed. Restarting...');
@@ -1149,6 +1456,13 @@ export class Matterbridge extends EventEmitter {
1149
1456
  this.log.debug('Cleanup already started...');
1150
1457
  }
1151
1458
  }
1459
+ /**
1460
+ * Creates and configures the server node for a single not bridged device.
1461
+ *
1462
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1463
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1464
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1465
+ */
1152
1466
  async createDeviceServerNode(plugin, device) {
1153
1467
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1154
1468
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1159,6 +1473,14 @@ export class Matterbridge extends EventEmitter {
1159
1473
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1160
1474
  }
1161
1475
  }
1476
+ /**
1477
+ * Creates and configures the server node for an accessory plugin for a given device.
1478
+ *
1479
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1480
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1481
+ * @param {boolean} [start] - Whether to start the server node after adding the device. Default is `false`.
1482
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1483
+ */
1162
1484
  async createAccessoryPlugin(plugin, device, start = false) {
1163
1485
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1164
1486
  plugin.locked = true;
@@ -1172,6 +1494,13 @@ export class Matterbridge extends EventEmitter {
1172
1494
  await this.startServerNode(plugin.serverNode);
1173
1495
  }
1174
1496
  }
1497
+ /**
1498
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
1499
+ *
1500
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1501
+ * @param {boolean} [start] - Whether to start the server node after adding the aggregator node.
1502
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
1503
+ */
1175
1504
  async createDynamicPlugin(plugin, start = false) {
1176
1505
  if (!plugin.locked) {
1177
1506
  plugin.locked = true;
@@ -1184,7 +1513,14 @@ export class Matterbridge extends EventEmitter {
1184
1513
  await this.startServerNode(plugin.serverNode);
1185
1514
  }
1186
1515
  }
1516
+ /**
1517
+ * Starts the Matterbridge in bridge mode.
1518
+ *
1519
+ * @private
1520
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1521
+ */
1187
1522
  async startBridge() {
1523
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1188
1524
  if (!this.matterStorageManager)
1189
1525
  throw new Error('No storage manager initialized');
1190
1526
  if (!this.matterbridgeContext)
@@ -1223,13 +1559,16 @@ export class Matterbridge extends EventEmitter {
1223
1559
  clearInterval(this.startMatterInterval);
1224
1560
  this.startMatterInterval = undefined;
1225
1561
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1226
- this.startServerNode(this.serverNode);
1562
+ // Start the Matter server node
1563
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1564
+ // Start the Matter server node of single devices in mode 'server'
1227
1565
  for (const device of this.devices.array()) {
1228
1566
  if (device.mode === 'server' && device.serverNode) {
1229
1567
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1230
- this.startServerNode(device.serverNode);
1568
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1231
1569
  }
1232
1570
  }
1571
+ // Configure the plugins
1233
1572
  this.configureTimeout = setTimeout(async () => {
1234
1573
  for (const plugin of this.plugins) {
1235
1574
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1247,16 +1586,24 @@ export class Matterbridge extends EventEmitter {
1247
1586
  }
1248
1587
  this.frontend.wssSendRefreshRequired('plugins');
1249
1588
  }, 30 * 1000).unref();
1589
+ // Setting reachability to true
1250
1590
  this.reachabilityTimeout = setTimeout(() => {
1251
1591
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1252
1592
  if (this.aggregatorNode)
1253
1593
  this.setAggregatorReachability(this.aggregatorNode, true);
1254
1594
  this.frontend.wssSendRefreshRequired('reachability');
1255
1595
  }, 60 * 1000).unref();
1596
+ // Logger.get('LogServerNode').info(this.serverNode);
1256
1597
  this.emit('bridge_started');
1257
1598
  this.log.notice('Matterbridge bridge started successfully');
1258
1599
  }, 1000);
1259
1600
  }
1601
+ /**
1602
+ * Starts the Matterbridge in childbridge mode.
1603
+ *
1604
+ * @private
1605
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1606
+ */
1260
1607
  async startChildbridge() {
1261
1608
  if (!this.matterStorageManager)
1262
1609
  throw new Error('No storage manager initialized');
@@ -1294,6 +1641,7 @@ export class Matterbridge extends EventEmitter {
1294
1641
  clearInterval(this.startMatterInterval);
1295
1642
  this.startMatterInterval = undefined;
1296
1643
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1644
+ // Configure the plugins
1297
1645
  this.configureTimeout = setTimeout(async () => {
1298
1646
  for (const plugin of this.plugins) {
1299
1647
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1330,7 +1678,9 @@ export class Matterbridge extends EventEmitter {
1330
1678
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1331
1679
  continue;
1332
1680
  }
1333
- this.startServerNode(plugin.serverNode);
1681
+ // Start the Matter server node
1682
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1683
+ // Setting reachability to true
1334
1684
  plugin.reachabilityTimeout = setTimeout(() => {
1335
1685
  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}`);
1336
1686
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1338,19 +1688,241 @@ export class Matterbridge extends EventEmitter {
1338
1688
  this.frontend.wssSendRefreshRequired('reachability');
1339
1689
  }, 60 * 1000).unref();
1340
1690
  }
1691
+ // Start the Matter server node of single devices in mode 'server'
1341
1692
  for (const device of this.devices.array()) {
1342
1693
  if (device.mode === 'server' && device.serverNode) {
1343
1694
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1344
- this.startServerNode(device.serverNode);
1695
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1345
1696
  }
1346
1697
  }
1698
+ // Logger.get('LogServerNode').info(this.serverNode);
1347
1699
  this.emit('childbridge_started');
1348
1700
  this.log.notice('Matterbridge childbridge started successfully');
1349
1701
  }, 1000);
1350
1702
  }
1703
+ /**
1704
+ * Starts the Matterbridge controller.
1705
+ *
1706
+ * @private
1707
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1708
+ */
1351
1709
  async startController() {
1710
+ /*
1711
+ if (!this.matterStorageManager) {
1712
+ this.log.error('No storage manager initialized');
1713
+ await this.cleanup('No storage manager initialized');
1714
+ return;
1715
+ }
1716
+ this.log.info('Creating context: mattercontrollerContext');
1717
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1718
+ if (!this.controllerContext) {
1719
+ this.log.error('No storage context mattercontrollerContext initialized');
1720
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1721
+ return;
1722
+ }
1723
+
1724
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1725
+ this.matterServer = await this.createMatterServer(this.storageManager);
1726
+ this.log.info('Creating matter commissioning controller');
1727
+ this.commissioningController = new CommissioningController({
1728
+ autoConnect: false,
1729
+ });
1730
+ this.log.info('Adding matter commissioning controller to matter server');
1731
+ await this.matterServer.addCommissioningController(this.commissioningController);
1732
+
1733
+ this.log.info('Starting matter server');
1734
+ await this.matterServer.start();
1735
+ this.log.info('Matter server started');
1736
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1737
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1738
+ regulatoryCountryCode: 'XX',
1739
+ };
1740
+ const commissioningController = new CommissioningController({
1741
+ environment: {
1742
+ environment,
1743
+ id: uniqueId,
1744
+ },
1745
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1746
+ adminFabricLabel,
1747
+ });
1748
+
1749
+ if (hasParameter('pairingcode')) {
1750
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1751
+ const pairingCode = getParameter('pairingcode');
1752
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1753
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1754
+
1755
+ let longDiscriminator, setupPin, shortDiscriminator;
1756
+ if (pairingCode !== undefined) {
1757
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1758
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1759
+ longDiscriminator = undefined;
1760
+ setupPin = pairingCodeCodec.passcode;
1761
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1762
+ } else {
1763
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1764
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1765
+ setupPin = this.controllerContext.get('pin', 20202021);
1766
+ }
1767
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1768
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1769
+ }
1770
+
1771
+ const options = {
1772
+ commissioning: commissioningOptions,
1773
+ discovery: {
1774
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1775
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1776
+ },
1777
+ passcode: setupPin,
1778
+ } as NodeCommissioningOptions;
1779
+ this.log.info('Commissioning with options:', options);
1780
+ const nodeId = await this.commissioningController.commissionNode(options);
1781
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1782
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1783
+ } // (hasParameter('pairingcode'))
1784
+
1785
+ if (hasParameter('unpairall')) {
1786
+ this.log.info('***Commissioning controller unpairing all nodes...');
1787
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1788
+ for (const nodeId of nodeIds) {
1789
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1790
+ await this.commissioningController.removeNode(nodeId);
1791
+ }
1792
+ return;
1793
+ }
1794
+
1795
+ if (hasParameter('discover')) {
1796
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1797
+ // console.log(discover);
1798
+ }
1799
+
1800
+ if (!this.commissioningController.isCommissioned()) {
1801
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1802
+ return;
1803
+ }
1804
+
1805
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1806
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1807
+ for (const nodeId of nodeIds) {
1808
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1809
+
1810
+ const node = await this.commissioningController.connectNode(nodeId, {
1811
+ autoSubscribe: false,
1812
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1813
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1814
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1815
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1816
+ stateInformationCallback: (peerNodeId, info) => {
1817
+ switch (info) {
1818
+ case NodeStateInformation.Connected:
1819
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1820
+ break;
1821
+ case NodeStateInformation.Disconnected:
1822
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1823
+ break;
1824
+ case NodeStateInformation.Reconnecting:
1825
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1826
+ break;
1827
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1828
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1829
+ break;
1830
+ case NodeStateInformation.StructureChanged:
1831
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1832
+ break;
1833
+ case NodeStateInformation.Decommissioned:
1834
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1835
+ break;
1836
+ default:
1837
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1838
+ break;
1839
+ }
1840
+ },
1841
+ });
1842
+
1843
+ node.logStructure();
1844
+
1845
+ // Get the interaction client
1846
+ this.log.info('Getting the interaction client');
1847
+ const interactionClient = await node.getInteractionClient();
1848
+ let cluster;
1849
+ let attributes;
1850
+
1851
+ // Log BasicInformationCluster
1852
+ cluster = BasicInformationCluster;
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 PowerSourceCluster
1864
+ cluster = PowerSourceCluster;
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 ThreadNetworkDiagnostics
1876
+ cluster = ThreadNetworkDiagnosticsCluster;
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
+ // Log SwitchCluster
1888
+ cluster = SwitchCluster;
1889
+ attributes = await interactionClient.getMultipleAttributes({
1890
+ attributes: [{ clusterId: cluster.id }],
1891
+ });
1892
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1893
+ attributes.forEach((attribute) => {
1894
+ this.log.info(
1895
+ `- 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}`,
1896
+ );
1897
+ });
1898
+
1899
+ this.log.info('Subscribing to all attributes and events');
1900
+ await node.subscribeAllAttributesAndEvents({
1901
+ ignoreInitialTriggers: false,
1902
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1903
+ this.log.info(
1904
+ `***${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}`,
1905
+ ),
1906
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1907
+ this.log.info(
1908
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1909
+ );
1910
+ },
1911
+ });
1912
+ this.log.info('Subscribed to all attributes and events');
1913
+ }
1914
+ */
1352
1915
  }
1916
+ /** */
1917
+ /** Matter.js methods */
1918
+ /** */
1919
+ /**
1920
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1921
+ *
1922
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1923
+ */
1353
1924
  async startMatterStorage() {
1925
+ // Setup Matter storage
1354
1926
  this.log.info(`Starting matter node storage...`);
1355
1927
  this.matterStorageService = this.environment.get(StorageService);
1356
1928
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1359,8 +1931,17 @@ export class Matterbridge extends EventEmitter {
1359
1931
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
1360
1932
  this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
1361
1933
  this.log.info('Matter node storage started');
1934
+ // Backup matter storage since it is created/opened correctly
1362
1935
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1363
1936
  }
1937
+ /**
1938
+ * Makes a backup copy of the specified matter storage directory.
1939
+ *
1940
+ * @param {string} storageName - The name of the storage directory to be backed up.
1941
+ * @param {string} backupName - The name of the backup directory to be created.
1942
+ * @private
1943
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1944
+ */
1364
1945
  async backupMatterStorage(storageName, backupName) {
1365
1946
  this.log.info('Creating matter node storage backup...');
1366
1947
  try {
@@ -1371,6 +1952,11 @@ export class Matterbridge extends EventEmitter {
1371
1952
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1372
1953
  }
1373
1954
  }
1955
+ /**
1956
+ * Stops the matter storage.
1957
+ *
1958
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1959
+ */
1374
1960
  async stopMatterStorage() {
1375
1961
  this.log.info('Closing matter node storage...');
1376
1962
  await this.matterStorageManager?.close();
@@ -1379,6 +1965,19 @@ export class Matterbridge extends EventEmitter {
1379
1965
  this.matterbridgeContext = undefined;
1380
1966
  this.log.info('Matter node storage closed');
1381
1967
  }
1968
+ /**
1969
+ * Creates a server node storage context.
1970
+ *
1971
+ * @param {string} pluginName - The name of the plugin.
1972
+ * @param {string} deviceName - The name of the device.
1973
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1974
+ * @param {number} vendorId - The vendor ID.
1975
+ * @param {string} vendorName - The vendor name.
1976
+ * @param {number} productId - The product ID.
1977
+ * @param {string} productName - The product name.
1978
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1979
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1980
+ */
1382
1981
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1383
1982
  const { randomBytes } = await import('node:crypto');
1384
1983
  if (!this.matterStorageService)
@@ -1412,6 +2011,15 @@ export class Matterbridge extends EventEmitter {
1412
2011
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1413
2012
  return storageContext;
1414
2013
  }
2014
+ /**
2015
+ * Creates a server node.
2016
+ *
2017
+ * @param {StorageContext} storageContext - The storage context for the server node.
2018
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
2019
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
2020
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
2021
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
2022
+ */
1415
2023
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1416
2024
  const storeId = await storageContext.get('storeId');
1417
2025
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1421,24 +2029,37 @@ export class Matterbridge extends EventEmitter {
1421
2029
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1422
2030
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1423
2031
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2032
+ /**
2033
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
2034
+ */
1424
2035
  const serverNode = await ServerNode.create({
2036
+ // Required: Give the Node a unique ID which is used to store the state of this node
1425
2037
  id: storeId,
2038
+ // Provide Network relevant configuration like the port
2039
+ // Optional when operating only one device on a host, Default port is 5540
1426
2040
  network: {
1427
2041
  listeningAddressIpv4: this.ipv4address,
1428
2042
  listeningAddressIpv6: this.ipv6address,
1429
2043
  port,
1430
2044
  },
2045
+ // Provide the certificate for the device
1431
2046
  operationalCredentials: {
1432
2047
  certification: this.certification,
1433
2048
  },
2049
+ // Provide Commissioning relevant settings
2050
+ // Optional for development/testing purposes
1434
2051
  commissioning: {
1435
2052
  passcode,
1436
2053
  discriminator,
1437
2054
  },
2055
+ // Provide Node announcement settings
2056
+ // Optional: If Ommitted some development defaults are used
1438
2057
  productDescription: {
1439
2058
  name: await storageContext.get('deviceName'),
1440
2059
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1441
2060
  },
2061
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
2062
+ // Optional: If Omitted some development defaults are used
1442
2063
  basicInformation: {
1443
2064
  vendorId: VendorId(await storageContext.get('vendorId')),
1444
2065
  vendorName: await storageContext.get('vendorName'),
@@ -1456,12 +2077,13 @@ export class Matterbridge extends EventEmitter {
1456
2077
  },
1457
2078
  });
1458
2079
  const sanitizeFabrics = (fabrics, resetSessions = false) => {
2080
+ // New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
1459
2081
  const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1460
2082
  this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1461
2083
  if (this.bridgeMode === 'bridge') {
1462
2084
  this.matterbridgeFabricInformations = sanitizedFabrics;
1463
2085
  if (resetSessions)
1464
- this.matterbridgeSessionInformations = undefined;
2086
+ this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1465
2087
  this.matterbridgePaired = true;
1466
2088
  }
1467
2089
  if (this.bridgeMode === 'childbridge') {
@@ -1469,16 +2091,22 @@ export class Matterbridge extends EventEmitter {
1469
2091
  if (plugin) {
1470
2092
  plugin.fabricInformations = sanitizedFabrics;
1471
2093
  if (resetSessions)
1472
- plugin.sessionInformations = undefined;
2094
+ plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
1473
2095
  plugin.paired = true;
1474
2096
  }
1475
2097
  }
1476
2098
  };
2099
+ /**
2100
+ * This event is triggered when the device is initially commissioned successfully.
2101
+ * This means: It is added to the first fabric.
2102
+ */
1477
2103
  serverNode.lifecycle.commissioned.on(() => {
1478
2104
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1479
2105
  clearTimeout(this.endAdvertiseTimeout);
1480
2106
  });
2107
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1481
2108
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
2109
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1482
2110
  serverNode.lifecycle.online.on(async () => {
1483
2111
  this.log.notice(`Server node for ${storeId} is online`);
1484
2112
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1517,6 +2145,7 @@ export class Matterbridge extends EventEmitter {
1517
2145
  }
1518
2146
  }
1519
2147
  }
2148
+ // Set a timeout to show that advertising stops after 15 minutes if not commissioned
1520
2149
  this.startEndAdvertiseTimer(serverNode);
1521
2150
  }
1522
2151
  else {
@@ -1528,6 +2157,7 @@ export class Matterbridge extends EventEmitter {
1528
2157
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1529
2158
  this.emit('online', storeId);
1530
2159
  });
2160
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1531
2161
  serverNode.lifecycle.offline.on(() => {
1532
2162
  this.log.notice(`Server node for ${storeId} is offline`);
1533
2163
  if (this.bridgeMode === 'bridge') {
@@ -1552,6 +2182,10 @@ export class Matterbridge extends EventEmitter {
1552
2182
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1553
2183
  this.emit('offline', storeId);
1554
2184
  });
2185
+ /**
2186
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2187
+ * information is needed.
2188
+ */
1555
2189
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1556
2190
  let action = '';
1557
2191
  switch (fabricAction) {
@@ -1582,16 +2216,24 @@ export class Matterbridge extends EventEmitter {
1582
2216
  }
1583
2217
  }
1584
2218
  };
2219
+ /**
2220
+ * This event is triggered when an operative new session was opened by a Controller.
2221
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2222
+ */
1585
2223
  serverNode.events.sessions.opened.on((session) => {
1586
2224
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1587
2225
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1588
2226
  this.frontend.wssSendRefreshRequired('sessions');
1589
2227
  });
2228
+ /**
2229
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2230
+ */
1590
2231
  serverNode.events.sessions.closed.on((session) => {
1591
2232
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1592
2233
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1593
2234
  this.frontend.wssSendRefreshRequired('sessions');
1594
2235
  });
2236
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1595
2237
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1596
2238
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1597
2239
  sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
@@ -1600,6 +2242,11 @@ export class Matterbridge extends EventEmitter {
1600
2242
  this.log.info(`Created server node for ${storeId}`);
1601
2243
  return serverNode;
1602
2244
  }
2245
+ /**
2246
+ * Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
2247
+ *
2248
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2249
+ */
1603
2250
  startEndAdvertiseTimer(matterServerNode) {
1604
2251
  if (this.endAdvertiseTimeout) {
1605
2252
  this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
@@ -1628,12 +2275,25 @@ export class Matterbridge extends EventEmitter {
1628
2275
  this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
1629
2276
  }, 15 * 60 * 1000).unref();
1630
2277
  }
2278
+ /**
2279
+ * Starts the specified server node.
2280
+ *
2281
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2282
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2283
+ */
1631
2284
  async startServerNode(matterServerNode) {
1632
2285
  if (!matterServerNode)
1633
2286
  return;
1634
2287
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1635
2288
  await matterServerNode.start();
1636
2289
  }
2290
+ /**
2291
+ * Stops the specified server node.
2292
+ *
2293
+ * @param {ServerNode} matterServerNode - The server node to stop.
2294
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2295
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2296
+ */
1637
2297
  async stopServerNode(matterServerNode, timeout = 30000) {
1638
2298
  if (!matterServerNode)
1639
2299
  return;
@@ -1646,6 +2306,12 @@ export class Matterbridge extends EventEmitter {
1646
2306
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1647
2307
  }
1648
2308
  }
2309
+ /**
2310
+ * Advertises the specified server node.
2311
+ *
2312
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2313
+ * @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.
2314
+ */
1649
2315
  async advertiseServerNode(matterServerNode) {
1650
2316
  if (matterServerNode) {
1651
2317
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1654,19 +2320,39 @@ export class Matterbridge extends EventEmitter {
1654
2320
  return { qrPairingCode, manualPairingCode };
1655
2321
  }
1656
2322
  }
2323
+ /**
2324
+ * Stop advertise the specified server node.
2325
+ *
2326
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2327
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2328
+ */
1657
2329
  async stopAdvertiseServerNode(matterServerNode) {
1658
2330
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1659
2331
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1660
2332
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1661
2333
  }
1662
2334
  }
2335
+ /**
2336
+ * Creates an aggregator node with the specified storage context.
2337
+ *
2338
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2339
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2340
+ */
1663
2341
  async createAggregatorNode(storageContext) {
1664
2342
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1665
2343
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1666
2344
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1667
2345
  return aggregatorNode;
1668
2346
  }
2347
+ /**
2348
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2349
+ *
2350
+ * @param {string} pluginName - The name of the plugin.
2351
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2352
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2353
+ */
1669
2354
  async addBridgedEndpoint(pluginName, device) {
2355
+ // Check if the plugin is registered
1670
2356
  const plugin = this.plugins.get(pluginName);
1671
2357
  if (!plugin) {
1672
2358
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1686,6 +2372,7 @@ export class Matterbridge extends EventEmitter {
1686
2372
  }
1687
2373
  else if (this.bridgeMode === 'bridge') {
1688
2374
  if (device.mode === 'matter') {
2375
+ // Register and add the device to the matterbridge server node
1689
2376
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1690
2377
  if (!this.serverNode) {
1691
2378
  this.log.error('Server node not found for Matterbridge');
@@ -1702,6 +2389,7 @@ export class Matterbridge extends EventEmitter {
1702
2389
  }
1703
2390
  }
1704
2391
  else {
2392
+ // Register and add the device to the matterbridge aggregator node
1705
2393
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1706
2394
  if (!this.aggregatorNode) {
1707
2395
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1719,6 +2407,7 @@ export class Matterbridge extends EventEmitter {
1719
2407
  }
1720
2408
  }
1721
2409
  else if (this.bridgeMode === 'childbridge') {
2410
+ // Register and add the device to the plugin server node
1722
2411
  if (plugin.type === 'AccessoryPlatform') {
1723
2412
  try {
1724
2413
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1742,10 +2431,12 @@ export class Matterbridge extends EventEmitter {
1742
2431
  return;
1743
2432
  }
1744
2433
  }
2434
+ // Register and add the device to the plugin aggregator node
1745
2435
  if (plugin.type === 'DynamicPlatform') {
1746
2436
  try {
1747
2437
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1748
2438
  await this.createDynamicPlugin(plugin);
2439
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1749
2440
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1750
2441
  if (!plugin.aggregatorNode) {
1751
2442
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1768,17 +2459,28 @@ export class Matterbridge extends EventEmitter {
1768
2459
  plugin.registeredDevices++;
1769
2460
  if (plugin.addedDevices !== undefined)
1770
2461
  plugin.addedDevices++;
2462
+ // Add the device to the DeviceManager
1771
2463
  this.devices.set(device);
2464
+ // Subscribe to the reachable$Changed event
1772
2465
  await this.subscribeAttributeChanged(plugin, device);
1773
2466
  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}`);
1774
2467
  }
2468
+ /**
2469
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2470
+ *
2471
+ * @param {string} pluginName - The name of the plugin.
2472
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2473
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2474
+ */
1775
2475
  async removeBridgedEndpoint(pluginName, device) {
1776
2476
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2477
+ // Check if the plugin is registered
1777
2478
  const plugin = this.plugins.get(pluginName);
1778
2479
  if (!plugin) {
1779
2480
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1780
2481
  return;
1781
2482
  }
2483
+ // Register and add the device to the matterbridge aggregator node
1782
2484
  if (this.bridgeMode === 'bridge') {
1783
2485
  if (!this.aggregatorNode) {
1784
2486
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1793,6 +2495,7 @@ export class Matterbridge extends EventEmitter {
1793
2495
  }
1794
2496
  else if (this.bridgeMode === 'childbridge') {
1795
2497
  if (plugin.type === 'AccessoryPlatform') {
2498
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1796
2499
  }
1797
2500
  else if (plugin.type === 'DynamicPlatform') {
1798
2501
  if (!plugin.aggregatorNode) {
@@ -1807,8 +2510,21 @@ export class Matterbridge extends EventEmitter {
1807
2510
  if (plugin.addedDevices !== undefined)
1808
2511
  plugin.addedDevices--;
1809
2512
  }
2513
+ // Remove the device from the DeviceManager
1810
2514
  this.devices.remove(device);
1811
2515
  }
2516
+ /**
2517
+ * Removes all bridged endpoints from the specified plugin.
2518
+ *
2519
+ * @param {string} pluginName - The name of the plugin.
2520
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2521
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2522
+ *
2523
+ * @remarks
2524
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2525
+ * It also applies a delay between each removal if specified.
2526
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2527
+ */
1812
2528
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1813
2529
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1814
2530
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1819,6 +2535,15 @@ export class Matterbridge extends EventEmitter {
1819
2535
  if (delay > 0)
1820
2536
  await new Promise((resolve) => setTimeout(resolve, 2000));
1821
2537
  }
2538
+ /**
2539
+ * Subscribes to the attribute change event for the given device and plugin.
2540
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2541
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2542
+ *
2543
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2544
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2545
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2546
+ */
1822
2547
  async subscribeAttributeChanged(plugin, device) {
1823
2548
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
1824
2549
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
@@ -1834,6 +2559,12 @@ export class Matterbridge extends EventEmitter {
1834
2559
  });
1835
2560
  }
1836
2561
  }
2562
+ /**
2563
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2564
+ *
2565
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2566
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2567
+ */
1837
2568
  sanitizeFabricInformations(fabricInfo) {
1838
2569
  return fabricInfo.map((info) => {
1839
2570
  return {
@@ -1847,6 +2578,12 @@ export class Matterbridge extends EventEmitter {
1847
2578
  };
1848
2579
  });
1849
2580
  }
2581
+ /**
2582
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2583
+ *
2584
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2585
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2586
+ */
1850
2587
  sanitizeSessionInformation(sessions) {
1851
2588
  return sessions
1852
2589
  .filter((session) => session.isPeerActive)
@@ -1873,7 +2610,21 @@ export class Matterbridge extends EventEmitter {
1873
2610
  };
1874
2611
  });
1875
2612
  }
2613
+ /**
2614
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2615
+ *
2616
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2617
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2618
+ */
2619
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1876
2620
  async setAggregatorReachability(aggregatorNode, reachable) {
2621
+ /*
2622
+ for (const child of aggregatorNode.parts) {
2623
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2624
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2625
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2626
+ }
2627
+ */
1877
2628
  }
1878
2629
  getVendorIdName = (vendorId) => {
1879
2630
  if (!vendorId)
@@ -1917,3 +2668,4 @@ export class Matterbridge extends EventEmitter {
1917
2668
  return vendorName;
1918
2669
  };
1919
2670
  }
2671
+ //# sourceMappingURL=matterbridge.js.map