matterbridge 3.2.4-dev-20250830-5c48452 → 3.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/dist/cli.d.ts +26 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +91 -2
  4. package/dist/cli.js.map +1 -0
  5. package/dist/cliEmitter.d.ts +34 -0
  6. package/dist/cliEmitter.d.ts.map +1 -0
  7. package/dist/cliEmitter.js +30 -0
  8. package/dist/cliEmitter.js.map +1 -0
  9. package/dist/clusters/export.d.ts +2 -0
  10. package/dist/clusters/export.d.ts.map +1 -0
  11. package/dist/clusters/export.js +2 -0
  12. package/dist/clusters/export.js.map +1 -0
  13. package/dist/defaultConfigSchema.d.ts +28 -0
  14. package/dist/defaultConfigSchema.d.ts.map +1 -0
  15. package/dist/defaultConfigSchema.js +24 -0
  16. package/dist/defaultConfigSchema.js.map +1 -0
  17. package/dist/deviceManager.d.ts +112 -0
  18. package/dist/deviceManager.d.ts.map +1 -0
  19. package/dist/deviceManager.js +94 -1
  20. package/dist/deviceManager.js.map +1 -0
  21. package/dist/devices/batteryStorage.d.ts +48 -0
  22. package/dist/devices/batteryStorage.d.ts.map +1 -0
  23. package/dist/devices/batteryStorage.js +48 -1
  24. package/dist/devices/batteryStorage.js.map +1 -0
  25. package/dist/devices/cooktop.d.ts +60 -0
  26. package/dist/devices/cooktop.d.ts.map +1 -0
  27. package/dist/devices/cooktop.js +55 -0
  28. package/dist/devices/cooktop.js.map +1 -0
  29. package/dist/devices/dishwasher.d.ts +71 -0
  30. package/dist/devices/dishwasher.d.ts.map +1 -0
  31. package/dist/devices/dishwasher.js +57 -0
  32. package/dist/devices/dishwasher.js.map +1 -0
  33. package/dist/devices/evse.d.ts +75 -0
  34. package/dist/devices/evse.d.ts.map +1 -0
  35. package/dist/devices/evse.js +74 -10
  36. package/dist/devices/evse.js.map +1 -0
  37. package/dist/devices/export.d.ts +15 -0
  38. package/dist/devices/export.d.ts.map +1 -0
  39. package/dist/devices/export.js +4 -0
  40. package/dist/devices/export.js.map +1 -0
  41. package/dist/devices/extractorHood.d.ts +46 -0
  42. package/dist/devices/extractorHood.d.ts.map +1 -0
  43. package/dist/devices/extractorHood.js +42 -0
  44. package/dist/devices/extractorHood.js.map +1 -0
  45. package/dist/devices/heatPump.d.ts +47 -0
  46. package/dist/devices/heatPump.d.ts.map +1 -0
  47. package/dist/devices/heatPump.js +50 -2
  48. package/dist/devices/heatPump.js.map +1 -0
  49. package/dist/devices/laundryDryer.d.ts +67 -0
  50. package/dist/devices/laundryDryer.d.ts.map +1 -0
  51. package/dist/devices/laundryDryer.js +62 -3
  52. package/dist/devices/laundryDryer.js.map +1 -0
  53. package/dist/devices/laundryWasher.d.ts +81 -0
  54. package/dist/devices/laundryWasher.d.ts.map +1 -0
  55. package/dist/devices/laundryWasher.js +70 -4
  56. package/dist/devices/laundryWasher.js.map +1 -0
  57. package/dist/devices/microwaveOven.d.ts +168 -0
  58. package/dist/devices/microwaveOven.d.ts.map +1 -0
  59. package/dist/devices/microwaveOven.js +88 -5
  60. package/dist/devices/microwaveOven.js.map +1 -0
  61. package/dist/devices/oven.d.ts +105 -0
  62. package/dist/devices/oven.d.ts.map +1 -0
  63. package/dist/devices/oven.js +85 -0
  64. package/dist/devices/oven.js.map +1 -0
  65. package/dist/devices/refrigerator.d.ts +93 -0
  66. package/dist/devices/refrigerator.d.ts.map +1 -0
  67. package/dist/devices/refrigerator.js +80 -0
  68. package/dist/devices/refrigerator.js.map +1 -0
  69. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  70. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  71. package/dist/devices/roboticVacuumCleaner.js +93 -7
  72. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  73. package/dist/devices/solarPower.d.ts +40 -0
  74. package/dist/devices/solarPower.d.ts.map +1 -0
  75. package/dist/devices/solarPower.js +38 -0
  76. package/dist/devices/solarPower.js.map +1 -0
  77. package/dist/devices/temperatureControl.d.ts +166 -0
  78. package/dist/devices/temperatureControl.d.ts.map +1 -0
  79. package/dist/devices/temperatureControl.js +25 -3
  80. package/dist/devices/temperatureControl.js.map +1 -0
  81. package/dist/devices/waterHeater.d.ts +111 -0
  82. package/dist/devices/waterHeater.d.ts.map +1 -0
  83. package/dist/devices/waterHeater.js +82 -2
  84. package/dist/devices/waterHeater.js.map +1 -0
  85. package/dist/dgram/coap.d.ts +205 -0
  86. package/dist/dgram/coap.d.ts.map +1 -0
  87. package/dist/dgram/coap.js +126 -13
  88. package/dist/dgram/coap.js.map +1 -0
  89. package/dist/dgram/dgram.d.ts +140 -0
  90. package/dist/dgram/dgram.d.ts.map +1 -0
  91. package/dist/dgram/dgram.js +113 -2
  92. package/dist/dgram/dgram.js.map +1 -0
  93. package/dist/dgram/mb_coap.d.ts +24 -0
  94. package/dist/dgram/mb_coap.d.ts.map +1 -0
  95. package/dist/dgram/mb_coap.js +41 -3
  96. package/dist/dgram/mb_coap.js.map +1 -0
  97. package/dist/dgram/mb_mdns.d.ts +24 -0
  98. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  99. package/dist/dgram/mb_mdns.js +51 -13
  100. package/dist/dgram/mb_mdns.js.map +1 -0
  101. package/dist/dgram/mdns.d.ts +288 -0
  102. package/dist/dgram/mdns.d.ts.map +1 -0
  103. package/dist/dgram/mdns.js +298 -137
  104. package/dist/dgram/mdns.js.map +1 -0
  105. package/dist/dgram/multicast.d.ts +65 -0
  106. package/dist/dgram/multicast.d.ts.map +1 -0
  107. package/dist/dgram/multicast.js +60 -1
  108. package/dist/dgram/multicast.js.map +1 -0
  109. package/dist/dgram/unicast.d.ts +56 -0
  110. package/dist/dgram/unicast.d.ts.map +1 -0
  111. package/dist/dgram/unicast.js +54 -0
  112. package/dist/dgram/unicast.js.map +1 -0
  113. package/dist/frontend.d.ts +313 -0
  114. package/dist/frontend.d.ts.map +1 -0
  115. package/dist/frontend.js +451 -24
  116. package/dist/frontend.js.map +1 -0
  117. package/dist/globalMatterbridge.d.ts +59 -0
  118. package/dist/globalMatterbridge.d.ts.map +1 -0
  119. package/dist/globalMatterbridge.js +47 -0
  120. package/dist/globalMatterbridge.js.map +1 -0
  121. package/dist/helpers.d.ts +48 -0
  122. package/dist/helpers.d.ts.map +1 -0
  123. package/dist/helpers.js +53 -0
  124. package/dist/helpers.js.map +1 -0
  125. package/dist/index.d.ts +33 -0
  126. package/dist/index.d.ts.map +1 -0
  127. package/dist/index.js +30 -1
  128. package/dist/index.js.map +1 -0
  129. package/dist/logger/export.d.ts +2 -0
  130. package/dist/logger/export.d.ts.map +1 -0
  131. package/dist/logger/export.js +1 -0
  132. package/dist/logger/export.js.map +1 -0
  133. package/dist/matter/behaviors.d.ts +2 -0
  134. package/dist/matter/behaviors.d.ts.map +1 -0
  135. package/dist/matter/behaviors.js +2 -0
  136. package/dist/matter/behaviors.js.map +1 -0
  137. package/dist/matter/clusters.d.ts +2 -0
  138. package/dist/matter/clusters.d.ts.map +1 -0
  139. package/dist/matter/clusters.js +2 -0
  140. package/dist/matter/clusters.js.map +1 -0
  141. package/dist/matter/devices.d.ts +2 -0
  142. package/dist/matter/devices.d.ts.map +1 -0
  143. package/dist/matter/devices.js +2 -0
  144. package/dist/matter/devices.js.map +1 -0
  145. package/dist/matter/endpoints.d.ts +2 -0
  146. package/dist/matter/endpoints.d.ts.map +1 -0
  147. package/dist/matter/endpoints.js +2 -0
  148. package/dist/matter/endpoints.js.map +1 -0
  149. package/dist/matter/export.d.ts +5 -0
  150. package/dist/matter/export.d.ts.map +1 -0
  151. package/dist/matter/export.js +3 -0
  152. package/dist/matter/export.js.map +1 -0
  153. package/dist/matter/types.d.ts +3 -0
  154. package/dist/matter/types.d.ts.map +1 -0
  155. package/dist/matter/types.js +3 -0
  156. package/dist/matter/types.js.map +1 -0
  157. package/dist/matterbridge.d.ts +462 -0
  158. package/dist/matterbridge.d.ts.map +1 -0
  159. package/dist/matterbridge.js +789 -50
  160. package/dist/matterbridge.js.map +1 -0
  161. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  162. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  163. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  164. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  165. package/dist/matterbridgeBehaviors.d.ts +1351 -0
  166. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  167. package/dist/matterbridgeBehaviors.js +65 -5
  168. package/dist/matterbridgeBehaviors.js.map +1 -0
  169. package/dist/matterbridgeDeviceTypes.d.ts +709 -0
  170. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  171. package/dist/matterbridgeDeviceTypes.js +579 -15
  172. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  173. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  174. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  175. package/dist/matterbridgeDynamicPlatform.js +36 -0
  176. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  177. package/dist/matterbridgeEndpoint.d.ts +1356 -0
  178. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  179. package/dist/matterbridgeEndpoint.js +1220 -54
  180. package/dist/matterbridgeEndpoint.js.map +1 -0
  181. package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
  182. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  183. package/dist/matterbridgeEndpointHelpers.js +345 -12
  184. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  185. package/dist/matterbridgePlatform.d.ts +331 -0
  186. package/dist/matterbridgePlatform.d.ts.map +1 -0
  187. package/dist/matterbridgePlatform.js +256 -0
  188. package/dist/matterbridgePlatform.js.map +1 -0
  189. package/dist/matterbridgeTypes.d.ts +198 -0
  190. package/dist/matterbridgeTypes.d.ts.map +1 -0
  191. package/dist/matterbridgeTypes.js +25 -0
  192. package/dist/matterbridgeTypes.js.map +1 -0
  193. package/dist/pluginManager.d.ts +270 -0
  194. package/dist/pluginManager.d.ts.map +1 -0
  195. package/dist/pluginManager.js +249 -3
  196. package/dist/pluginManager.js.map +1 -0
  197. package/dist/shelly.d.ts +174 -0
  198. package/dist/shelly.d.ts.map +1 -0
  199. package/dist/shelly.js +168 -7
  200. package/dist/shelly.js.map +1 -0
  201. package/dist/storage/export.d.ts +2 -0
  202. package/dist/storage/export.d.ts.map +1 -0
  203. package/dist/storage/export.js +1 -0
  204. package/dist/storage/export.js.map +1 -0
  205. package/dist/update.d.ts +75 -0
  206. package/dist/update.d.ts.map +1 -0
  207. package/dist/update.js +69 -0
  208. package/dist/update.js.map +1 -0
  209. package/dist/utils/colorUtils.d.ts +99 -0
  210. package/dist/utils/colorUtils.d.ts.map +1 -0
  211. package/dist/utils/colorUtils.js +97 -2
  212. package/dist/utils/colorUtils.js.map +1 -0
  213. package/dist/utils/commandLine.d.ts +59 -0
  214. package/dist/utils/commandLine.d.ts.map +1 -0
  215. package/dist/utils/commandLine.js +54 -0
  216. package/dist/utils/commandLine.js.map +1 -0
  217. package/dist/utils/copyDirectory.d.ts +33 -0
  218. package/dist/utils/copyDirectory.d.ts.map +1 -0
  219. package/dist/utils/copyDirectory.js +38 -1
  220. package/dist/utils/copyDirectory.js.map +1 -0
  221. package/dist/utils/createDirectory.d.ts +34 -0
  222. package/dist/utils/createDirectory.d.ts.map +1 -0
  223. package/dist/utils/createDirectory.js +33 -0
  224. package/dist/utils/createDirectory.js.map +1 -0
  225. package/dist/utils/createZip.d.ts +39 -0
  226. package/dist/utils/createZip.d.ts.map +1 -0
  227. package/dist/utils/createZip.js +47 -2
  228. package/dist/utils/createZip.js.map +1 -0
  229. package/dist/utils/deepCopy.d.ts +32 -0
  230. package/dist/utils/deepCopy.d.ts.map +1 -0
  231. package/dist/utils/deepCopy.js +39 -0
  232. package/dist/utils/deepCopy.js.map +1 -0
  233. package/dist/utils/deepEqual.d.ts +54 -0
  234. package/dist/utils/deepEqual.d.ts.map +1 -0
  235. package/dist/utils/deepEqual.js +72 -1
  236. package/dist/utils/deepEqual.js.map +1 -0
  237. package/dist/utils/error.d.ts +44 -0
  238. package/dist/utils/error.d.ts.map +1 -0
  239. package/dist/utils/error.js +41 -0
  240. package/dist/utils/error.js.map +1 -0
  241. package/dist/utils/export.d.ts +12 -0
  242. package/dist/utils/export.d.ts.map +1 -0
  243. package/dist/utils/export.js +1 -0
  244. package/dist/utils/export.js.map +1 -0
  245. package/dist/utils/hex.d.ts +89 -0
  246. package/dist/utils/hex.d.ts.map +1 -0
  247. package/dist/utils/hex.js +124 -0
  248. package/dist/utils/hex.js.map +1 -0
  249. package/dist/utils/isvalid.d.ts +103 -0
  250. package/dist/utils/isvalid.d.ts.map +1 -0
  251. package/dist/utils/isvalid.js +101 -0
  252. package/dist/utils/isvalid.js.map +1 -0
  253. package/dist/utils/network.d.ts +84 -0
  254. package/dist/utils/network.d.ts.map +1 -0
  255. package/dist/utils/network.js +91 -5
  256. package/dist/utils/network.js.map +1 -0
  257. package/dist/utils/spawn.d.ts +33 -0
  258. package/dist/utils/spawn.d.ts.map +1 -0
  259. package/dist/utils/spawn.js +40 -0
  260. package/dist/utils/spawn.js.map +1 -0
  261. package/dist/utils/wait.d.ts +54 -0
  262. package/dist/utils/wait.d.ts.map +1 -0
  263. package/dist/utils/wait.js +60 -8
  264. package/dist/utils/wait.js.map +1 -0
  265. package/npm-shrinkwrap.json +2 -2
  266. package/package.json +2 -1
@@ -1,15 +1,43 @@
1
+ /**
2
+ * This file contains the class Matterbridge.
3
+ *
4
+ * @file matterbridge.ts
5
+ * @author Luca Liguori
6
+ * @created 2023-12-29
7
+ * @version 1.6.0
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2023, 2024, 2025 Luca Liguori.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ // Node.js modules
1
25
  import os from 'node:os';
2
26
  import path from 'node:path';
3
27
  import { promises as fs } from 'node:fs';
4
28
  import EventEmitter from 'node:events';
5
29
  import { inspect } from 'node:util';
30
+ // AnsiLogger module
6
31
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE } from 'node-ansi-logger';
32
+ // NodeStorage module
7
33
  import { NodeStorageManager } from 'node-persist-manager';
34
+ // @matter
8
35
  import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
9
36
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
10
37
  import { AggregatorEndpoint } from '@matter/main/endpoints';
11
38
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
12
39
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
40
+ // Matterbridge
13
41
  import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
14
42
  import { withTimeout, waiter, wait } from './utils/wait.js';
15
43
  import { dev, plg, typ } from './matterbridgeTypes.js';
@@ -19,6 +47,9 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
19
47
  import { bridge } from './matterbridgeDeviceTypes.js';
20
48
  import { Frontend } from './frontend.js';
21
49
  import { addVirtualDevices } from './helpers.js';
50
+ /**
51
+ * Represents the Matterbridge application.
52
+ */
22
53
  export class Matterbridge extends EventEmitter {
23
54
  systemInformation = {
24
55
  interfaceName: '',
@@ -67,7 +98,7 @@ export class Matterbridge extends EventEmitter {
67
98
  shellySysUpdate: false,
68
99
  shellyMainUpdate: false,
69
100
  profile: getParameter('profile'),
70
- loggerLevel: "info",
101
+ loggerLevel: "info" /* LogLevel.INFO */,
71
102
  fileLogger: false,
72
103
  matterLoggerLevel: MatterLogLevel.INFO,
73
104
  matterFileLogger: false,
@@ -95,15 +126,18 @@ export class Matterbridge extends EventEmitter {
95
126
  profile = getParameter('profile');
96
127
  shutdown = false;
97
128
  failCountLimit = hasParameter('shelly') ? 600 : 120;
98
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
129
+ // Matterbridge log files
130
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
99
131
  matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
100
132
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
101
133
  plugins = new PluginManager(this);
102
134
  devices = new DeviceManager(this);
103
135
  frontend = new Frontend(this);
136
+ // Matterbridge storage
104
137
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
105
138
  nodeStorage;
106
139
  nodeContext;
140
+ // Cleanup
107
141
  hasCleanupStarted = false;
108
142
  initialized = false;
109
143
  startMatterInterval;
@@ -117,19 +151,23 @@ export class Matterbridge extends EventEmitter {
117
151
  sigtermHandler;
118
152
  exceptionHandler;
119
153
  rejectionHandler;
154
+ // Matter environment
120
155
  environment = Environment.default;
156
+ // Matter storage
121
157
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
122
158
  matterStorageService;
123
159
  matterStorageManager;
124
160
  matterbridgeContext;
125
161
  controllerContext;
126
- mdnsInterface;
127
- ipv4address;
128
- ipv6address;
129
- port;
130
- passcode;
131
- discriminator;
132
- certification;
162
+ // Matter parameters
163
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
164
+ ipv4address; // matter server node listeningAddressIpv4
165
+ ipv6address; // matter server node listeningAddressIpv6
166
+ port; // first server node port
167
+ passcode; // first server node passcode
168
+ discriminator; // first server node discriminator
169
+ certification; // device certification
170
+ // Matter nodes
133
171
  serverNode;
134
172
  aggregatorNode;
135
173
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
@@ -140,15 +178,31 @@ export class Matterbridge extends EventEmitter {
140
178
  aggregatorSerialNumber = getParameter('serialNumber');
141
179
  aggregatorUniqueId = getParameter('uniqueId');
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,72 +277,105 @@ 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 wait(pause, 'destroyInstance start', true);
284
+ // Cleanup
207
285
  await this.cleanup('destroying instance...', false, timeout);
286
+ // Close servers mdns service
208
287
  this.log.info(`Dispose ${servers.length} MdnsService...`);
209
288
  for (const server of servers) {
210
289
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
211
290
  this.log.info(`Closed ${server.id} MdnsService`);
212
291
  }
292
+ // Let any already‐queued microtasks run first
213
293
  await Promise.resolve();
294
+ // Wait for the cleanup to finish
214
295
  await wait(pause, 'destroyInstance stop', true);
215
296
  }
297
+ /**
298
+ * Initializes the Matterbridge application.
299
+ *
300
+ * @remarks
301
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
302
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
303
+ * node version, registers signal handlers, initializes storage, and parses the command line.
304
+ *
305
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
306
+ */
216
307
  async initialize() {
308
+ // Emit the initialize_started event
217
309
  this.emit('initialize_started');
310
+ // Set the restart mode
218
311
  if (hasParameter('service'))
219
312
  this.restartMode = 'service';
220
313
  if (hasParameter('docker'))
221
314
  this.restartMode = 'docker';
315
+ // Set the matterbridge home directory
222
316
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
223
317
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
224
318
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
319
+ // Set the matterbridge directory
225
320
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
226
321
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
227
322
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
228
323
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
229
324
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
325
+ // Set the matterbridge plugin directory
230
326
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
231
327
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
232
328
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
329
+ // Set the matterbridge cert directory
233
330
  this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
234
331
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
235
332
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
333
+ // Set the matterbridge root directory
236
334
  const { fileURLToPath } = await import('node:url');
237
335
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
238
336
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
239
337
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
338
+ // Setup the matter environment
240
339
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
241
340
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
242
341
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
243
342
  this.environment.vars.set('runtime.signals', false);
244
343
  this.environment.vars.set('runtime.exitcode', false);
344
+ // Register process handlers
245
345
  this.registerProcessHandlers();
346
+ // Initialize nodeStorage and nodeContext
246
347
  try {
247
348
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
248
349
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
249
350
  this.log.debug('Creating node storage context for matterbridge');
250
351
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
352
+ // TODO: Remove this code when node-persist-manager is updated
353
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
251
354
  const keys = (await this.nodeStorage?.storage.keys());
252
355
  for (const key of keys) {
253
356
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
357
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
358
  await this.nodeStorage?.storage.get(key);
255
359
  }
256
360
  const storages = await this.nodeStorage.getStorageNames();
257
361
  for (const storage of storages) {
258
362
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
259
363
  const nodeContext = await this.nodeStorage?.createStorage(storage);
364
+ // TODO: Remove this code when node-persist-manager is updated
365
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
260
366
  const keys = (await nodeContext?.storage.keys());
261
367
  keys.forEach(async (key) => {
262
368
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
263
369
  await nodeContext?.get(key);
264
370
  });
265
371
  }
372
+ // Creating a backup of the node storage since it is not corrupted
266
373
  this.log.debug('Creating node storage backup...');
267
374
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
268
375
  this.log.debug('Created node storage backup');
269
376
  }
270
377
  catch (error) {
378
+ // Restoring the backup of the node storage since it is corrupted
271
379
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
272
380
  if (hasParameter('norestore')) {
273
381
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -281,14 +389,19 @@ export class Matterbridge extends EventEmitter {
281
389
  if (!this.nodeStorage || !this.nodeContext) {
282
390
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
283
391
  }
392
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
284
393
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
394
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
285
395
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
396
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
286
397
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
398
+ // Certificate management
287
399
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
288
400
  try {
289
401
  await fs.access(pairingFilePath, fs.constants.R_OK);
290
402
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
291
403
  const pairingFileJson = JSON.parse(pairingFileContent);
404
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
292
405
  if (isValidNumber(pairingFileJson.vendorId)) {
293
406
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
294
407
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -317,11 +430,13 @@ export class Matterbridge extends EventEmitter {
317
430
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
318
431
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
319
432
  }
433
+ // Override the passcode and discriminator if they are present in the pairing file
320
434
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
321
435
  this.passcode = pairingFileJson.passcode;
322
436
  this.discriminator = pairingFileJson.discriminator;
323
437
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
324
438
  }
439
+ // Set the certification for matter.js if it is present in the pairing file
325
440
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
326
441
  const { hexToBuffer } = await import('./utils/hex.js');
327
442
  this.certification = {
@@ -336,41 +451,44 @@ export class Matterbridge extends EventEmitter {
336
451
  catch (error) {
337
452
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
338
453
  }
454
+ // Store the passcode, discriminator and port in the node context
339
455
  await this.nodeContext.set('matterport', this.port);
340
456
  await this.nodeContext.set('matterpasscode', this.passcode);
341
457
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
342
458
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
459
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
343
460
  if (hasParameter('logger')) {
344
461
  const level = getParameter('logger');
345
462
  if (level === 'debug') {
346
- this.log.logLevel = "debug";
463
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
347
464
  }
348
465
  else if (level === 'info') {
349
- this.log.logLevel = "info";
466
+ this.log.logLevel = "info" /* LogLevel.INFO */;
350
467
  }
351
468
  else if (level === 'notice') {
352
- this.log.logLevel = "notice";
469
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
353
470
  }
354
471
  else if (level === 'warn') {
355
- this.log.logLevel = "warn";
472
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
356
473
  }
357
474
  else if (level === 'error') {
358
- this.log.logLevel = "error";
475
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
359
476
  }
360
477
  else if (level === 'fatal') {
361
- this.log.logLevel = "fatal";
478
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
362
479
  }
363
480
  else {
364
481
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
365
- this.log.logLevel = "info";
482
+ this.log.logLevel = "info" /* LogLevel.INFO */;
366
483
  }
367
484
  }
368
485
  else {
369
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
486
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
370
487
  }
371
488
  this.frontend.logLevel = this.log.logLevel;
372
489
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
373
490
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
491
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
374
492
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
375
493
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
376
494
  this.matterbridgeInformation.fileLogger = true;
@@ -379,6 +497,7 @@ export class Matterbridge extends EventEmitter {
379
497
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
380
498
  if (this.profile !== undefined)
381
499
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
500
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
382
501
  if (hasParameter('matterlogger')) {
383
502
  const level = getParameter('matterlogger');
384
503
  if (level === 'debug') {
@@ -409,7 +528,9 @@ export class Matterbridge extends EventEmitter {
409
528
  }
410
529
  Logger.format = MatterLogFormat.ANSI;
411
530
  Logger.setLogger('default', this.createMatterLogger());
531
+ // Logger.destinations.default.write = this.createMatterLogger();
412
532
  this.matterbridgeInformation.matterLoggerLevel = Logger.level;
533
+ // Create the file logger for matter.js (context: matterFileLog)
413
534
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
414
535
  this.matterbridgeInformation.matterFileLogger = true;
415
536
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -418,7 +539,9 @@ export class Matterbridge extends EventEmitter {
418
539
  });
419
540
  }
420
541
  this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
542
+ // Log network interfaces
421
543
  const networkInterfaces = os.networkInterfaces();
544
+ // console.log(`Network interfaces:`, networkInterfaces);
422
545
  const availableAddresses = Object.entries(networkInterfaces);
423
546
  const availableInterfaces = Object.keys(networkInterfaces);
424
547
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -430,6 +553,7 @@ export class Matterbridge extends EventEmitter {
430
553
  });
431
554
  }
432
555
  }
556
+ // Set the interface to use for matter server node mdnsInterface
433
557
  if (hasParameter('mdnsinterface')) {
434
558
  this.mdnsInterface = getParameter('mdnsinterface');
435
559
  }
@@ -438,6 +562,7 @@ export class Matterbridge extends EventEmitter {
438
562
  if (this.mdnsInterface === '')
439
563
  this.mdnsInterface = undefined;
440
564
  }
565
+ // Validate mdnsInterface
441
566
  if (this.mdnsInterface) {
442
567
  if (!availableInterfaces.includes(this.mdnsInterface)) {
443
568
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -450,6 +575,7 @@ export class Matterbridge extends EventEmitter {
450
575
  }
451
576
  if (this.mdnsInterface)
452
577
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
578
+ // Set the listeningAddressIpv4 for the matter commissioning server
453
579
  if (hasParameter('ipv4address')) {
454
580
  this.ipv4address = getParameter('ipv4address');
455
581
  }
@@ -458,6 +584,7 @@ export class Matterbridge extends EventEmitter {
458
584
  if (this.ipv4address === '')
459
585
  this.ipv4address = undefined;
460
586
  }
587
+ // Validate ipv4address
461
588
  if (this.ipv4address) {
462
589
  let isValid = false;
463
590
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -473,6 +600,7 @@ export class Matterbridge extends EventEmitter {
473
600
  await this.nodeContext.remove('matteripv4address');
474
601
  }
475
602
  }
603
+ // Set the listeningAddressIpv6 for the matter commissioning server
476
604
  if (hasParameter('ipv6address')) {
477
605
  this.ipv6address = getParameter('ipv6address');
478
606
  }
@@ -481,6 +609,7 @@ export class Matterbridge extends EventEmitter {
481
609
  if (this.ipv6address === '')
482
610
  this.ipv6address = undefined;
483
611
  }
612
+ // Validate ipv6address
484
613
  if (this.ipv6address) {
485
614
  let isValid = false;
486
615
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -489,6 +618,7 @@ export class Matterbridge extends EventEmitter {
489
618
  isValid = true;
490
619
  break;
491
620
  }
621
+ /* istanbul ignore next */
492
622
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
493
623
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
494
624
  isValid = true;
@@ -501,6 +631,7 @@ export class Matterbridge extends EventEmitter {
501
631
  await this.nodeContext.remove('matteripv6address');
502
632
  }
503
633
  }
634
+ // Initialize the virtual mode
504
635
  if (hasParameter('novirtual')) {
505
636
  this.matterbridgeInformation.virtualMode = 'disabled';
506
637
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -509,12 +640,17 @@ export class Matterbridge extends EventEmitter {
509
640
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
510
641
  }
511
642
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
643
+ // Initialize PluginManager
512
644
  this.plugins.logLevel = this.log.logLevel;
513
645
  await this.plugins.loadFromStorage();
646
+ // Initialize DeviceManager
514
647
  this.devices.logLevel = this.log.logLevel;
648
+ // Get the plugins from node storage and create the plugins node storage contexts
515
649
  for (const plugin of this.plugins) {
516
650
  const packageJson = await this.plugins.parse(plugin);
517
651
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
652
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
653
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
518
654
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
519
655
  try {
520
656
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -537,6 +673,7 @@ export class Matterbridge extends EventEmitter {
537
673
  await plugin.nodeContext.set('description', plugin.description);
538
674
  await plugin.nodeContext.set('author', plugin.author);
539
675
  }
676
+ // Log system info and create .matterbridge directory
540
677
  await this.logNodeAndSystemInfo();
541
678
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
542
679
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -544,6 +681,7 @@ export class Matterbridge extends EventEmitter {
544
681
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
545
682
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
546
683
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
684
+ // Check node version and throw error
547
685
  const minNodeVersion = 18;
548
686
  const nodeVersion = process.versions.node;
549
687
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -551,10 +689,18 @@ export class Matterbridge extends EventEmitter {
551
689
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
552
690
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
553
691
  }
692
+ // Parse command line
554
693
  await this.parseCommandLine();
694
+ // Emit the initialize_completed event
555
695
  this.emit('initialize_completed');
556
696
  this.initialized = true;
557
697
  }
698
+ /**
699
+ * Parses the command line arguments and performs the corresponding actions.
700
+ *
701
+ * @private
702
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
703
+ */
558
704
  async parseCommandLine() {
559
705
  if (hasParameter('help')) {
560
706
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -616,6 +762,19 @@ export class Matterbridge extends EventEmitter {
616
762
  }
617
763
  index++;
618
764
  }
765
+ /*
766
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
767
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
768
+ serializedRegisteredDevices?.forEach((device, index) => {
769
+ if (index !== serializedRegisteredDevices.length - 1) {
770
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
771
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
772
+ } else {
773
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
774
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
775
+ }
776
+ });
777
+ */
619
778
  this.shutdown = true;
620
779
  return;
621
780
  }
@@ -665,6 +824,7 @@ export class Matterbridge extends EventEmitter {
665
824
  this.shutdown = true;
666
825
  return;
667
826
  }
827
+ // Start the matter storage and create the matterbridge context
668
828
  try {
669
829
  await this.startMatterStorage();
670
830
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -681,18 +841,21 @@ export class Matterbridge extends EventEmitter {
681
841
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
682
842
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
683
843
  }
844
+ // Clear the matterbridge context if the reset parameter is set
684
845
  if (hasParameter('reset') && getParameter('reset') === undefined) {
685
846
  this.initialized = true;
686
847
  await this.shutdownProcessAndReset();
687
848
  this.shutdown = true;
688
849
  return;
689
850
  }
851
+ // Clear matterbridge plugin context if the reset parameter is set
690
852
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
691
853
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
692
854
  const plugin = this.plugins.get(getParameter('reset'));
693
855
  if (plugin) {
694
856
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
695
857
  if (!matterStorageManager) {
858
+ /* istanbul ignore next */
696
859
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
697
860
  }
698
861
  else {
@@ -711,37 +874,45 @@ export class Matterbridge extends EventEmitter {
711
874
  this.shutdown = true;
712
875
  return;
713
876
  }
877
+ // Initialize frontend
714
878
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
715
879
  await this.frontend.start(getIntParameter('frontend'));
880
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
716
881
  clearTimeout(this.checkUpdateTimeout);
717
882
  this.checkUpdateTimeout = setTimeout(async () => {
718
883
  const { checkUpdates } = await import('./update.js');
719
884
  checkUpdates(this);
720
885
  }, 30 * 1000).unref();
886
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
721
887
  clearInterval(this.checkUpdateInterval);
722
888
  this.checkUpdateInterval = setInterval(async () => {
723
889
  const { checkUpdates } = await import('./update.js');
724
890
  checkUpdates(this);
725
891
  }, 12 * 60 * 60 * 1000).unref();
892
+ // Start the matterbridge in mode test
726
893
  if (hasParameter('test')) {
727
894
  this.bridgeMode = 'bridge';
728
895
  return;
729
896
  }
897
+ // Start the matterbridge in mode controller
730
898
  if (hasParameter('controller')) {
731
899
  this.bridgeMode = 'controller';
732
900
  await this.startController();
733
901
  return;
734
902
  }
903
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
735
904
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
736
905
  this.log.info('Setting default matterbridge start mode to bridge');
737
906
  await this.nodeContext?.set('bridgeMode', 'bridge');
738
907
  }
908
+ // Start matterbridge in bridge mode
739
909
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
740
910
  this.bridgeMode = 'bridge';
741
911
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
742
912
  await this.startBridge();
743
913
  return;
744
914
  }
915
+ // Start matterbridge in childbridge mode
745
916
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
746
917
  this.bridgeMode = 'childbridge';
747
918
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -749,10 +920,20 @@ export class Matterbridge extends EventEmitter {
749
920
  return;
750
921
  }
751
922
  }
923
+ /**
924
+ * Asynchronously loads and starts the registered plugins.
925
+ *
926
+ * This method is responsible for initializing and starting all enabled plugins.
927
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
928
+ *
929
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
930
+ */
752
931
  async startPlugins() {
932
+ // Check, load and start the plugins
753
933
  for (const plugin of this.plugins) {
754
934
  plugin.configJson = await this.plugins.loadConfig(plugin);
755
935
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
936
+ // Check if the plugin is available
756
937
  if (!(await this.plugins.resolve(plugin.path))) {
757
938
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
758
939
  plugin.enabled = false;
@@ -770,10 +951,14 @@ export class Matterbridge extends EventEmitter {
770
951
  plugin.configured = false;
771
952
  plugin.registeredDevices = undefined;
772
953
  plugin.addedDevices = undefined;
773
- this.plugins.load(plugin, true, 'Matterbridge is starting');
954
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
774
955
  }
775
956
  this.frontend.wssSendRefreshRequired('plugins');
776
957
  }
958
+ /**
959
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
960
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
961
+ */
777
962
  registerProcessHandlers() {
778
963
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
779
964
  process.removeAllListeners('uncaughtException');
@@ -800,6 +985,9 @@ export class Matterbridge extends EventEmitter {
800
985
  };
801
986
  process.on('SIGTERM', this.sigtermHandler);
802
987
  }
988
+ /**
989
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
990
+ */
803
991
  deregisterProcessHandlers() {
804
992
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
805
993
  if (this.exceptionHandler)
@@ -816,12 +1004,17 @@ export class Matterbridge extends EventEmitter {
816
1004
  process.off('SIGTERM', this.sigtermHandler);
817
1005
  this.sigtermHandler = undefined;
818
1006
  }
1007
+ /**
1008
+ * Logs the node and system information.
1009
+ */
819
1010
  async logNodeAndSystemInfo() {
1011
+ // IP address information
820
1012
  const networkInterfaces = os.networkInterfaces();
821
1013
  this.systemInformation.interfaceName = '';
822
1014
  this.systemInformation.ipv4Address = '';
823
1015
  this.systemInformation.ipv6Address = '';
824
1016
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
1017
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
825
1018
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
826
1019
  continue;
827
1020
  if (!interfaceDetails) {
@@ -847,19 +1040,22 @@ export class Matterbridge extends EventEmitter {
847
1040
  break;
848
1041
  }
849
1042
  }
1043
+ // Node information
850
1044
  this.systemInformation.nodeVersion = process.versions.node;
851
1045
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
852
1046
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
853
1047
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1048
+ // Host system information
854
1049
  this.systemInformation.hostname = os.hostname();
855
1050
  this.systemInformation.user = os.userInfo().username;
856
- this.systemInformation.osType = os.type();
857
- this.systemInformation.osRelease = os.release();
858
- this.systemInformation.osPlatform = os.platform();
859
- this.systemInformation.osArch = os.arch();
860
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
861
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
862
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
1051
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1052
+ this.systemInformation.osRelease = os.release(); // Kernel version
1053
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1054
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
1055
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1056
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1057
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
1058
+ // Log the system information
863
1059
  this.log.debug('Host System Information:');
864
1060
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
865
1061
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -875,14 +1071,17 @@ export class Matterbridge extends EventEmitter {
875
1071
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
876
1072
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
877
1073
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1074
+ // Log directories
878
1075
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
879
1076
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
880
1077
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
881
1078
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
882
1079
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1080
+ // Global node_modules directory
883
1081
  if (this.nodeContext)
884
1082
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
885
1083
  if (this.globalModulesDirectory === '') {
1084
+ // First run of Matterbridge so the node storage is empty
886
1085
  this.log.debug(`Getting global node_modules directory...`);
887
1086
  try {
888
1087
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -895,6 +1094,7 @@ export class Matterbridge extends EventEmitter {
895
1094
  }
896
1095
  }
897
1096
  else {
1097
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
898
1098
  this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
899
1099
  try {
900
1100
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -906,50 +1106,68 @@ export class Matterbridge extends EventEmitter {
906
1106
  this.log.error(`Error checking global node_modules directory: ${error}`);
907
1107
  }
908
1108
  }
1109
+ // Matterbridge version
909
1110
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
910
1111
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
911
1112
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
912
1113
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1114
+ // Matterbridge latest version (will be set in the checkUpdate function)
913
1115
  if (this.nodeContext)
914
1116
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
915
1117
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1118
+ // Matterbridge dev version (will be set in the checkUpdate function)
916
1119
  if (this.nodeContext)
917
1120
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
918
1121
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1122
+ // Current working directory
919
1123
  const currentDir = process.cwd();
920
1124
  this.log.debug(`Current Working Directory: ${currentDir}`);
1125
+ // Command line arguments (excluding 'node' and the script name)
921
1126
  const cmdArgs = process.argv.slice(2).join(' ');
922
1127
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
923
1128
  }
1129
+ /**
1130
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1131
+ *
1132
+ * @returns {Function} The MatterLogger function.
1133
+ */
924
1134
  createMatterLogger() {
925
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1135
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
926
1136
  return (level, formattedLog) => {
927
1137
  const logger = formattedLog.slice(44, 44 + 20).trim();
928
1138
  const message = formattedLog.slice(65);
929
1139
  matterLogger.logName = logger;
930
1140
  switch (level) {
931
1141
  case MatterLogLevel.DEBUG:
932
- matterLogger.log("debug", message);
1142
+ matterLogger.log("debug" /* LogLevel.DEBUG */, message);
933
1143
  break;
934
1144
  case MatterLogLevel.INFO:
935
- matterLogger.log("info", message);
1145
+ matterLogger.log("info" /* LogLevel.INFO */, message);
936
1146
  break;
937
1147
  case MatterLogLevel.NOTICE:
938
- matterLogger.log("notice", message);
1148
+ matterLogger.log("notice" /* LogLevel.NOTICE */, message);
939
1149
  break;
940
1150
  case MatterLogLevel.WARN:
941
- matterLogger.log("warn", message);
1151
+ matterLogger.log("warn" /* LogLevel.WARN */, message);
942
1152
  break;
943
1153
  case MatterLogLevel.ERROR:
944
- matterLogger.log("error", message);
1154
+ matterLogger.log("error" /* LogLevel.ERROR */, message);
945
1155
  break;
946
1156
  case MatterLogLevel.FATAL:
947
- matterLogger.log("fatal", message);
1157
+ matterLogger.log("fatal" /* LogLevel.FATAL */, message);
948
1158
  break;
949
1159
  }
950
1160
  };
951
1161
  }
1162
+ /**
1163
+ * Creates a Matter File Logger.
1164
+ *
1165
+ * @param {string} filePath - The path to the log file.
1166
+ * @param {boolean} [unlink] - Whether to unlink the log file before creating a new one.
1167
+ * @returns {Function} - A function that logs formatted messages to the log file.
1168
+ */
952
1169
  async createMatterFileLogger(filePath, unlink = false) {
1170
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
953
1171
  let fileSize = 0;
954
1172
  if (unlink) {
955
1173
  try {
@@ -960,10 +1178,12 @@ export class Matterbridge extends EventEmitter {
960
1178
  }
961
1179
  }
962
1180
  return async (level, formattedLog) => {
1181
+ /* istanbul ignore if */
963
1182
  if (fileSize > 100000000) {
964
- return;
1183
+ return; // Stop logging if the file size is greater than 100MB
965
1184
  }
966
1185
  fileSize += formattedLog.length;
1186
+ /* istanbul ignore if */
967
1187
  if (fileSize > 100000000) {
968
1188
  await fs.appendFile(filePath, `Logging on file has been stopped because the file size is greater than 100MB.` + os.EOL);
969
1189
  return;
@@ -996,12 +1216,27 @@ export class Matterbridge extends EventEmitter {
996
1216
  }
997
1217
  };
998
1218
  }
1219
+ /**
1220
+ * Restarts the process by exiting the current instance and loading a new instance (/api/restart).
1221
+ *
1222
+ * @returns {Promise<void>} A promise that resolves when the restart is completed.
1223
+ */
999
1224
  async restartProcess() {
1000
1225
  await this.cleanup('restarting...', true);
1001
1226
  }
1227
+ /**
1228
+ * Shut down the process (/api/shutdown).
1229
+ *
1230
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1231
+ */
1002
1232
  async shutdownProcess() {
1003
1233
  await this.cleanup('shutting down...', false);
1004
1234
  }
1235
+ /**
1236
+ * Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
1237
+ *
1238
+ * @returns {Promise<void>} A promise that resolves when the update is completed.
1239
+ */
1005
1240
  async updateProcess() {
1006
1241
  this.log.info('Updating matterbridge...');
1007
1242
  try {
@@ -1015,6 +1250,13 @@ export class Matterbridge extends EventEmitter {
1015
1250
  this.frontend.wssSendRestartRequired();
1016
1251
  await this.cleanup('updating...', false);
1017
1252
  }
1253
+ /**
1254
+ * Unregister all devices and shut down the process (/api/unregister).
1255
+ *
1256
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1257
+ *
1258
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1259
+ */
1018
1260
  async unregisterAndShutdownProcess(timeout = 1000) {
1019
1261
  this.log.info('Unregistering all devices and shutting down...');
1020
1262
  for (const plugin of this.plugins.array()) {
@@ -1028,46 +1270,71 @@ export class Matterbridge extends EventEmitter {
1028
1270
  await this.removeAllBridgedEndpoints(plugin.name, 100);
1029
1271
  }
1030
1272
  this.log.debug('Waiting for the MessageExchange to finish...');
1031
- await wait(timeout);
1273
+ await wait(timeout); // Wait for MessageExchange to finish
1032
1274
  this.log.debug('Cleaning up and shutting down...');
1033
1275
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
1034
1276
  }
1277
+ /**
1278
+ * Reset commissioning and shut down the process (/api/reset).
1279
+ *
1280
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1281
+ */
1035
1282
  async shutdownProcessAndReset() {
1036
1283
  await this.cleanup('shutting down with reset...', false);
1037
1284
  }
1285
+ /**
1286
+ * Factory reset and shut down the process (/api/factory-reset).
1287
+ *
1288
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1289
+ */
1038
1290
  async shutdownProcessAndFactoryReset() {
1039
1291
  await this.cleanup('shutting down with factory reset...', false);
1040
1292
  }
1293
+ /**
1294
+ * Cleans up the Matterbridge instance.
1295
+ *
1296
+ * @param {string} message - The cleanup message.
1297
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1298
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1299
+ *
1300
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1301
+ */
1041
1302
  async cleanup(message, restart = false, timeout = 1000) {
1042
1303
  if (this.initialized && !this.hasCleanupStarted) {
1043
1304
  this.emit('cleanup_started');
1044
1305
  this.hasCleanupStarted = true;
1045
1306
  this.log.info(message);
1307
+ // Clear the start matter interval
1046
1308
  if (this.startMatterInterval) {
1047
1309
  clearInterval(this.startMatterInterval);
1048
1310
  this.startMatterInterval = undefined;
1049
1311
  this.log.debug('Start matter interval cleared');
1050
1312
  }
1313
+ // Clear the check update timeout
1051
1314
  if (this.checkUpdateTimeout) {
1052
1315
  clearTimeout(this.checkUpdateTimeout);
1053
1316
  this.checkUpdateTimeout = undefined;
1054
1317
  this.log.debug('Check update timeout cleared');
1055
1318
  }
1319
+ // Clear the check update interval
1056
1320
  if (this.checkUpdateInterval) {
1057
1321
  clearInterval(this.checkUpdateInterval);
1058
1322
  this.checkUpdateInterval = undefined;
1059
1323
  this.log.debug('Check update interval cleared');
1060
1324
  }
1325
+ // Clear the configure timeout
1061
1326
  if (this.configureTimeout) {
1062
1327
  clearTimeout(this.configureTimeout);
1063
1328
  this.configureTimeout = undefined;
1064
1329
  this.log.debug('Matterbridge configure timeout cleared');
1065
1330
  }
1331
+ // Clear the reachability timeout
1066
1332
  if (this.reachabilityTimeout) {
1067
1333
  clearTimeout(this.reachabilityTimeout);
1068
1334
  this.reachabilityTimeout = undefined;
1069
1335
  this.log.debug('Matterbridge reachability timeout cleared');
1070
1336
  }
1337
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
1071
1338
  for (const plugin of this.plugins) {
1072
1339
  if (!plugin.enabled || plugin.error)
1073
1340
  continue;
@@ -1078,6 +1345,7 @@ export class Matterbridge extends EventEmitter {
1078
1345
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1079
1346
  }
1080
1347
  }
1348
+ // Stop matter server nodes
1081
1349
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1082
1350
  this.log.debug('Waiting for the MessageExchange to finish...');
1083
1351
  await wait(timeout, 'Waiting for the MessageExchange to finish...', true);
@@ -1102,6 +1370,7 @@ export class Matterbridge extends EventEmitter {
1102
1370
  }
1103
1371
  }
1104
1372
  this.log.notice('Stopped matter server nodes');
1373
+ // Matter commisioning reset
1105
1374
  if (message === 'shutting down with reset...') {
1106
1375
  this.log.info('Resetting Matterbridge commissioning information...');
1107
1376
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1111,6 +1380,7 @@ export class Matterbridge extends EventEmitter {
1111
1380
  await this.matterbridgeContext?.clearAll();
1112
1381
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1113
1382
  }
1383
+ // Unregister all devices
1114
1384
  if (message === 'unregistered all devices and shutting down...') {
1115
1385
  if (this.bridgeMode === 'bridge') {
1116
1386
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1128,18 +1398,36 @@ export class Matterbridge extends EventEmitter {
1128
1398
  }
1129
1399
  this.log.info('Matter storage reset done!');
1130
1400
  }
1401
+ // Stop matter storage
1131
1402
  await this.stopMatterStorage();
1403
+ // Stop the frontend
1132
1404
  await this.frontend.stop();
1405
+ // Remove the matterfilelogger
1133
1406
  try {
1134
1407
  Logger.removeLogger('matterfilelogger');
1135
1408
  }
1136
1409
  catch (error) {
1137
1410
  this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
1138
1411
  }
1412
+ // Close the matterbridge node storage and context
1139
1413
  if (this.nodeStorage && this.nodeContext) {
1414
+ /*
1415
+ TODO: Implement serialization of registered devices in edge mode
1416
+ this.log.info('Saving registered devices...');
1417
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1418
+ this.devices.forEach(async (device) => {
1419
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1420
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1421
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1422
+ });
1423
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1424
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1425
+ */
1426
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1140
1427
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1141
1428
  await this.nodeContext.close();
1142
1429
  this.nodeContext = undefined;
1430
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1143
1431
  for (const plugin of this.plugins) {
1144
1432
  if (plugin.nodeContext) {
1145
1433
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1156,8 +1444,10 @@ export class Matterbridge extends EventEmitter {
1156
1444
  }
1157
1445
  this.plugins.clear();
1158
1446
  this.devices.clear();
1447
+ // Factory reset
1159
1448
  if (message === 'shutting down with factory reset...') {
1160
1449
  try {
1450
+ // Delete matter storage directory with its subdirectories and backup
1161
1451
  const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
1162
1452
  this.log.info(`Removing matter storage directory: ${dir}`);
1163
1453
  await fs.rm(dir, { recursive: true });
@@ -1166,11 +1456,13 @@ export class Matterbridge extends EventEmitter {
1166
1456
  await fs.rm(backup, { recursive: true });
1167
1457
  }
1168
1458
  catch (error) {
1459
+ // istanbul ignore next if
1169
1460
  if (error instanceof Error && error.code !== 'ENOENT') {
1170
1461
  this.log.error(`Error removing matter storage directory: ${error}`);
1171
1462
  }
1172
1463
  }
1173
1464
  try {
1465
+ // Delete matterbridge storage directory with its subdirectories and backup
1174
1466
  const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
1175
1467
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1176
1468
  await fs.rm(dir, { recursive: true });
@@ -1179,18 +1471,20 @@ export class Matterbridge extends EventEmitter {
1179
1471
  await fs.rm(backup, { recursive: true });
1180
1472
  }
1181
1473
  catch (error) {
1474
+ // istanbul ignore next if
1182
1475
  if (error instanceof Error && error.code !== 'ENOENT') {
1183
1476
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1184
1477
  }
1185
1478
  }
1186
1479
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1187
1480
  }
1481
+ // Deregisters the process handlers
1188
1482
  this.deregisterProcessHandlers();
1189
1483
  if (restart) {
1190
1484
  if (message === 'updating...') {
1191
1485
  this.log.info('Cleanup completed. Updating...');
1192
1486
  Matterbridge.instance = undefined;
1193
- this.emit('update');
1487
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1194
1488
  }
1195
1489
  else if (message === 'restarting...') {
1196
1490
  this.log.info('Cleanup completed. Restarting...');
@@ -1211,6 +1505,13 @@ export class Matterbridge extends EventEmitter {
1211
1505
  this.log.debug('Cleanup already started...');
1212
1506
  }
1213
1507
  }
1508
+ /**
1509
+ * Creates and configures the server node for a single not bridged device.
1510
+ *
1511
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1512
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1513
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1514
+ */
1214
1515
  async createDeviceServerNode(plugin, device) {
1215
1516
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1216
1517
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1221,6 +1522,13 @@ export class Matterbridge extends EventEmitter {
1221
1522
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1222
1523
  }
1223
1524
  }
1525
+ /**
1526
+ * Creates and configures the server node for an accessory plugin for a given device.
1527
+ *
1528
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1529
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1530
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1531
+ */
1224
1532
  async createAccessoryPlugin(plugin, device) {
1225
1533
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1226
1534
  plugin.locked = true;
@@ -1232,6 +1540,12 @@ export class Matterbridge extends EventEmitter {
1232
1540
  await plugin.serverNode.add(device);
1233
1541
  }
1234
1542
  }
1543
+ /**
1544
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
1545
+ *
1546
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1547
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
1548
+ */
1235
1549
  async createDynamicPlugin(plugin) {
1236
1550
  if (!plugin.locked) {
1237
1551
  plugin.locked = true;
@@ -1242,7 +1556,14 @@ export class Matterbridge extends EventEmitter {
1242
1556
  await plugin.serverNode.add(plugin.aggregatorNode);
1243
1557
  }
1244
1558
  }
1559
+ /**
1560
+ * Starts the Matterbridge in bridge mode.
1561
+ *
1562
+ * @private
1563
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1564
+ */
1245
1565
  async startBridge() {
1566
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1246
1567
  if (!this.matterStorageManager)
1247
1568
  throw new Error('No storage manager initialized');
1248
1569
  if (!this.matterbridgeContext)
@@ -1281,13 +1602,16 @@ export class Matterbridge extends EventEmitter {
1281
1602
  clearInterval(this.startMatterInterval);
1282
1603
  this.startMatterInterval = undefined;
1283
1604
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1284
- this.startServerNode(this.serverNode);
1605
+ // Start the Matter server node
1606
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1607
+ // Start the Matter server node of single devices in mode 'server'
1285
1608
  for (const device of this.devices.array()) {
1286
1609
  if (device.mode === 'server' && device.serverNode) {
1287
1610
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1288
- this.startServerNode(device.serverNode);
1611
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1289
1612
  }
1290
1613
  }
1614
+ // Configure the plugins
1291
1615
  this.configureTimeout = setTimeout(async () => {
1292
1616
  for (const plugin of this.plugins.array()) {
1293
1617
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1305,16 +1629,25 @@ export class Matterbridge extends EventEmitter {
1305
1629
  }
1306
1630
  this.frontend.wssSendRefreshRequired('plugins');
1307
1631
  }, 30 * 1000).unref();
1632
+ // Setting reachability to true
1308
1633
  this.reachabilityTimeout = setTimeout(() => {
1309
1634
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1310
1635
  if (this.aggregatorNode)
1311
1636
  this.setAggregatorReachability(this.aggregatorNode, true);
1312
1637
  this.frontend.wssSendRefreshRequired('reachability');
1313
1638
  }, 60 * 1000).unref();
1639
+ // Logger.get('LogServerNode').info(this.serverNode);
1314
1640
  this.emit('bridge_started');
1315
1641
  this.log.notice('Matterbridge bridge started successfully');
1316
1642
  }, this.startMatterIntervalMs);
1317
1643
  }
1644
+ /**
1645
+ * Starts the Matterbridge in childbridge mode.
1646
+ *
1647
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1648
+ *
1649
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1650
+ */
1318
1651
  async startChildbridge(delay = 1000) {
1319
1652
  if (!this.matterStorageManager)
1320
1653
  throw new Error('No storage manager initialized');
@@ -1352,8 +1685,9 @@ export class Matterbridge extends EventEmitter {
1352
1685
  clearInterval(this.startMatterInterval);
1353
1686
  this.startMatterInterval = undefined;
1354
1687
  if (delay > 0)
1355
- await wait(delay);
1688
+ await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1356
1689
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1690
+ // Configure the plugins
1357
1691
  this.configureTimeout = setTimeout(async () => {
1358
1692
  for (const plugin of this.plugins.array()) {
1359
1693
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1390,7 +1724,9 @@ export class Matterbridge extends EventEmitter {
1390
1724
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1391
1725
  continue;
1392
1726
  }
1393
- this.startServerNode(plugin.serverNode);
1727
+ // Start the Matter server node
1728
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1729
+ // Setting reachability to true
1394
1730
  plugin.reachabilityTimeout = setTimeout(() => {
1395
1731
  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}`);
1396
1732
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1398,19 +1734,241 @@ export class Matterbridge extends EventEmitter {
1398
1734
  this.frontend.wssSendRefreshRequired('reachability');
1399
1735
  }, 60 * 1000).unref();
1400
1736
  }
1737
+ // Start the Matter server node of single devices in mode 'server'
1401
1738
  for (const device of this.devices.array()) {
1402
1739
  if (device.mode === 'server' && device.serverNode) {
1403
1740
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1404
- this.startServerNode(device.serverNode);
1741
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1405
1742
  }
1406
1743
  }
1744
+ // Logger.get('LogServerNode').info(this.serverNode);
1407
1745
  this.emit('childbridge_started');
1408
1746
  this.log.notice('Matterbridge childbridge started successfully');
1409
1747
  }, this.startMatterIntervalMs);
1410
1748
  }
1749
+ /**
1750
+ * Starts the Matterbridge controller.
1751
+ *
1752
+ * @private
1753
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1754
+ */
1411
1755
  async startController() {
1756
+ /*
1757
+ if (!this.matterStorageManager) {
1758
+ this.log.error('No storage manager initialized');
1759
+ await this.cleanup('No storage manager initialized');
1760
+ return;
1761
+ }
1762
+ this.log.info('Creating context: mattercontrollerContext');
1763
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1764
+ if (!this.controllerContext) {
1765
+ this.log.error('No storage context mattercontrollerContext initialized');
1766
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1767
+ return;
1768
+ }
1769
+
1770
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1771
+ this.matterServer = await this.createMatterServer(this.storageManager);
1772
+ this.log.info('Creating matter commissioning controller');
1773
+ this.commissioningController = new CommissioningController({
1774
+ autoConnect: false,
1775
+ });
1776
+ this.log.info('Adding matter commissioning controller to matter server');
1777
+ await this.matterServer.addCommissioningController(this.commissioningController);
1778
+
1779
+ this.log.info('Starting matter server');
1780
+ await this.matterServer.start();
1781
+ this.log.info('Matter server started');
1782
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1783
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1784
+ regulatoryCountryCode: 'XX',
1785
+ };
1786
+ const commissioningController = new CommissioningController({
1787
+ environment: {
1788
+ environment,
1789
+ id: uniqueId,
1790
+ },
1791
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1792
+ adminFabricLabel,
1793
+ });
1794
+
1795
+ if (hasParameter('pairingcode')) {
1796
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1797
+ const pairingCode = getParameter('pairingcode');
1798
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1799
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1800
+
1801
+ let longDiscriminator, setupPin, shortDiscriminator;
1802
+ if (pairingCode !== undefined) {
1803
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1804
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1805
+ longDiscriminator = undefined;
1806
+ setupPin = pairingCodeCodec.passcode;
1807
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1808
+ } else {
1809
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1810
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1811
+ setupPin = this.controllerContext.get('pin', 20202021);
1812
+ }
1813
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1814
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1815
+ }
1816
+
1817
+ const options = {
1818
+ commissioning: commissioningOptions,
1819
+ discovery: {
1820
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1821
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1822
+ },
1823
+ passcode: setupPin,
1824
+ } as NodeCommissioningOptions;
1825
+ this.log.info('Commissioning with options:', options);
1826
+ const nodeId = await this.commissioningController.commissionNode(options);
1827
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1828
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1829
+ } // (hasParameter('pairingcode'))
1830
+
1831
+ if (hasParameter('unpairall')) {
1832
+ this.log.info('***Commissioning controller unpairing all nodes...');
1833
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1834
+ for (const nodeId of nodeIds) {
1835
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1836
+ await this.commissioningController.removeNode(nodeId);
1837
+ }
1838
+ return;
1839
+ }
1840
+
1841
+ if (hasParameter('discover')) {
1842
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1843
+ // console.log(discover);
1844
+ }
1845
+
1846
+ if (!this.commissioningController.isCommissioned()) {
1847
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1848
+ return;
1849
+ }
1850
+
1851
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1852
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1853
+ for (const nodeId of nodeIds) {
1854
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1855
+
1856
+ const node = await this.commissioningController.connectNode(nodeId, {
1857
+ autoSubscribe: false,
1858
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1859
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1860
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1861
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1862
+ stateInformationCallback: (peerNodeId, info) => {
1863
+ switch (info) {
1864
+ case NodeStateInformation.Connected:
1865
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1866
+ break;
1867
+ case NodeStateInformation.Disconnected:
1868
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1869
+ break;
1870
+ case NodeStateInformation.Reconnecting:
1871
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1872
+ break;
1873
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1874
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1875
+ break;
1876
+ case NodeStateInformation.StructureChanged:
1877
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1878
+ break;
1879
+ case NodeStateInformation.Decommissioned:
1880
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1881
+ break;
1882
+ default:
1883
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1884
+ break;
1885
+ }
1886
+ },
1887
+ });
1888
+
1889
+ node.logStructure();
1890
+
1891
+ // Get the interaction client
1892
+ this.log.info('Getting the interaction client');
1893
+ const interactionClient = await node.getInteractionClient();
1894
+ let cluster;
1895
+ let attributes;
1896
+
1897
+ // Log BasicInformationCluster
1898
+ cluster = BasicInformationCluster;
1899
+ attributes = await interactionClient.getMultipleAttributes({
1900
+ attributes: [{ clusterId: cluster.id }],
1901
+ });
1902
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1903
+ attributes.forEach((attribute) => {
1904
+ this.log.info(
1905
+ `- 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}`,
1906
+ );
1907
+ });
1908
+
1909
+ // Log PowerSourceCluster
1910
+ cluster = PowerSourceCluster;
1911
+ attributes = await interactionClient.getMultipleAttributes({
1912
+ attributes: [{ clusterId: cluster.id }],
1913
+ });
1914
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1915
+ attributes.forEach((attribute) => {
1916
+ this.log.info(
1917
+ `- 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}`,
1918
+ );
1919
+ });
1920
+
1921
+ // Log ThreadNetworkDiagnostics
1922
+ cluster = ThreadNetworkDiagnosticsCluster;
1923
+ attributes = await interactionClient.getMultipleAttributes({
1924
+ attributes: [{ clusterId: cluster.id }],
1925
+ });
1926
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1927
+ attributes.forEach((attribute) => {
1928
+ this.log.info(
1929
+ `- 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}`,
1930
+ );
1931
+ });
1932
+
1933
+ // Log SwitchCluster
1934
+ cluster = SwitchCluster;
1935
+ attributes = await interactionClient.getMultipleAttributes({
1936
+ attributes: [{ clusterId: cluster.id }],
1937
+ });
1938
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1939
+ attributes.forEach((attribute) => {
1940
+ this.log.info(
1941
+ `- 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}`,
1942
+ );
1943
+ });
1944
+
1945
+ this.log.info('Subscribing to all attributes and events');
1946
+ await node.subscribeAllAttributesAndEvents({
1947
+ ignoreInitialTriggers: false,
1948
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1949
+ this.log.info(
1950
+ `***${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}`,
1951
+ ),
1952
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1953
+ this.log.info(
1954
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1955
+ );
1956
+ },
1957
+ });
1958
+ this.log.info('Subscribed to all attributes and events');
1959
+ }
1960
+ */
1412
1961
  }
1962
+ /** */
1963
+ /** Matter.js methods */
1964
+ /** */
1965
+ /**
1966
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1967
+ *
1968
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1969
+ */
1413
1970
  async startMatterStorage() {
1971
+ // Setup Matter storage
1414
1972
  this.log.info(`Starting matter node storage...`);
1415
1973
  this.matterStorageService = this.environment.get(StorageService);
1416
1974
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1419,8 +1977,17 @@ export class Matterbridge extends EventEmitter {
1419
1977
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1420
1978
  this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
1421
1979
  this.log.info('Matter node storage started');
1980
+ // Backup matter storage since it is created/opened correctly
1422
1981
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1423
1982
  }
1983
+ /**
1984
+ * Makes a backup copy of the specified matter storage directory.
1985
+ *
1986
+ * @param {string} storageName - The name of the storage directory to be backed up.
1987
+ * @param {string} backupName - The name of the backup directory to be created.
1988
+ * @private
1989
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1990
+ */
1424
1991
  async backupMatterStorage(storageName, backupName) {
1425
1992
  this.log.info('Creating matter node storage backup...');
1426
1993
  try {
@@ -1431,6 +1998,11 @@ export class Matterbridge extends EventEmitter {
1431
1998
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1432
1999
  }
1433
2000
  }
2001
+ /**
2002
+ * Stops the matter storage.
2003
+ *
2004
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
2005
+ */
1434
2006
  async stopMatterStorage() {
1435
2007
  this.log.info('Closing matter node storage...');
1436
2008
  await this.matterStorageManager?.close();
@@ -1439,6 +2011,20 @@ export class Matterbridge extends EventEmitter {
1439
2011
  this.matterbridgeContext = undefined;
1440
2012
  this.log.info('Matter node storage closed');
1441
2013
  }
2014
+ /**
2015
+ * Creates a server node storage context.
2016
+ *
2017
+ * @param {string} pluginName - The name of the plugin.
2018
+ * @param {string} deviceName - The name of the device.
2019
+ * @param {DeviceTypeId} deviceType - The device type of the device.
2020
+ * @param {number} vendorId - The vendor ID.
2021
+ * @param {string} vendorName - The vendor name.
2022
+ * @param {number} productId - The product ID.
2023
+ * @param {string} productName - The product name.
2024
+ * @param {string} [serialNumber] - The serial number of the device (optional).
2025
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
2026
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
2027
+ */
1442
2028
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1443
2029
  const { randomBytes } = await import('node:crypto');
1444
2030
  if (!this.matterStorageService)
@@ -1472,6 +2058,15 @@ export class Matterbridge extends EventEmitter {
1472
2058
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1473
2059
  return storageContext;
1474
2060
  }
2061
+ /**
2062
+ * Creates a server node.
2063
+ *
2064
+ * @param {StorageContext} storageContext - The storage context for the server node.
2065
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
2066
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
2067
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
2068
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
2069
+ */
1475
2070
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1476
2071
  const storeId = await storageContext.get('storeId');
1477
2072
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1481,24 +2076,37 @@ export class Matterbridge extends EventEmitter {
1481
2076
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1482
2077
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1483
2078
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2079
+ /**
2080
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
2081
+ */
1484
2082
  const serverNode = await ServerNode.create({
2083
+ // Required: Give the Node a unique ID which is used to store the state of this node
1485
2084
  id: storeId,
2085
+ // Provide Network relevant configuration like the port
2086
+ // Optional when operating only one device on a host, Default port is 5540
1486
2087
  network: {
1487
2088
  listeningAddressIpv4: this.ipv4address,
1488
2089
  listeningAddressIpv6: this.ipv6address,
1489
2090
  port,
1490
2091
  },
2092
+ // Provide the certificate for the device
1491
2093
  operationalCredentials: {
1492
2094
  certification: this.certification,
1493
2095
  },
2096
+ // Provide Commissioning relevant settings
2097
+ // Optional for development/testing purposes
1494
2098
  commissioning: {
1495
2099
  passcode,
1496
2100
  discriminator,
1497
2101
  },
2102
+ // Provide Node announcement settings
2103
+ // Optional: If Ommitted some development defaults are used
1498
2104
  productDescription: {
1499
2105
  name: await storageContext.get('deviceName'),
1500
2106
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1501
2107
  },
2108
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
2109
+ // Optional: If Omitted some development defaults are used
1502
2110
  basicInformation: {
1503
2111
  vendorId: VendorId(await storageContext.get('vendorId')),
1504
2112
  vendorName: await storageContext.get('vendorName'),
@@ -1515,14 +2123,20 @@ export class Matterbridge extends EventEmitter {
1515
2123
  reachable: true,
1516
2124
  },
1517
2125
  });
2126
+ /**
2127
+ * This event is triggered when the device is initially commissioned successfully.
2128
+ * This means: It is added to the first fabric.
2129
+ */
1518
2130
  serverNode.lifecycle.commissioned.on(() => {
1519
2131
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1520
2132
  clearTimeout(this.endAdvertiseTimeout);
1521
2133
  });
2134
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1522
2135
  serverNode.lifecycle.decommissioned.on(() => {
1523
2136
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1524
2137
  clearTimeout(this.endAdvertiseTimeout);
1525
2138
  });
2139
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1526
2140
  serverNode.lifecycle.online.on(async () => {
1527
2141
  this.log.notice(`Server node for ${storeId} is online`);
1528
2142
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1530,9 +2144,11 @@ export class Matterbridge extends EventEmitter {
1530
2144
  const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
1531
2145
  this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
1532
2146
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
2147
+ // Set a timeout to show that advertising stops after 15 minutes if not commissioned
1533
2148
  this.startEndAdvertiseTimer(serverNode);
1534
2149
  }
1535
2150
  else {
2151
+ // istanbul ignore next
1536
2152
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
1537
2153
  }
1538
2154
  this.frontend.wssSendRefreshRequired('plugins');
@@ -1540,14 +2156,19 @@ export class Matterbridge extends EventEmitter {
1540
2156
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1541
2157
  this.emit('online', storeId);
1542
2158
  });
2159
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1543
2160
  serverNode.lifecycle.offline.on(() => {
1544
2161
  this.log.notice(`Server node for ${storeId} is offline`);
1545
- this.matterbridgeInformation.matterbridgeEndAdvertise = true;
2162
+ this.matterbridgeInformation.matterbridgeEndAdvertise = true; // Set the end advertise flag to true, so the frontend won't show the QR code anymore
1546
2163
  this.frontend.wssSendRefreshRequired('plugins');
1547
2164
  this.frontend.wssSendRefreshRequired('settings');
1548
2165
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1549
2166
  this.emit('offline', storeId);
1550
2167
  });
2168
+ /**
2169
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2170
+ * information is needed.
2171
+ */
1551
2172
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1552
2173
  let action = '';
1553
2174
  switch (fabricAction) {
@@ -1564,14 +2185,22 @@ export class Matterbridge extends EventEmitter {
1564
2185
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1565
2186
  this.frontend.wssSendRefreshRequired('fabrics');
1566
2187
  });
2188
+ /**
2189
+ * This event is triggered when an operative new session was opened by a Controller.
2190
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2191
+ */
1567
2192
  serverNode.events.sessions.opened.on((session) => {
1568
2193
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1569
2194
  this.frontend.wssSendRefreshRequired('sessions');
1570
2195
  });
2196
+ /**
2197
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2198
+ */
1571
2199
  serverNode.events.sessions.closed.on((session) => {
1572
2200
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1573
2201
  this.frontend.wssSendRefreshRequired('sessions');
1574
2202
  });
2203
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1575
2204
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1576
2205
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1577
2206
  this.frontend.wssSendRefreshRequired('sessions');
@@ -1579,6 +2208,11 @@ export class Matterbridge extends EventEmitter {
1579
2208
  this.log.info(`Created server node for ${storeId}`);
1580
2209
  return serverNode;
1581
2210
  }
2211
+ /**
2212
+ * Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
2213
+ *
2214
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2215
+ */
1582
2216
  startEndAdvertiseTimer(matterServerNode) {
1583
2217
  if (this.endAdvertiseTimeout) {
1584
2218
  this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
@@ -1597,12 +2231,25 @@ export class Matterbridge extends EventEmitter {
1597
2231
  this.log.notice(`Advertising stopped. Restart to commission again.`);
1598
2232
  }, 15 * 60 * 1000).unref();
1599
2233
  }
2234
+ /**
2235
+ * Starts the specified server node.
2236
+ *
2237
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2238
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2239
+ */
1600
2240
  async startServerNode(matterServerNode) {
1601
2241
  if (!matterServerNode)
1602
2242
  return;
1603
2243
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1604
2244
  await matterServerNode.start();
1605
2245
  }
2246
+ /**
2247
+ * Stops the specified server node.
2248
+ *
2249
+ * @param {ServerNode} matterServerNode - The server node to stop.
2250
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2251
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2252
+ */
1606
2253
  async stopServerNode(matterServerNode, timeout = 30000) {
1607
2254
  if (!matterServerNode)
1608
2255
  return;
@@ -1615,6 +2262,12 @@ export class Matterbridge extends EventEmitter {
1615
2262
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1616
2263
  }
1617
2264
  }
2265
+ /**
2266
+ * Advertises the specified server node.
2267
+ *
2268
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2269
+ * @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.
2270
+ */
1618
2271
  async advertiseServerNode(matterServerNode) {
1619
2272
  if (matterServerNode) {
1620
2273
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1623,19 +2276,39 @@ export class Matterbridge extends EventEmitter {
1623
2276
  return { qrPairingCode, manualPairingCode };
1624
2277
  }
1625
2278
  }
2279
+ /**
2280
+ * Stop advertise the specified server node.
2281
+ *
2282
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2283
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2284
+ */
1626
2285
  async stopAdvertiseServerNode(matterServerNode) {
1627
2286
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1628
2287
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
1629
2288
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1630
2289
  }
1631
2290
  }
2291
+ /**
2292
+ * Creates an aggregator node with the specified storage context.
2293
+ *
2294
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2295
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2296
+ */
1632
2297
  async createAggregatorNode(storageContext) {
1633
2298
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1634
2299
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1635
2300
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1636
2301
  return aggregatorNode;
1637
2302
  }
2303
+ /**
2304
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2305
+ *
2306
+ * @param {string} pluginName - The name of the plugin.
2307
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2308
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2309
+ */
1638
2310
  async addBridgedEndpoint(pluginName, device) {
2311
+ // Check if the plugin is registered
1639
2312
  const plugin = this.plugins.get(pluginName);
1640
2313
  if (!plugin) {
1641
2314
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1655,6 +2328,7 @@ export class Matterbridge extends EventEmitter {
1655
2328
  }
1656
2329
  else if (this.bridgeMode === 'bridge') {
1657
2330
  if (device.mode === 'matter') {
2331
+ // Register and add the device to the matterbridge server node
1658
2332
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1659
2333
  if (!this.serverNode) {
1660
2334
  this.log.error('Server node not found for Matterbridge');
@@ -1671,6 +2345,7 @@ export class Matterbridge extends EventEmitter {
1671
2345
  }
1672
2346
  }
1673
2347
  else {
2348
+ // Register and add the device to the matterbridge aggregator node
1674
2349
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1675
2350
  if (!this.aggregatorNode) {
1676
2351
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1688,6 +2363,7 @@ export class Matterbridge extends EventEmitter {
1688
2363
  }
1689
2364
  }
1690
2365
  else if (this.bridgeMode === 'childbridge') {
2366
+ // Register and add the device to the plugin server node
1691
2367
  if (plugin.type === 'AccessoryPlatform') {
1692
2368
  try {
1693
2369
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1711,10 +2387,12 @@ export class Matterbridge extends EventEmitter {
1711
2387
  return;
1712
2388
  }
1713
2389
  }
2390
+ // Register and add the device to the plugin aggregator node
1714
2391
  if (plugin.type === 'DynamicPlatform') {
1715
2392
  try {
1716
2393
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1717
2394
  await this.createDynamicPlugin(plugin);
2395
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1718
2396
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1719
2397
  if (!plugin.aggregatorNode) {
1720
2398
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1737,17 +2415,28 @@ export class Matterbridge extends EventEmitter {
1737
2415
  plugin.registeredDevices++;
1738
2416
  if (plugin.addedDevices !== undefined)
1739
2417
  plugin.addedDevices++;
2418
+ // Add the device to the DeviceManager
1740
2419
  this.devices.set(device);
2420
+ // Subscribe to the reachable$Changed event
1741
2421
  await this.subscribeAttributeChanged(plugin, device);
1742
2422
  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}`);
1743
2423
  }
2424
+ /**
2425
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2426
+ *
2427
+ * @param {string} pluginName - The name of the plugin.
2428
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2429
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2430
+ */
1744
2431
  async removeBridgedEndpoint(pluginName, device) {
1745
2432
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2433
+ // Check if the plugin is registered
1746
2434
  const plugin = this.plugins.get(pluginName);
1747
2435
  if (!plugin) {
1748
2436
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1749
2437
  return;
1750
2438
  }
2439
+ // Register and add the device to the matterbridge aggregator node
1751
2440
  if (this.bridgeMode === 'bridge') {
1752
2441
  if (!this.aggregatorNode) {
1753
2442
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1762,6 +2451,7 @@ export class Matterbridge extends EventEmitter {
1762
2451
  }
1763
2452
  else if (this.bridgeMode === 'childbridge') {
1764
2453
  if (plugin.type === 'AccessoryPlatform') {
2454
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1765
2455
  }
1766
2456
  else if (plugin.type === 'DynamicPlatform') {
1767
2457
  if (!plugin.aggregatorNode) {
@@ -1776,8 +2466,21 @@ export class Matterbridge extends EventEmitter {
1776
2466
  if (plugin.addedDevices !== undefined)
1777
2467
  plugin.addedDevices--;
1778
2468
  }
2469
+ // Remove the device from the DeviceManager
1779
2470
  this.devices.remove(device);
1780
2471
  }
2472
+ /**
2473
+ * Removes all bridged endpoints from the specified plugin.
2474
+ *
2475
+ * @param {string} pluginName - The name of the plugin.
2476
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2477
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2478
+ *
2479
+ * @remarks
2480
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2481
+ * It also applies a delay between each removal if specified.
2482
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2483
+ */
1781
2484
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1782
2485
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1783
2486
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1788,6 +2491,15 @@ export class Matterbridge extends EventEmitter {
1788
2491
  if (delay > 0)
1789
2492
  await wait(2000);
1790
2493
  }
2494
+ /**
2495
+ * Subscribes to the attribute change event for the given device and plugin.
2496
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2497
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2498
+ *
2499
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2500
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2501
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2502
+ */
1791
2503
  async subscribeAttributeChanged(plugin, device) {
1792
2504
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
1793
2505
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
@@ -1803,6 +2515,12 @@ export class Matterbridge extends EventEmitter {
1803
2515
  });
1804
2516
  }
1805
2517
  }
2518
+ /**
2519
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2520
+ *
2521
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2522
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2523
+ */
1806
2524
  sanitizeFabricInformations(fabricInfo) {
1807
2525
  return fabricInfo.map((info) => {
1808
2526
  return {
@@ -1816,6 +2534,12 @@ export class Matterbridge extends EventEmitter {
1816
2534
  };
1817
2535
  });
1818
2536
  }
2537
+ /**
2538
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2539
+ *
2540
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2541
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2542
+ */
1819
2543
  sanitizeSessionInformation(sessions) {
1820
2544
  return sessions
1821
2545
  .filter((session) => session.isPeerActive)
@@ -1842,7 +2566,21 @@ export class Matterbridge extends EventEmitter {
1842
2566
  };
1843
2567
  });
1844
2568
  }
2569
+ /**
2570
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2571
+ *
2572
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2573
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2574
+ */
2575
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1845
2576
  async setAggregatorReachability(aggregatorNode, reachable) {
2577
+ /*
2578
+ for (const child of aggregatorNode.parts) {
2579
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2580
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2581
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2582
+ }
2583
+ */
1846
2584
  }
1847
2585
  getVendorIdName = (vendorId) => {
1848
2586
  if (!vendorId)
@@ -1882,10 +2620,11 @@ export class Matterbridge extends EventEmitter {
1882
2620
  case 0x1488:
1883
2621
  vendorName = '(ShortcutLabsFlic)';
1884
2622
  break;
1885
- case 65521:
2623
+ case 65521: // 0xFFF1
1886
2624
  vendorName = '(MatterTest)';
1887
2625
  break;
1888
2626
  }
1889
2627
  return vendorName;
1890
2628
  };
1891
2629
  }
2630
+ //# sourceMappingURL=matterbridge.js.map