matterbridge 3.4.2-dev-20251204-5a2b977 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/broadcastServer.d.ts +144 -0
  3. package/dist/broadcastServer.d.ts.map +1 -0
  4. package/dist/broadcastServer.js +119 -0
  5. package/dist/broadcastServer.js.map +1 -0
  6. package/dist/broadcastServerTypes.d.ts +841 -0
  7. package/dist/broadcastServerTypes.d.ts.map +1 -0
  8. package/dist/broadcastServerTypes.js +24 -0
  9. package/dist/broadcastServerTypes.js.map +1 -0
  10. package/dist/cli.d.ts +30 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +97 -1
  13. package/dist/cli.js.map +1 -0
  14. package/dist/cliEmitter.d.ts +50 -0
  15. package/dist/cliEmitter.d.ts.map +1 -0
  16. package/dist/cliEmitter.js +37 -0
  17. package/dist/cliEmitter.js.map +1 -0
  18. package/dist/cliHistory.d.ts +48 -0
  19. package/dist/cliHistory.d.ts.map +1 -0
  20. package/dist/cliHistory.js +38 -0
  21. package/dist/cliHistory.js.map +1 -0
  22. package/dist/clusters/export.d.ts +2 -0
  23. package/dist/clusters/export.d.ts.map +1 -0
  24. package/dist/clusters/export.js +2 -0
  25. package/dist/clusters/export.js.map +1 -0
  26. package/dist/deviceManager.d.ts +135 -0
  27. package/dist/deviceManager.d.ts.map +1 -0
  28. package/dist/deviceManager.js +113 -1
  29. package/dist/deviceManager.js.map +1 -0
  30. package/dist/devices/airConditioner.d.ts +98 -0
  31. package/dist/devices/airConditioner.d.ts.map +1 -0
  32. package/dist/devices/airConditioner.js +57 -0
  33. package/dist/devices/airConditioner.js.map +1 -0
  34. package/dist/devices/batteryStorage.d.ts +48 -0
  35. package/dist/devices/batteryStorage.d.ts.map +1 -0
  36. package/dist/devices/batteryStorage.js +48 -1
  37. package/dist/devices/batteryStorage.js.map +1 -0
  38. package/dist/devices/cooktop.d.ts +61 -0
  39. package/dist/devices/cooktop.d.ts.map +1 -0
  40. package/dist/devices/cooktop.js +56 -0
  41. package/dist/devices/cooktop.js.map +1 -0
  42. package/dist/devices/dishwasher.d.ts +71 -0
  43. package/dist/devices/dishwasher.d.ts.map +1 -0
  44. package/dist/devices/dishwasher.js +57 -0
  45. package/dist/devices/dishwasher.js.map +1 -0
  46. package/dist/devices/evse.d.ts +76 -0
  47. package/dist/devices/evse.d.ts.map +1 -0
  48. package/dist/devices/evse.js +74 -10
  49. package/dist/devices/evse.js.map +1 -0
  50. package/dist/devices/export.d.ts +17 -0
  51. package/dist/devices/export.d.ts.map +1 -0
  52. package/dist/devices/export.js +5 -0
  53. package/dist/devices/export.js.map +1 -0
  54. package/dist/devices/extractorHood.d.ts +46 -0
  55. package/dist/devices/extractorHood.d.ts.map +1 -0
  56. package/dist/devices/extractorHood.js +43 -0
  57. package/dist/devices/extractorHood.js.map +1 -0
  58. package/dist/devices/heatPump.d.ts +47 -0
  59. package/dist/devices/heatPump.d.ts.map +1 -0
  60. package/dist/devices/heatPump.js +50 -2
  61. package/dist/devices/heatPump.js.map +1 -0
  62. package/dist/devices/laundryDryer.d.ts +67 -0
  63. package/dist/devices/laundryDryer.d.ts.map +1 -0
  64. package/dist/devices/laundryDryer.js +62 -3
  65. package/dist/devices/laundryDryer.js.map +1 -0
  66. package/dist/devices/laundryWasher.d.ts +81 -0
  67. package/dist/devices/laundryWasher.d.ts.map +1 -0
  68. package/dist/devices/laundryWasher.js +70 -4
  69. package/dist/devices/laundryWasher.js.map +1 -0
  70. package/dist/devices/microwaveOven.d.ts +168 -0
  71. package/dist/devices/microwaveOven.d.ts.map +1 -0
  72. package/dist/devices/microwaveOven.js +88 -5
  73. package/dist/devices/microwaveOven.js.map +1 -0
  74. package/dist/devices/oven.d.ts +105 -0
  75. package/dist/devices/oven.d.ts.map +1 -0
  76. package/dist/devices/oven.js +85 -0
  77. package/dist/devices/oven.js.map +1 -0
  78. package/dist/devices/refrigerator.d.ts +118 -0
  79. package/dist/devices/refrigerator.d.ts.map +1 -0
  80. package/dist/devices/refrigerator.js +102 -0
  81. package/dist/devices/refrigerator.js.map +1 -0
  82. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  83. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  84. package/dist/devices/roboticVacuumCleaner.js +100 -9
  85. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  86. package/dist/devices/solarPower.d.ts +40 -0
  87. package/dist/devices/solarPower.d.ts.map +1 -0
  88. package/dist/devices/solarPower.js +38 -0
  89. package/dist/devices/solarPower.js.map +1 -0
  90. package/dist/devices/speaker.d.ts +87 -0
  91. package/dist/devices/speaker.d.ts.map +1 -0
  92. package/dist/devices/speaker.js +84 -0
  93. package/dist/devices/speaker.js.map +1 -0
  94. package/dist/devices/temperatureControl.d.ts +166 -0
  95. package/dist/devices/temperatureControl.d.ts.map +1 -0
  96. package/dist/devices/temperatureControl.js +24 -3
  97. package/dist/devices/temperatureControl.js.map +1 -0
  98. package/dist/devices/waterHeater.d.ts +111 -0
  99. package/dist/devices/waterHeater.d.ts.map +1 -0
  100. package/dist/devices/waterHeater.js +82 -2
  101. package/dist/devices/waterHeater.js.map +1 -0
  102. package/dist/dgram/coap.d.ts +205 -0
  103. package/dist/dgram/coap.d.ts.map +1 -0
  104. package/dist/dgram/coap.js +126 -13
  105. package/dist/dgram/coap.js.map +1 -0
  106. package/dist/dgram/dgram.d.ts +141 -0
  107. package/dist/dgram/dgram.d.ts.map +1 -0
  108. package/dist/dgram/dgram.js +114 -2
  109. package/dist/dgram/dgram.js.map +1 -0
  110. package/dist/dgram/mb_coap.d.ts +24 -0
  111. package/dist/dgram/mb_coap.d.ts.map +1 -0
  112. package/dist/dgram/mb_coap.js +41 -3
  113. package/dist/dgram/mb_coap.js.map +1 -0
  114. package/dist/dgram/mb_mdns.d.ts +24 -0
  115. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  116. package/dist/dgram/mb_mdns.js +80 -15
  117. package/dist/dgram/mb_mdns.js.map +1 -0
  118. package/dist/dgram/mdns.d.ts +290 -0
  119. package/dist/dgram/mdns.d.ts.map +1 -0
  120. package/dist/dgram/mdns.js +299 -137
  121. package/dist/dgram/mdns.js.map +1 -0
  122. package/dist/dgram/multicast.d.ts +67 -0
  123. package/dist/dgram/multicast.d.ts.map +1 -0
  124. package/dist/dgram/multicast.js +62 -1
  125. package/dist/dgram/multicast.js.map +1 -0
  126. package/dist/dgram/unicast.d.ts +56 -0
  127. package/dist/dgram/unicast.d.ts.map +1 -0
  128. package/dist/dgram/unicast.js +54 -0
  129. package/dist/dgram/unicast.js.map +1 -0
  130. package/dist/frontend.d.ts +238 -0
  131. package/dist/frontend.d.ts.map +1 -0
  132. package/dist/frontend.js +455 -35
  133. package/dist/frontend.js.map +1 -0
  134. package/dist/frontendTypes.d.ts +529 -0
  135. package/dist/frontendTypes.d.ts.map +1 -0
  136. package/dist/frontendTypes.js +45 -0
  137. package/dist/frontendTypes.js.map +1 -0
  138. package/dist/helpers.d.ts +48 -0
  139. package/dist/helpers.d.ts.map +1 -0
  140. package/dist/helpers.js +53 -0
  141. package/dist/helpers.js.map +1 -0
  142. package/dist/index.d.ts +34 -0
  143. package/dist/index.d.ts.map +1 -0
  144. package/dist/index.js +25 -0
  145. package/dist/index.js.map +1 -0
  146. package/dist/jestutils/export.d.ts +2 -0
  147. package/dist/jestutils/export.d.ts.map +1 -0
  148. package/dist/jestutils/export.js +1 -0
  149. package/dist/jestutils/export.js.map +1 -0
  150. package/dist/jestutils/jestHelpers.d.ts +345 -0
  151. package/dist/jestutils/jestHelpers.d.ts.map +1 -0
  152. package/dist/jestutils/jestHelpers.js +371 -14
  153. package/dist/jestutils/jestHelpers.js.map +1 -0
  154. package/dist/logger/export.d.ts +2 -0
  155. package/dist/logger/export.d.ts.map +1 -0
  156. package/dist/logger/export.js +1 -0
  157. package/dist/logger/export.js.map +1 -0
  158. package/dist/matter/behaviors.d.ts +2 -0
  159. package/dist/matter/behaviors.d.ts.map +1 -0
  160. package/dist/matter/behaviors.js +2 -0
  161. package/dist/matter/behaviors.js.map +1 -0
  162. package/dist/matter/clusters.d.ts +2 -0
  163. package/dist/matter/clusters.d.ts.map +1 -0
  164. package/dist/matter/clusters.js +2 -0
  165. package/dist/matter/clusters.js.map +1 -0
  166. package/dist/matter/devices.d.ts +2 -0
  167. package/dist/matter/devices.d.ts.map +1 -0
  168. package/dist/matter/devices.js +2 -0
  169. package/dist/matter/devices.js.map +1 -0
  170. package/dist/matter/endpoints.d.ts +2 -0
  171. package/dist/matter/endpoints.d.ts.map +1 -0
  172. package/dist/matter/endpoints.js +2 -0
  173. package/dist/matter/endpoints.js.map +1 -0
  174. package/dist/matter/export.d.ts +5 -0
  175. package/dist/matter/export.d.ts.map +1 -0
  176. package/dist/matter/export.js +3 -0
  177. package/dist/matter/export.js.map +1 -0
  178. package/dist/matter/types.d.ts +3 -0
  179. package/dist/matter/types.d.ts.map +1 -0
  180. package/dist/matter/types.js +3 -0
  181. package/dist/matter/types.js.map +1 -0
  182. package/dist/matterNode.d.ts +342 -0
  183. package/dist/matterNode.d.ts.map +1 -0
  184. package/dist/matterNode.js +369 -8
  185. package/dist/matterNode.js.map +1 -0
  186. package/dist/matterbridge.d.ts +492 -0
  187. package/dist/matterbridge.d.ts.map +1 -0
  188. package/dist/matterbridge.js +809 -46
  189. package/dist/matterbridge.js.map +1 -0
  190. package/dist/matterbridgeAccessoryPlatform.d.ts +41 -0
  191. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  192. package/dist/matterbridgeAccessoryPlatform.js +38 -0
  193. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  194. package/dist/matterbridgeBehaviors.d.ts +2404 -0
  195. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  196. package/dist/matterbridgeBehaviors.js +68 -5
  197. package/dist/matterbridgeBehaviors.js.map +1 -0
  198. package/dist/matterbridgeDeviceTypes.d.ts +698 -0
  199. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  200. package/dist/matterbridgeDeviceTypes.js +635 -14
  201. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  202. package/dist/matterbridgeDynamicPlatform.d.ts +41 -0
  203. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  204. package/dist/matterbridgeDynamicPlatform.js +38 -0
  205. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  206. package/dist/matterbridgeEndpoint.d.ts +1507 -0
  207. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  208. package/dist/matterbridgeEndpoint.js +1444 -53
  209. package/dist/matterbridgeEndpoint.js.map +1 -0
  210. package/dist/matterbridgeEndpointHelpers.d.ts +787 -0
  211. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  212. package/dist/matterbridgeEndpointHelpers.js +483 -20
  213. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  214. package/dist/matterbridgeEndpointTypes.d.ts +166 -0
  215. package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
  216. package/dist/matterbridgeEndpointTypes.js +25 -0
  217. package/dist/matterbridgeEndpointTypes.js.map +1 -0
  218. package/dist/matterbridgePlatform.d.ts +539 -0
  219. package/dist/matterbridgePlatform.d.ts.map +1 -0
  220. package/dist/matterbridgePlatform.js +451 -1
  221. package/dist/matterbridgePlatform.js.map +1 -0
  222. package/dist/matterbridgeTypes.d.ts +251 -0
  223. package/dist/matterbridgeTypes.d.ts.map +1 -0
  224. package/dist/matterbridgeTypes.js +26 -0
  225. package/dist/matterbridgeTypes.js.map +1 -0
  226. package/dist/pluginManager.d.ts +372 -0
  227. package/dist/pluginManager.d.ts.map +1 -0
  228. package/dist/pluginManager.js +341 -5
  229. package/dist/pluginManager.js.map +1 -0
  230. package/dist/shelly.d.ts +181 -0
  231. package/dist/shelly.d.ts.map +1 -0
  232. package/dist/shelly.js +178 -7
  233. package/dist/shelly.js.map +1 -0
  234. package/dist/storage/export.d.ts +2 -0
  235. package/dist/storage/export.d.ts.map +1 -0
  236. package/dist/storage/export.js +1 -0
  237. package/dist/storage/export.js.map +1 -0
  238. package/dist/update.d.ts +84 -0
  239. package/dist/update.d.ts.map +1 -0
  240. package/dist/update.js +93 -1
  241. package/dist/update.js.map +1 -0
  242. package/dist/utils/colorUtils.d.ts +101 -0
  243. package/dist/utils/colorUtils.d.ts.map +1 -0
  244. package/dist/utils/colorUtils.js +97 -2
  245. package/dist/utils/colorUtils.js.map +1 -0
  246. package/dist/utils/commandLine.d.ts +66 -0
  247. package/dist/utils/commandLine.d.ts.map +1 -0
  248. package/dist/utils/commandLine.js +60 -0
  249. package/dist/utils/commandLine.js.map +1 -0
  250. package/dist/utils/copyDirectory.d.ts +35 -0
  251. package/dist/utils/copyDirectory.d.ts.map +1 -0
  252. package/dist/utils/copyDirectory.js +37 -0
  253. package/dist/utils/copyDirectory.js.map +1 -0
  254. package/dist/utils/createDirectory.d.ts +34 -0
  255. package/dist/utils/createDirectory.d.ts.map +1 -0
  256. package/dist/utils/createDirectory.js +33 -0
  257. package/dist/utils/createDirectory.js.map +1 -0
  258. package/dist/utils/createZip.d.ts +39 -0
  259. package/dist/utils/createZip.d.ts.map +1 -0
  260. package/dist/utils/createZip.js +47 -2
  261. package/dist/utils/createZip.js.map +1 -0
  262. package/dist/utils/deepCopy.d.ts +32 -0
  263. package/dist/utils/deepCopy.d.ts.map +1 -0
  264. package/dist/utils/deepCopy.js +39 -0
  265. package/dist/utils/deepCopy.js.map +1 -0
  266. package/dist/utils/deepEqual.d.ts +54 -0
  267. package/dist/utils/deepEqual.d.ts.map +1 -0
  268. package/dist/utils/deepEqual.js +72 -1
  269. package/dist/utils/deepEqual.js.map +1 -0
  270. package/dist/utils/error.d.ts +45 -0
  271. package/dist/utils/error.d.ts.map +1 -0
  272. package/dist/utils/error.js +42 -0
  273. package/dist/utils/error.js.map +1 -0
  274. package/dist/utils/export.d.ts +13 -0
  275. package/dist/utils/export.d.ts.map +1 -0
  276. package/dist/utils/export.js +1 -0
  277. package/dist/utils/export.js.map +1 -0
  278. package/dist/utils/format.d.ts +53 -0
  279. package/dist/utils/format.d.ts.map +1 -0
  280. package/dist/utils/format.js +49 -0
  281. package/dist/utils/format.js.map +1 -0
  282. package/dist/utils/hex.d.ts +89 -0
  283. package/dist/utils/hex.d.ts.map +1 -0
  284. package/dist/utils/hex.js +124 -0
  285. package/dist/utils/hex.js.map +1 -0
  286. package/dist/utils/inspector.d.ts +87 -0
  287. package/dist/utils/inspector.d.ts.map +1 -0
  288. package/dist/utils/inspector.js +69 -1
  289. package/dist/utils/inspector.js.map +1 -0
  290. package/dist/utils/isvalid.d.ts +103 -0
  291. package/dist/utils/isvalid.d.ts.map +1 -0
  292. package/dist/utils/isvalid.js +101 -0
  293. package/dist/utils/isvalid.js.map +1 -0
  294. package/dist/utils/network.d.ts +111 -0
  295. package/dist/utils/network.d.ts.map +1 -0
  296. package/dist/utils/network.js +96 -5
  297. package/dist/utils/network.js.map +1 -0
  298. package/dist/utils/spawn.d.ts +33 -0
  299. package/dist/utils/spawn.d.ts.map +1 -0
  300. package/dist/utils/spawn.js +71 -1
  301. package/dist/utils/spawn.js.map +1 -0
  302. package/dist/utils/tracker.d.ts +108 -0
  303. package/dist/utils/tracker.d.ts.map +1 -0
  304. package/dist/utils/tracker.js +64 -1
  305. package/dist/utils/tracker.js.map +1 -0
  306. package/dist/utils/wait.d.ts +54 -0
  307. package/dist/utils/wait.d.ts.map +1 -0
  308. package/dist/utils/wait.js +60 -8
  309. package/dist/utils/wait.js.map +1 -0
  310. package/dist/workerGlobalPrefix.d.ts +25 -0
  311. package/dist/workerGlobalPrefix.d.ts.map +1 -0
  312. package/dist/workerGlobalPrefix.js +37 -5
  313. package/dist/workerGlobalPrefix.js.map +1 -0
  314. package/dist/workerTypes.d.ts +52 -0
  315. package/dist/workerTypes.d.ts.map +1 -0
  316. package/dist/workerTypes.js +24 -0
  317. package/dist/workerTypes.js.map +1 -0
  318. package/dist/workers.d.ts +69 -0
  319. package/dist/workers.d.ts.map +1 -0
  320. package/dist/workers.js +68 -4
  321. package/dist/workers.js.map +1 -0
  322. package/npm-shrinkwrap.json +2 -2
  323. package/package.json +2 -1
@@ -1,19 +1,48 @@
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.2
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
+ // eslint-disable-next-line no-console
1
25
  if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
26
  console.log('\u001B[32mMatterbridge loaded.\u001B[40;0m');
27
+ // Node.js modules
3
28
  import os from 'node:os';
4
29
  import path from 'node:path';
5
30
  import fs from 'node:fs';
6
31
  import EventEmitter from 'node:events';
7
32
  import { inspect } from 'node:util';
33
+ // AnsiLogger module
8
34
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE, or } from 'node-ansi-logger';
35
+ // NodeStorage module
9
36
  import { NodeStorageManager } from 'node-persist-manager';
10
- import '@matter/nodejs';
37
+ // @matter
38
+ import '@matter/nodejs'; // Set up Node.js environment for matter.js
11
39
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, UINT32_MAX, UINT16_MAX, Crypto, Environment, StorageService } from '@matter/general';
12
40
  import { FabricAction, PaseClient } from '@matter/protocol';
13
41
  import { Endpoint, ServerNode } from '@matter/node';
14
42
  import { DeviceTypeId, VendorId } from '@matter/types/datatype';
15
43
  import { AggregatorEndpoint } from '@matter/node/endpoints';
16
44
  import { BasicInformationServer } from '@matter/node/behaviors/basic-information';
45
+ // Matterbridge
17
46
  import { getParameter, getIntParameter, hasParameter } from './utils/commandLine.js';
18
47
  import { copyDirectory } from './utils/copyDirectory.js';
19
48
  import { createDirectory } from './utils/createDirectory.js';
@@ -27,19 +56,27 @@ import { bridge } from './matterbridgeDeviceTypes.js';
27
56
  import { Frontend } from './frontend.js';
28
57
  import { addVirtualDevice, addVirtualDevices } from './helpers.js';
29
58
  import { BroadcastServer } from './broadcastServer.js';
59
+ /**
60
+ * Represents the Matterbridge application.
61
+ */
30
62
  export class Matterbridge extends EventEmitter {
63
+ /** Matterbridge system information */
31
64
  systemInformation = {
65
+ // Network properties
32
66
  interfaceName: '',
33
67
  macAddress: '',
34
68
  ipv4Address: '',
35
69
  ipv6Address: '',
70
+ // Node.js properties
36
71
  nodeVersion: '',
72
+ // Fixed system properties
37
73
  hostname: '',
38
74
  user: '',
39
75
  osType: '',
40
76
  osRelease: '',
41
77
  osPlatform: '',
42
78
  osArch: '',
79
+ // Cpu and memory properties
43
80
  totalMemory: '',
44
81
  freeMemory: '',
45
82
  systemUptime: '',
@@ -50,6 +87,7 @@ export class Matterbridge extends EventEmitter {
50
87
  heapTotal: '',
51
88
  heapUsed: '',
52
89
  };
90
+ // Matterbridge settings
53
91
  homeDirectory = '';
54
92
  rootDirectory = '';
55
93
  matterbridgeDirectory = '';
@@ -64,12 +102,19 @@ export class Matterbridge extends EventEmitter {
64
102
  restartMode = '';
65
103
  virtualMode = 'outlet';
66
104
  profile = getParameter('profile');
67
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
105
+ /** Matterbridge logger */
106
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
107
+ /** Matterbridge logger level */
68
108
  logLevel = this.log.logLevel;
109
+ /** Whether to log to a file */
69
110
  fileLogger = false;
70
- matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
111
+ /** Matter logger */
112
+ matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
113
+ /** Matter logger level */
71
114
  matterLogLevel = this.matterLog.logLevel;
115
+ /** Whether to log Matter to a file */
72
116
  matterFileLogger = false;
117
+ // Frontend settings
73
118
  readOnly = hasParameter('readonly') || hasParameter('shelly');
74
119
  shellyBoard = hasParameter('shelly');
75
120
  shellySysUpdate = false;
@@ -77,12 +122,18 @@ export class Matterbridge extends EventEmitter {
77
122
  restartRequired = false;
78
123
  fixedRestartRequired = false;
79
124
  updateRequired = false;
125
+ // Managers
80
126
  plugins = new PluginManager(this);
81
127
  devices = new DeviceManager();
128
+ // Frontend
82
129
  frontend = new Frontend(this);
130
+ /** Matterbridge node storage manager created in the directory 'storage' in matterbridgeDirectory */
83
131
  nodeStorage;
132
+ /** Matterbridge node context created with name 'matterbridge' */
84
133
  nodeContext;
134
+ /** The main instance of the Matterbridge class (singleton) */
85
135
  static instance;
136
+ // Instance properties
86
137
  shutdown = false;
87
138
  failCountLimit = hasParameter('shelly') ? 600 : 120;
88
139
  hasCleanupStarted = false;
@@ -97,19 +148,32 @@ export class Matterbridge extends EventEmitter {
97
148
  sigtermHandler;
98
149
  exceptionHandler;
99
150
  rejectionHandler;
151
+ /** Matter environment default */
100
152
  environment = Environment.default;
153
+ /** Matter storage service from environment default */
101
154
  matterStorageService;
155
+ /** Matter storage manager created with name 'Matterbridge' */
102
156
  matterStorageManager;
157
+ /** Matter matterbridge storage context created in the storage manager with name 'persist' */
103
158
  matterbridgeContext;
104
159
  controllerContext;
160
+ /** Matter mdns interface e.g. 'eth0' or 'wlan0' or 'Wi-Fi' */
105
161
  mdnsInterface;
162
+ /** Matter listeningAddressIpv4 address */
106
163
  ipv4Address;
164
+ /** Matter listeningAddressIpv6 address */
107
165
  ipv6Address;
108
- port;
109
- passcode;
110
- discriminator;
111
- certification;
166
+ /** Matter commissioning port */
167
+ port; // first server node port
168
+ /** Matter commissioning passcode */
169
+ passcode; // first server node passcode
170
+ /** Matter commissioning discriminator */
171
+ discriminator; // first server node discriminator
172
+ /** Matter device certification */
173
+ certification; // device certification
174
+ /** Matter server node in bridge mode */
112
175
  serverNode;
176
+ /** Matter aggregator node in bridge mode */
113
177
  aggregatorNode;
114
178
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
115
179
  aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
@@ -118,9 +182,12 @@ export class Matterbridge extends EventEmitter {
118
182
  aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
119
183
  aggregatorSerialNumber = getParameter('serialNumber');
120
184
  aggregatorUniqueId = getParameter('uniqueId');
185
+ /** Advertising nodes map: time advertising started keyed by storeId */
121
186
  advertisingNodes = new Map();
187
+ /** Broadcast server */
122
188
  server;
123
189
  verbose = hasParameter('verbose');
190
+ /** We load asyncronously so is private */
124
191
  constructor() {
125
192
  super();
126
193
  this.log.logNameColor = '\x1b[38;5;115m';
@@ -171,8 +238,19 @@ export class Matterbridge extends EventEmitter {
171
238
  }
172
239
  }
173
240
  }
241
+ //* ************************************************************************************************************************************ */
242
+ // loadInstance() and cleanup() methods */
243
+ //* ************************************************************************************************************************************ */
244
+ /**
245
+ * Loads an instance of the Matterbridge class.
246
+ * If an instance already exists, return that instance.
247
+ *
248
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
249
+ * @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
250
+ */
174
251
  static async loadInstance(initialize = false) {
175
252
  if (!Matterbridge.instance) {
253
+ // eslint-disable-next-line no-console
176
254
  if (hasParameter('debug'))
177
255
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
178
256
  Matterbridge.instance = new Matterbridge();
@@ -181,56 +259,84 @@ export class Matterbridge extends EventEmitter {
181
259
  }
182
260
  return Matterbridge.instance;
183
261
  }
262
+ /**
263
+ * Initializes the Matterbridge application.
264
+ *
265
+ * @remarks
266
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
267
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
268
+ * node version, registers signal handlers, initializes storage, and parses the command line.
269
+ *
270
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
271
+ */
184
272
  async initialize() {
273
+ // for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
274
+ // Emit the initialize_started event
185
275
  this.emit('initialize_started');
276
+ // Set the restart mode
186
277
  if (hasParameter('service'))
187
278
  this.restartMode = 'service';
188
279
  if (hasParameter('docker'))
189
280
  this.restartMode = 'docker';
281
+ // Set the matterbridge home directory
190
282
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
191
283
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
284
+ // Set the matterbridge directory
192
285
  this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
193
286
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
194
287
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
195
288
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
289
+ // Set the matterbridge plugin directory
196
290
  this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
197
291
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
292
+ // Set the matterbridge cert directory
198
293
  this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
199
294
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
295
+ // Set the matterbridge root directory
200
296
  const { fileURLToPath } = await import('node:url');
201
297
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
202
- this.rootDirectory = path.resolve(currentFileDirectory, '../');
298
+ this.rootDirectory = path.resolve(currentFileDirectory, '../'); // Adjust the path for dist directory
299
+ // Setup the matter environment with default values
203
300
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
204
301
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
205
302
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
206
303
  this.environment.vars.set('runtime.signals', false);
207
304
  this.environment.vars.set('runtime.exitcode', false);
305
+ // Register process handlers
208
306
  this.registerProcessHandlers();
307
+ // Initialize nodeStorage and nodeContext
209
308
  try {
210
309
  this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
211
310
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
212
311
  this.log.debug('Creating node storage context for matterbridge');
213
312
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
313
+ // TODO: Remove this code when node-persist-manager is updated
314
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
214
315
  const keys = (await this.nodeStorage?.storage.keys());
215
316
  for (const key of keys) {
216
317
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
318
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
319
  await this.nodeStorage?.storage.get(key);
218
320
  }
219
321
  const storages = await this.nodeStorage.getStorageNames();
220
322
  for (const storage of storages) {
221
323
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
222
324
  const nodeContext = await this.nodeStorage?.createStorage(storage);
325
+ // TODO: Remove this code when node-persist-manager is updated
326
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
223
327
  const keys = (await nodeContext?.storage.keys());
224
328
  keys.forEach(async (key) => {
225
329
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
226
330
  await nodeContext?.get(key);
227
331
  });
228
332
  }
333
+ // Creating a backup of the node storage since it is not corrupted
229
334
  this.log.debug('Creating node storage backup...');
230
335
  await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
231
336
  this.log.debug('Created node storage backup');
232
337
  }
233
338
  catch (error) {
339
+ // Restoring the backup of the node storage since it is corrupted
234
340
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
235
341
  if (hasParameter('norestore')) {
236
342
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -244,14 +350,19 @@ export class Matterbridge extends EventEmitter {
244
350
  if (!this.nodeStorage || !this.nodeContext) {
245
351
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
246
352
  }
353
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
247
354
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
355
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
248
356
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
357
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
249
358
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
359
+ // Certificate management
250
360
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
251
361
  try {
252
362
  await fs.promises.access(pairingFilePath, fs.constants.R_OK);
253
363
  const pairingFileContent = await fs.promises.readFile(pairingFilePath, 'utf8');
254
364
  const pairingFileJson = JSON.parse(pairingFileContent);
365
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
255
366
  if (isValidNumber(pairingFileJson.vendorId)) {
256
367
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
257
368
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -280,11 +391,13 @@ export class Matterbridge extends EventEmitter {
280
391
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
281
392
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
282
393
  }
394
+ // Override the passcode and discriminator if they are present in the pairing file
283
395
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
284
396
  this.passcode = pairingFileJson.passcode;
285
397
  this.discriminator = pairingFileJson.discriminator;
286
398
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
287
399
  }
400
+ // Set the certification for matter.js if it is present in the pairing file
288
401
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
289
402
  const { hexToBuffer } = await import('./utils/hex.js');
290
403
  this.certification = {
@@ -299,41 +412,44 @@ export class Matterbridge extends EventEmitter {
299
412
  catch (error) {
300
413
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
301
414
  }
415
+ // Store the passcode, discriminator and port in the node context
302
416
  await this.nodeContext.set('matterport', this.port);
303
417
  await this.nodeContext.set('matterpasscode', this.passcode);
304
418
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
305
419
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
420
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
306
421
  if (hasParameter('logger')) {
307
422
  const level = getParameter('logger');
308
423
  if (level === 'debug') {
309
- this.log.logLevel = "debug";
424
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
310
425
  }
311
426
  else if (level === 'info') {
312
- this.log.logLevel = "info";
427
+ this.log.logLevel = "info" /* LogLevel.INFO */;
313
428
  }
314
429
  else if (level === 'notice') {
315
- this.log.logLevel = "notice";
430
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
316
431
  }
317
432
  else if (level === 'warn') {
318
- this.log.logLevel = "warn";
433
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
319
434
  }
320
435
  else if (level === 'error') {
321
- this.log.logLevel = "error";
436
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
322
437
  }
323
438
  else if (level === 'fatal') {
324
- this.log.logLevel = "fatal";
439
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
325
440
  }
326
441
  else {
327
442
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
328
- this.log.logLevel = "info";
443
+ this.log.logLevel = "info" /* LogLevel.INFO */;
329
444
  }
330
445
  }
331
446
  else {
332
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
447
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
333
448
  }
334
449
  this.logLevel = this.log.logLevel;
335
450
  this.frontend.logLevel = this.log.logLevel;
336
451
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
452
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
337
453
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
338
454
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
339
455
  this.fileLogger = true;
@@ -342,6 +458,7 @@ export class Matterbridge extends EventEmitter {
342
458
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
343
459
  if (this.profile !== undefined)
344
460
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
461
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
345
462
  if (hasParameter('matterlogger')) {
346
463
  const level = getParameter('matterlogger');
347
464
  if (level === 'debug') {
@@ -372,11 +489,13 @@ export class Matterbridge extends EventEmitter {
372
489
  }
373
490
  Logger.format = MatterLogFormat.ANSI;
374
491
  this.matterLogLevel = MatterLogLevel.names[Logger.level];
492
+ // Create the logger for matter.js with file logging (context: matterFileLog)
375
493
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
376
494
  this.matterFileLogger = true;
377
495
  }
378
496
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
379
497
  this.log.debug(`Matter logLevel: ${this.matterLogLevel} fileLoger: ${this.matterFileLogger}.`);
498
+ // Log network interfaces
380
499
  const networkInterfaces = os.networkInterfaces();
381
500
  const availableAddresses = Object.entries(networkInterfaces);
382
501
  const availableInterfaceNames = Object.keys(networkInterfaces);
@@ -389,6 +508,7 @@ export class Matterbridge extends EventEmitter {
389
508
  });
390
509
  }
391
510
  }
511
+ // Set the interface to use for matter server node mdnsInterface
392
512
  if (hasParameter('mdnsinterface')) {
393
513
  this.mdnsInterface = getParameter('mdnsinterface');
394
514
  }
@@ -397,6 +517,7 @@ export class Matterbridge extends EventEmitter {
397
517
  if (this.mdnsInterface === '')
398
518
  this.mdnsInterface = undefined;
399
519
  }
520
+ // Validate mdnsInterface
400
521
  if (this.mdnsInterface) {
401
522
  if (!availableInterfaceNames.includes(this.mdnsInterface)) {
402
523
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
@@ -409,6 +530,7 @@ export class Matterbridge extends EventEmitter {
409
530
  }
410
531
  if (this.mdnsInterface)
411
532
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
533
+ // Set the listeningAddressIpv4 for the matter commissioning server
412
534
  if (hasParameter('ipv4address')) {
413
535
  this.ipv4Address = getParameter('ipv4address');
414
536
  }
@@ -417,6 +539,7 @@ export class Matterbridge extends EventEmitter {
417
539
  if (this.ipv4Address === '')
418
540
  this.ipv4Address = undefined;
419
541
  }
542
+ // Validate ipv4address
420
543
  if (this.ipv4Address) {
421
544
  let isValid = false;
422
545
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -432,6 +555,7 @@ export class Matterbridge extends EventEmitter {
432
555
  await this.nodeContext.remove('matteripv4address');
433
556
  }
434
557
  }
558
+ // Set the listeningAddressIpv6 for the matter commissioning server
435
559
  if (hasParameter('ipv6address')) {
436
560
  this.ipv6Address = getParameter('ipv6address');
437
561
  }
@@ -440,6 +564,7 @@ export class Matterbridge extends EventEmitter {
440
564
  if (this.ipv6Address === '')
441
565
  this.ipv6Address = undefined;
442
566
  }
567
+ // Validate ipv6address
443
568
  if (this.ipv6Address) {
444
569
  let isValid = false;
445
570
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -448,6 +573,7 @@ export class Matterbridge extends EventEmitter {
448
573
  isValid = true;
449
574
  break;
450
575
  }
576
+ /* istanbul ignore next */
451
577
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
452
578
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
453
579
  isValid = true;
@@ -460,6 +586,7 @@ export class Matterbridge extends EventEmitter {
460
586
  await this.nodeContext.remove('matteripv6address');
461
587
  }
462
588
  }
589
+ // Initialize the virtual mode
463
590
  if (hasParameter('novirtual')) {
464
591
  this.virtualMode = 'disabled';
465
592
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -468,10 +595,15 @@ export class Matterbridge extends EventEmitter {
468
595
  this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
469
596
  }
470
597
  this.log.debug(`Virtual mode ${this.virtualMode}.`);
598
+ // Initialize PluginManager
471
599
  this.plugins.logLevel = this.log.logLevel;
472
600
  await this.plugins.loadFromStorage();
601
+ // Initialize DeviceManager
473
602
  this.devices.logLevel = this.log.logLevel;
603
+ // Get the plugins from node storage and create the plugins node storage contexts
474
604
  for (const plugin of this.plugins) {
605
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
606
+ // We don't do this when the add and other shutdown parameters are set because we shut down the process after adding the plugin
475
607
  if (!fs.existsSync(plugin.path) && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
476
608
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm...`);
477
609
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -501,6 +633,7 @@ export class Matterbridge extends EventEmitter {
501
633
  await plugin.nodeContext.set('description', plugin.description);
502
634
  await plugin.nodeContext.set('author', plugin.author);
503
635
  }
636
+ // Log system info and create .matterbridge directory
504
637
  await this.logNodeAndSystemInfo();
505
638
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
506
639
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -508,6 +641,7 @@ export class Matterbridge extends EventEmitter {
508
641
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
509
642
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
510
643
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
644
+ // Check node version and throw error
511
645
  const minNodeVersion = 20;
512
646
  const nodeVersion = process.versions.node;
513
647
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -515,10 +649,18 @@ export class Matterbridge extends EventEmitter {
515
649
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
516
650
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
517
651
  }
652
+ // Parse command line
518
653
  await this.parseCommandLine();
654
+ // Emit the initialize_completed event
519
655
  this.emit('initialize_completed');
520
656
  this.initialized = true;
521
657
  }
658
+ /**
659
+ * Parses the command line arguments and performs the corresponding actions.
660
+ *
661
+ * @private
662
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
663
+ */
522
664
  async parseCommandLine() {
523
665
  if (hasParameter('list')) {
524
666
  this.log.info(`│ Registered plugins (${this.plugins.length})`);
@@ -534,6 +676,19 @@ export class Matterbridge extends EventEmitter {
534
676
  }
535
677
  index++;
536
678
  }
679
+ /*
680
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
681
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
682
+ serializedRegisteredDevices?.forEach((device, index) => {
683
+ if (index !== serializedRegisteredDevices.length - 1) {
684
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
685
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
686
+ } else {
687
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
688
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
689
+ }
690
+ });
691
+ */
537
692
  this.shutdown = true;
538
693
  return;
539
694
  }
@@ -583,8 +738,10 @@ export class Matterbridge extends EventEmitter {
583
738
  this.shutdown = true;
584
739
  return;
585
740
  }
741
+ // Initialize frontend
586
742
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
587
743
  await this.frontend.start(getIntParameter('frontend'));
744
+ // Start the matter storage and create the matterbridge context
588
745
  try {
589
746
  await this.startMatterStorage();
590
747
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -598,18 +755,21 @@ export class Matterbridge extends EventEmitter {
598
755
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
599
756
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
600
757
  }
758
+ // Clear the matterbridge context if the reset parameter is set (bridge mode)
601
759
  if (hasParameter('reset') && getParameter('reset') === undefined) {
602
760
  this.initialized = true;
603
761
  await this.shutdownProcessAndReset();
604
762
  this.shutdown = true;
605
763
  return;
606
764
  }
765
+ // Clear matterbridge plugin context if the reset parameter is set (childbridge mode)
607
766
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
608
767
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
609
768
  const plugin = this.plugins.get(getParameter('reset'));
610
769
  if (plugin) {
611
770
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
612
771
  if (!matterStorageManager) {
772
+ /* istanbul ignore next */
613
773
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
614
774
  }
615
775
  else {
@@ -628,47 +788,56 @@ export class Matterbridge extends EventEmitter {
628
788
  this.shutdown = true;
629
789
  return;
630
790
  }
791
+ // Check in 5 minutes the latest and dev versions of matterbridge and the plugins
631
792
  clearTimeout(this.checkUpdateTimeout);
632
793
  this.checkUpdateTimeout = setTimeout(async () => {
633
794
  const { checkUpdates } = await import('./update.js');
634
795
  checkUpdates(this);
635
796
  }, 300 * 1000).unref();
797
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
636
798
  clearInterval(this.checkUpdateInterval);
637
799
  this.checkUpdateInterval = setInterval(async () => {
638
800
  const { checkUpdates } = await import('./update.js');
639
801
  checkUpdates(this);
640
802
  }, 12 * 60 * 60 * 1000).unref();
803
+ // Start the matterbridge in mode test
641
804
  if (hasParameter('test')) {
642
805
  this.bridgeMode = 'bridge';
643
806
  return;
644
807
  }
808
+ // Start the matterbridge in mode controller
645
809
  if (hasParameter('controller')) {
646
810
  this.bridgeMode = 'controller';
647
811
  await this.startController();
648
812
  return;
649
813
  }
814
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
650
815
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
651
816
  this.log.info('Setting default matterbridge start mode to bridge');
652
817
  await this.nodeContext?.set('bridgeMode', 'bridge');
653
818
  }
819
+ // Wait delay if specified (default 2 minutes) and the system uptime is less than 5 minutes. It solves race conditions on system startup.
654
820
  if (hasParameter('delay') && os.uptime() <= 60 * 5) {
655
821
  const { wait } = await import('./utils/wait.js');
656
822
  const delay = getIntParameter('delay') || 2000;
657
823
  this.log.warn('Delay switch found with system uptime less then 5 minutes. Waiting for ' + delay + ' seconds before starting matterbridge...');
658
824
  await wait(delay * 1000, 'Race condition delay', true);
659
825
  }
826
+ // Wait delay if specified (default 2 minutes). It solves race conditions on docker compose startup.
660
827
  if (hasParameter('fixed_delay')) {
661
828
  const { wait } = await import('./utils/wait.js');
662
829
  const delay = getIntParameter('fixed_delay') || 2000;
663
830
  this.log.warn('Fixed delay switch found. Waiting for ' + delay + ' seconds before starting matterbridge...');
664
831
  await wait(delay * 1000, 'Fixed race condition delay', true);
665
832
  }
833
+ // Start matterbridge in bridge mode
666
834
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
667
835
  this.bridgeMode = 'bridge';
668
836
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
669
837
  await this.startBridge();
670
838
  return;
671
839
  }
840
+ // Start matterbridge in childbridge mode
672
841
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
673
842
  this.bridgeMode = 'childbridge';
674
843
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -676,10 +845,22 @@ export class Matterbridge extends EventEmitter {
676
845
  return;
677
846
  }
678
847
  }
848
+ /**
849
+ * Asynchronously loads and starts the registered plugins.
850
+ *
851
+ * This method is responsible for initializing and starting all enabled plugins.
852
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
853
+ *
854
+ * @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving. Defaults to false.
855
+ * @param {boolean} [start] - If true, the method will start the plugins after loading them. Defaults to true.
856
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
857
+ */
679
858
  async startPlugins(wait = false, start = true) {
859
+ // Check, load and start the plugins
680
860
  for (const plugin of this.plugins) {
681
861
  plugin.configJson = await this.plugins.loadConfig(plugin);
682
862
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
863
+ // Check if the plugin is available
683
864
  if (!(await this.plugins.resolve(plugin.path))) {
684
865
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
685
866
  plugin.enabled = false;
@@ -699,10 +880,16 @@ export class Matterbridge extends EventEmitter {
699
880
  if (wait)
700
881
  await this.plugins.load(plugin, start, 'Matterbridge is starting');
701
882
  else
702
- this.plugins.load(plugin, start, 'Matterbridge is starting');
883
+ this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
703
884
  }
704
885
  this.frontend.wssSendRefreshRequired('plugins');
705
886
  }
887
+ /**
888
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
889
+ * - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
890
+ * - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
891
+ * - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
892
+ */
706
893
  registerProcessHandlers() {
707
894
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
708
895
  process.removeAllListeners('uncaughtException');
@@ -729,6 +916,9 @@ export class Matterbridge extends EventEmitter {
729
916
  };
730
917
  process.on('SIGTERM', this.sigtermHandler);
731
918
  }
919
+ /**
920
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
921
+ */
732
922
  deregisterProcessHandlers() {
733
923
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
734
924
  if (this.exceptionHandler)
@@ -745,13 +935,18 @@ export class Matterbridge extends EventEmitter {
745
935
  process.off('SIGTERM', this.sigtermHandler);
746
936
  this.sigtermHandler = undefined;
747
937
  }
938
+ /**
939
+ * Logs the node and system information.
940
+ */
748
941
  async logNodeAndSystemInfo() {
942
+ // IP address information
749
943
  const networkInterfaces = os.networkInterfaces();
750
944
  this.systemInformation.interfaceName = '';
751
945
  this.systemInformation.ipv4Address = '';
752
946
  this.systemInformation.ipv6Address = '';
753
947
  this.systemInformation.macAddress = '';
754
948
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
949
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
755
950
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
756
951
  continue;
757
952
  if (!interfaceDetails) {
@@ -777,16 +972,18 @@ export class Matterbridge extends EventEmitter {
777
972
  break;
778
973
  }
779
974
  }
975
+ // Node information
780
976
  this.systemInformation.nodeVersion = process.versions.node;
781
977
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
782
978
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
783
979
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
980
+ // Host system information
784
981
  this.systemInformation.hostname = os.hostname();
785
982
  this.systemInformation.user = os.userInfo().username;
786
- this.systemInformation.osType = os.type();
787
- this.systemInformation.osRelease = os.release();
788
- this.systemInformation.osPlatform = os.platform();
789
- this.systemInformation.osArch = os.arch();
983
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
984
+ this.systemInformation.osRelease = os.release(); // Kernel version
985
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
986
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
790
987
  this.systemInformation.totalMemory = formatBytes(os.totalmem());
791
988
  this.systemInformation.freeMemory = formatBytes(os.freemem());
792
989
  this.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -796,6 +993,7 @@ export class Matterbridge extends EventEmitter {
796
993
  this.systemInformation.rss = formatBytes(process.memoryUsage().rss);
797
994
  this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
798
995
  this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
996
+ // Log the system information
799
997
  this.log.debug('Host System Information:');
800
998
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
801
999
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -815,14 +1013,17 @@ export class Matterbridge extends EventEmitter {
815
1013
  this.log.debug(`- RSS: ${this.systemInformation.rss}`);
816
1014
  this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
817
1015
  this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
1016
+ // Log directories
818
1017
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
819
1018
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
820
1019
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
821
1020
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
822
1021
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1022
+ // Global node_modules directory
823
1023
  if (this.nodeContext)
824
1024
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
825
1025
  if (this.globalModulesDirectory === '') {
1026
+ // First run of Matterbridge so the node storage is empty
826
1027
  this.log.debug(`Getting global node_modules directory...`);
827
1028
  try {
828
1029
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -835,29 +1036,42 @@ export class Matterbridge extends EventEmitter {
835
1036
  }
836
1037
  }
837
1038
  else {
1039
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
838
1040
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
839
1041
  const { createESMWorker } = await import('./workers.js');
840
1042
  createESMWorker('NpmGlobalPrefix', path.join(this.rootDirectory, 'dist/workerGlobalPrefix.js'));
841
1043
  }
1044
+ // Matterbridge version
842
1045
  this.log.debug(`Reading matterbridge package.json...`);
843
1046
  const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
844
1047
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
845
1048
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1049
+ // Matterbridge latest version (will be set in the checkUpdate function)
846
1050
  if (this.nodeContext)
847
1051
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
848
1052
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1053
+ // Matterbridge dev version (will be set in the checkUpdate function)
849
1054
  if (this.nodeContext)
850
1055
  this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
851
1056
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1057
+ // Frontend version
852
1058
  this.log.debug(`Reading frontend package.json...`);
853
1059
  const frontendPackageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
854
1060
  this.frontendVersion = frontendPackageJson.version;
855
1061
  this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
1062
+ // Current working directory
856
1063
  const currentDir = process.cwd();
857
1064
  this.log.debug(`Current Working Directory: ${currentDir}`);
1065
+ // Command line arguments (excluding 'node' and the script name)
858
1066
  const cmdArgs = process.argv.slice(2).join(' ');
859
1067
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
860
1068
  }
1069
+ /**
1070
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
1071
+ *
1072
+ * @param {LogLevel} logLevel The logger logLevel to set.
1073
+ * @returns {Promise<LogLevel>} A promise that resolves when the logLevel has been set.
1074
+ */
861
1075
  async setLogLevel(logLevel) {
862
1076
  this.logLevel = logLevel;
863
1077
  this.log.logLevel = logLevel;
@@ -868,58 +1082,87 @@ export class Matterbridge extends EventEmitter {
868
1082
  for (const plugin of this.plugins) {
869
1083
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
870
1084
  continue;
871
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
872
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
873
- }
874
- let callbackLogLevel = "notice";
875
- if (logLevel === "info" || Logger.level === MatterLogLevel.INFO)
876
- callbackLogLevel = "info";
877
- if (logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG)
878
- callbackLogLevel = "debug";
1085
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel;
1086
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel);
1087
+ }
1088
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1089
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1090
+ if (logLevel === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1091
+ callbackLogLevel = "info" /* LogLevel.INFO */;
1092
+ if (logLevel === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1093
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
879
1094
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
880
1095
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
881
1096
  return logLevel;
882
1097
  }
1098
+ /**
1099
+ * Get the current logger logLevel.
1100
+ *
1101
+ * @returns {LogLevel} The current logger logLevel.
1102
+ */
883
1103
  getLogLevel() {
884
1104
  return this.log.logLevel;
885
1105
  }
1106
+ /**
1107
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1108
+ * It also logs to file (matter.log) if fileLogger is true.
1109
+ *
1110
+ * @param {boolean} fileLogger - Whether to log to file or not.
1111
+ * @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
1112
+ */
886
1113
  createDestinationMatterLogger(fileLogger) {
887
- this.matterLog.logNameColor = '\x1b[34m';
1114
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
888
1115
  if (fileLogger) {
889
1116
  this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
890
1117
  }
891
1118
  return (text, message) => {
1119
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
892
1120
  const logger = text.slice(44, 44 + 20).trim();
893
1121
  const msg = text.slice(65);
894
1122
  this.matterLog.logName = logger;
895
1123
  switch (message.level) {
896
1124
  case MatterLogLevel.DEBUG:
897
- this.matterLog.log("debug", msg);
1125
+ this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
898
1126
  break;
899
1127
  case MatterLogLevel.INFO:
900
- this.matterLog.log("info", msg);
1128
+ this.matterLog.log("info" /* LogLevel.INFO */, msg);
901
1129
  break;
902
1130
  case MatterLogLevel.NOTICE:
903
- this.matterLog.log("notice", msg);
1131
+ this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
904
1132
  break;
905
1133
  case MatterLogLevel.WARN:
906
- this.matterLog.log("warn", msg);
1134
+ this.matterLog.log("warn" /* LogLevel.WARN */, msg);
907
1135
  break;
908
1136
  case MatterLogLevel.ERROR:
909
- this.matterLog.log("error", msg);
1137
+ this.matterLog.log("error" /* LogLevel.ERROR */, msg);
910
1138
  break;
911
1139
  case MatterLogLevel.FATAL:
912
- this.matterLog.log("fatal", msg);
1140
+ this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
913
1141
  break;
914
1142
  }
915
1143
  };
916
1144
  }
1145
+ /**
1146
+ * Restarts the process by exiting the current instance and loading a new instance (/api/restart).
1147
+ *
1148
+ * @returns {Promise<void>} A promise that resolves when the restart is completed.
1149
+ */
917
1150
  async restartProcess() {
918
1151
  await this.cleanup('restarting...', true);
919
1152
  }
1153
+ /**
1154
+ * Shut down the process (/api/shutdown).
1155
+ *
1156
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1157
+ */
920
1158
  async shutdownProcess() {
921
1159
  await this.cleanup('shutting down...', false);
922
1160
  }
1161
+ /**
1162
+ * Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
1163
+ *
1164
+ * @returns {Promise<void>} A promise that resolves when the update is completed.
1165
+ */
923
1166
  async updateProcess() {
924
1167
  this.log.info('Updating matterbridge...');
925
1168
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -932,6 +1175,13 @@ export class Matterbridge extends EventEmitter {
932
1175
  this.frontend.wssSendRestartRequired();
933
1176
  await this.cleanup('updating...', false);
934
1177
  }
1178
+ /**
1179
+ * Unregister all devices and shut down the process (/api/unregister).
1180
+ *
1181
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1182
+ *
1183
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1184
+ */
935
1185
  async unregisterAndShutdownProcess(timeout = 1000) {
936
1186
  const { wait } = await import('./utils/wait.js');
937
1187
  this.log.info('Unregistering all devices and shutting down...');
@@ -944,46 +1194,71 @@ export class Matterbridge extends EventEmitter {
944
1194
  await this.removeAllBridgedEndpoints(plugin.name, 100);
945
1195
  }
946
1196
  this.log.debug('Waiting for the MessageExchange to finish...');
947
- await wait(timeout);
1197
+ await wait(timeout); // Wait for MessageExchange to finish
948
1198
  this.log.debug('Cleaning up and shutting down...');
949
1199
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
950
1200
  }
1201
+ /**
1202
+ * Reset commissioning and shut down the process (/api/reset).
1203
+ *
1204
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1205
+ */
951
1206
  async shutdownProcessAndReset() {
952
1207
  await this.cleanup('shutting down with reset...', false);
953
1208
  }
1209
+ /**
1210
+ * Factory reset and shut down the process (/api/factory-reset).
1211
+ *
1212
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1213
+ */
954
1214
  async shutdownProcessAndFactoryReset() {
955
1215
  await this.cleanup('shutting down with factory reset...', false);
956
1216
  }
1217
+ /**
1218
+ * Cleans up the Matterbridge instance.
1219
+ *
1220
+ * @param {string} message - The cleanup message.
1221
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1222
+ * @param {number} [pause] - The pause in ms to wait for the message exchange to complete in milliseconds. Default is 1000.
1223
+ *
1224
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1225
+ */
957
1226
  async cleanup(message, restart = false, pause = 1000) {
958
1227
  if (this.initialized && !this.hasCleanupStarted) {
959
1228
  this.emit('cleanup_started');
960
1229
  this.hasCleanupStarted = true;
961
1230
  this.log.info(message);
1231
+ // Clear the start matter interval
962
1232
  if (this.startMatterInterval) {
963
1233
  clearInterval(this.startMatterInterval);
964
1234
  this.startMatterInterval = undefined;
965
1235
  this.log.debug('Start matter interval cleared');
966
1236
  }
1237
+ // Clear the check update timeout
967
1238
  if (this.checkUpdateTimeout) {
968
1239
  clearTimeout(this.checkUpdateTimeout);
969
1240
  this.checkUpdateTimeout = undefined;
970
1241
  this.log.debug('Check update timeout cleared');
971
1242
  }
1243
+ // Clear the check update interval
972
1244
  if (this.checkUpdateInterval) {
973
1245
  clearInterval(this.checkUpdateInterval);
974
1246
  this.checkUpdateInterval = undefined;
975
1247
  this.log.debug('Check update interval cleared');
976
1248
  }
1249
+ // Clear the configure timeout
977
1250
  if (this.configureTimeout) {
978
1251
  clearTimeout(this.configureTimeout);
979
1252
  this.configureTimeout = undefined;
980
1253
  this.log.debug('Matterbridge configure timeout cleared');
981
1254
  }
1255
+ // Clear the reachability timeout
982
1256
  if (this.reachabilityTimeout) {
983
1257
  clearTimeout(this.reachabilityTimeout);
984
1258
  this.reachabilityTimeout = undefined;
985
1259
  this.log.debug('Matterbridge reachability timeout cleared');
986
1260
  }
1261
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
987
1262
  for (const plugin of this.plugins) {
988
1263
  if (!plugin.enabled || plugin.error)
989
1264
  continue;
@@ -994,6 +1269,7 @@ export class Matterbridge extends EventEmitter {
994
1269
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
995
1270
  }
996
1271
  }
1272
+ // Stop matter server nodes
997
1273
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
998
1274
  if (pause > 0) {
999
1275
  const { wait } = await import('./utils/wait.js');
@@ -1021,6 +1297,7 @@ export class Matterbridge extends EventEmitter {
1021
1297
  }
1022
1298
  }
1023
1299
  this.log.notice('Stopped matter server nodes');
1300
+ // Matter commisioning reset
1024
1301
  if (message === 'shutting down with reset...') {
1025
1302
  this.log.info('Resetting Matterbridge commissioning information...');
1026
1303
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1030,6 +1307,7 @@ export class Matterbridge extends EventEmitter {
1030
1307
  await this.matterbridgeContext?.clearAll();
1031
1308
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1032
1309
  }
1310
+ // Unregister all devices
1033
1311
  if (message === 'unregistered all devices and shutting down...') {
1034
1312
  if (this.bridgeMode === 'bridge') {
1035
1313
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1047,16 +1325,35 @@ export class Matterbridge extends EventEmitter {
1047
1325
  }
1048
1326
  this.log.info('Matter storage reset done!');
1049
1327
  }
1328
+ // Stop matter storage
1050
1329
  await this.stopMatterStorage();
1330
+ // Stop the frontend
1051
1331
  await this.frontend.stop();
1052
1332
  this.frontend.destroy();
1333
+ // Close PluginManager and DeviceManager
1053
1334
  this.plugins.destroy();
1054
1335
  this.devices.destroy();
1336
+ // Stop thread messaging server
1055
1337
  this.server.close();
1338
+ // Close the matterbridge node storage and context
1056
1339
  if (this.nodeStorage && this.nodeContext) {
1340
+ /*
1341
+ TODO: Implement serialization of registered devices
1342
+ this.log.info('Saving registered devices...');
1343
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1344
+ this.devices.forEach(async (device) => {
1345
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1346
+ this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1347
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1348
+ });
1349
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1350
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1351
+ */
1352
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1057
1353
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1058
1354
  await this.nodeContext.close();
1059
1355
  this.nodeContext = undefined;
1356
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1060
1357
  for (const plugin of this.plugins) {
1061
1358
  if (plugin.nodeContext) {
1062
1359
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1073,8 +1370,10 @@ export class Matterbridge extends EventEmitter {
1073
1370
  }
1074
1371
  this.plugins.clear();
1075
1372
  this.devices.clear();
1373
+ // Factory reset
1076
1374
  if (message === 'shutting down with factory reset...') {
1077
1375
  try {
1376
+ // Delete matter storage directory with its subdirectories and backup
1078
1377
  const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
1079
1378
  this.log.info(`Removing matter storage directory: ${dir}`);
1080
1379
  await fs.promises.rm(dir, { recursive: true });
@@ -1083,11 +1382,13 @@ export class Matterbridge extends EventEmitter {
1083
1382
  await fs.promises.rm(backup, { recursive: true });
1084
1383
  }
1085
1384
  catch (error) {
1385
+ // istanbul ignore next if
1086
1386
  if (error instanceof Error && error.code !== 'ENOENT') {
1087
1387
  this.log.error(`Error removing matter storage directory: ${error}`);
1088
1388
  }
1089
1389
  }
1090
1390
  try {
1391
+ // Delete matterbridge storage directory with its subdirectories and backup
1091
1392
  const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
1092
1393
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1093
1394
  await fs.promises.rm(dir, { recursive: true });
@@ -1096,18 +1397,20 @@ export class Matterbridge extends EventEmitter {
1096
1397
  await fs.promises.rm(backup, { recursive: true });
1097
1398
  }
1098
1399
  catch (error) {
1400
+ // istanbul ignore next if
1099
1401
  if (error instanceof Error && error.code !== 'ENOENT') {
1100
1402
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1101
1403
  }
1102
1404
  }
1103
1405
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1104
1406
  }
1407
+ // Deregisters the process handlers
1105
1408
  this.deregisterProcessHandlers();
1106
1409
  if (restart) {
1107
1410
  if (message === 'updating...') {
1108
1411
  this.log.info('Cleanup completed. Updating...');
1109
1412
  Matterbridge.instance = undefined;
1110
- this.emit('update');
1413
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1111
1414
  }
1112
1415
  else if (message === 'restarting...') {
1113
1416
  this.log.info('Cleanup completed. Restarting...');
@@ -1136,7 +1439,14 @@ export class Matterbridge extends EventEmitter {
1136
1439
  this.log.debug('Cleanup already started...');
1137
1440
  }
1138
1441
  }
1442
+ /**
1443
+ * Starts the Matterbridge in bridge mode.
1444
+ *
1445
+ * @private
1446
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1447
+ */
1139
1448
  async startBridge() {
1449
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1140
1450
  if (!this.matterStorageManager)
1141
1451
  throw new Error('No storage manager initialized');
1142
1452
  if (!this.matterbridgeContext)
@@ -1175,13 +1485,16 @@ export class Matterbridge extends EventEmitter {
1175
1485
  clearInterval(this.startMatterInterval);
1176
1486
  this.startMatterInterval = undefined;
1177
1487
  this.log.debug('Cleared startMatterInterval interval in bridge mode');
1178
- this.startServerNode(this.serverNode);
1488
+ // Start the Matter server node
1489
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1490
+ // Start the Matter server node of single devices in mode 'server'
1179
1491
  for (const device of this.devices.array()) {
1180
1492
  if (device.mode === 'server' && device.serverNode) {
1181
1493
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1182
- this.startServerNode(device.serverNode);
1494
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1183
1495
  }
1184
1496
  }
1497
+ // Configure the plugins
1185
1498
  this.configureTimeout = setTimeout(async () => {
1186
1499
  for (const plugin of this.plugins.array()) {
1187
1500
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1199,29 +1512,41 @@ export class Matterbridge extends EventEmitter {
1199
1512
  }
1200
1513
  this.frontend.wssSendRefreshRequired('plugins');
1201
1514
  }, 30 * 1000).unref();
1515
+ // Setting reachability to true
1202
1516
  this.reachabilityTimeout = setTimeout(() => {
1203
1517
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1204
1518
  if (this.aggregatorNode)
1205
1519
  this.setAggregatorReachability(this.aggregatorNode, true);
1206
1520
  }, 60 * 1000).unref();
1521
+ // Logger.get('LogServerNode').info(this.serverNode);
1207
1522
  this.emit('bridge_started');
1208
1523
  this.log.notice('Matterbridge bridge started successfully');
1209
1524
  this.frontend.wssSendRefreshRequired('settings');
1210
1525
  this.frontend.wssSendRefreshRequired('plugins');
1211
1526
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1212
1527
  }
1528
+ /**
1529
+ * Starts the Matterbridge in childbridge mode.
1530
+ *
1531
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1532
+ *
1533
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1534
+ */
1213
1535
  async startChildbridge(delay = 1000) {
1214
1536
  if (!this.matterStorageManager)
1215
1537
  throw new Error('No storage manager initialized');
1216
1538
  const { wait } = await import('./utils/wait.js');
1539
+ // Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
1217
1540
  this.log.debug('Loading all plugins in childbridge mode...');
1218
1541
  await this.startPlugins(true, false);
1542
+ // Create server nodes for DynamicPlatform plugins and start all plugins in the background
1219
1543
  this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
1220
1544
  for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
1221
1545
  if (plugin.type === 'DynamicPlatform')
1222
1546
  await this.createDynamicPlugin(plugin);
1223
- this.plugins.start(plugin, 'Matterbridge is starting');
1547
+ this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
1224
1548
  }
1549
+ // Start the Matterbridge in childbridge mode when all plugins are loaded and started
1225
1550
  this.log.debug('Starting start matter interval in childbridge mode...');
1226
1551
  let failCount = 0;
1227
1552
  this.startMatterInterval = setInterval(async () => {
@@ -1255,8 +1580,9 @@ export class Matterbridge extends EventEmitter {
1255
1580
  clearInterval(this.startMatterInterval);
1256
1581
  this.startMatterInterval = undefined;
1257
1582
  if (delay > 0)
1258
- await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
1583
+ await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1259
1584
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1585
+ // Configure the plugins
1260
1586
  this.configureTimeout = setTimeout(async () => {
1261
1587
  for (const plugin of this.plugins.array()) {
1262
1588
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1281,6 +1607,7 @@ export class Matterbridge extends EventEmitter {
1281
1607
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1282
1608
  continue;
1283
1609
  }
1610
+ // istanbul ignore next if cause is just a safety check
1284
1611
  if (!plugin.serverNode) {
1285
1612
  this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1286
1613
  continue;
@@ -1293,28 +1620,252 @@ export class Matterbridge extends EventEmitter {
1293
1620
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1294
1621
  continue;
1295
1622
  }
1296
- this.startServerNode(plugin.serverNode);
1623
+ // Start the Matter server node
1624
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1625
+ // Setting reachability to true
1297
1626
  plugin.reachabilityTimeout = setTimeout(() => {
1298
1627
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1299
1628
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1300
1629
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1301
1630
  }, 60 * 1000).unref();
1302
1631
  }
1632
+ // Start the Matter server node of single devices in mode 'server'
1303
1633
  for (const device of this.devices.array()) {
1304
1634
  if (device.mode === 'server' && device.serverNode) {
1305
1635
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1306
- this.startServerNode(device.serverNode);
1636
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1307
1637
  }
1308
1638
  }
1639
+ // Logger.get('LogServerNode').info(this.serverNode);
1309
1640
  this.emit('childbridge_started');
1310
1641
  this.log.notice('Matterbridge childbridge started successfully');
1311
1642
  this.frontend.wssSendRefreshRequired('settings');
1312
1643
  this.frontend.wssSendRefreshRequired('plugins');
1313
1644
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1314
1645
  }
1646
+ /**
1647
+ * Starts the Matterbridge controller.
1648
+ *
1649
+ * @private
1650
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1651
+ */
1315
1652
  async startController() {
1653
+ /*
1654
+ if (!this.matterStorageManager) {
1655
+ this.log.error('No storage manager initialized');
1656
+ await this.cleanup('No storage manager initialized');
1657
+ return;
1658
+ }
1659
+ this.log.info('Creating context: mattercontrollerContext');
1660
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1661
+ if (!this.controllerContext) {
1662
+ this.log.error('No storage context mattercontrollerContext initialized');
1663
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1664
+ return;
1665
+ }
1666
+
1667
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1668
+ this.matterServer = await this.createMatterServer(this.storageManager);
1669
+ this.log.info('Creating matter commissioning controller');
1670
+ this.commissioningController = new CommissioningController({
1671
+ autoConnect: false,
1672
+ });
1673
+ this.log.info('Adding matter commissioning controller to matter server');
1674
+ await this.matterServer.addCommissioningController(this.commissioningController);
1675
+
1676
+ this.log.info('Starting matter server');
1677
+ await this.matterServer.start();
1678
+ this.log.info('Matter server started');
1679
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1680
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1681
+ regulatoryCountryCode: 'XX',
1682
+ };
1683
+ const commissioningController = new CommissioningController({
1684
+ environment: {
1685
+ environment,
1686
+ id: uniqueId,
1687
+ },
1688
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1689
+ adminFabricLabel,
1690
+ });
1691
+
1692
+ if (hasParameter('pairingcode')) {
1693
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1694
+ const pairingCode = getParameter('pairingcode');
1695
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1696
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1697
+
1698
+ let longDiscriminator, setupPin, shortDiscriminator;
1699
+ if (pairingCode !== undefined) {
1700
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1701
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1702
+ longDiscriminator = undefined;
1703
+ setupPin = pairingCodeCodec.passcode;
1704
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1705
+ } else {
1706
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1707
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1708
+ setupPin = this.controllerContext.get('pin', 20202021);
1709
+ }
1710
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1711
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1712
+ }
1713
+
1714
+ const options = {
1715
+ commissioning: commissioningOptions,
1716
+ discovery: {
1717
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1718
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1719
+ },
1720
+ passcode: setupPin,
1721
+ } as NodeCommissioningOptions;
1722
+ this.log.info('Commissioning with options:', options);
1723
+ const nodeId = await this.commissioningController.commissionNode(options);
1724
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1725
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1726
+ } // (hasParameter('pairingcode'))
1727
+
1728
+ if (hasParameter('unpairall')) {
1729
+ this.log.info('***Commissioning controller unpairing all nodes...');
1730
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1731
+ for (const nodeId of nodeIds) {
1732
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1733
+ await this.commissioningController.removeNode(nodeId);
1734
+ }
1735
+ return;
1736
+ }
1737
+
1738
+ if (hasParameter('discover')) {
1739
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1740
+ // console.log(discover);
1741
+ }
1742
+
1743
+ if (!this.commissioningController.isCommissioned()) {
1744
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1745
+ return;
1746
+ }
1747
+
1748
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1749
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1750
+ for (const nodeId of nodeIds) {
1751
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1752
+
1753
+ const node = await this.commissioningController.connectNode(nodeId, {
1754
+ autoSubscribe: false,
1755
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1756
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1757
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1758
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1759
+ stateInformationCallback: (peerNodeId, info) => {
1760
+ switch (info) {
1761
+ case NodeStateInformation.Connected:
1762
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1763
+ break;
1764
+ case NodeStateInformation.Disconnected:
1765
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1766
+ break;
1767
+ case NodeStateInformation.Reconnecting:
1768
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1769
+ break;
1770
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1771
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1772
+ break;
1773
+ case NodeStateInformation.StructureChanged:
1774
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1775
+ break;
1776
+ case NodeStateInformation.Decommissioned:
1777
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1778
+ break;
1779
+ default:
1780
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1781
+ break;
1782
+ }
1783
+ },
1784
+ });
1785
+
1786
+ node.logStructure();
1787
+
1788
+ // Get the interaction client
1789
+ this.log.info('Getting the interaction client');
1790
+ const interactionClient = await node.getInteractionClient();
1791
+ let cluster;
1792
+ let attributes;
1793
+
1794
+ // Log BasicInformationCluster
1795
+ cluster = BasicInformationCluster;
1796
+ attributes = await interactionClient.getMultipleAttributes({
1797
+ attributes: [{ clusterId: cluster.id }],
1798
+ });
1799
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1800
+ attributes.forEach((attribute) => {
1801
+ this.log.info(
1802
+ `- 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}`,
1803
+ );
1804
+ });
1805
+
1806
+ // Log PowerSourceCluster
1807
+ cluster = PowerSourceCluster;
1808
+ attributes = await interactionClient.getMultipleAttributes({
1809
+ attributes: [{ clusterId: cluster.id }],
1810
+ });
1811
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1812
+ attributes.forEach((attribute) => {
1813
+ this.log.info(
1814
+ `- 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}`,
1815
+ );
1816
+ });
1817
+
1818
+ // Log ThreadNetworkDiagnostics
1819
+ cluster = ThreadNetworkDiagnosticsCluster;
1820
+ attributes = await interactionClient.getMultipleAttributes({
1821
+ attributes: [{ clusterId: cluster.id }],
1822
+ });
1823
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1824
+ attributes.forEach((attribute) => {
1825
+ this.log.info(
1826
+ `- 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}`,
1827
+ );
1828
+ });
1829
+
1830
+ // Log SwitchCluster
1831
+ cluster = SwitchCluster;
1832
+ attributes = await interactionClient.getMultipleAttributes({
1833
+ attributes: [{ clusterId: cluster.id }],
1834
+ });
1835
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1836
+ attributes.forEach((attribute) => {
1837
+ this.log.info(
1838
+ `- 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}`,
1839
+ );
1840
+ });
1841
+
1842
+ this.log.info('Subscribing to all attributes and events');
1843
+ await node.subscribeAllAttributesAndEvents({
1844
+ ignoreInitialTriggers: false,
1845
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1846
+ this.log.info(
1847
+ `***${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}`,
1848
+ ),
1849
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1850
+ this.log.info(
1851
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1852
+ );
1853
+ },
1854
+ });
1855
+ this.log.info('Subscribed to all attributes and events');
1856
+ }
1857
+ */
1316
1858
  }
1859
+ /** */
1860
+ /** Matter.js methods */
1861
+ /** */
1862
+ /**
1863
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1864
+ *
1865
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1866
+ */
1317
1867
  async startMatterStorage() {
1868
+ // Setup Matter storage
1318
1869
  this.log.info(`Starting matter node storage...`);
1319
1870
  this.matterStorageService = this.environment.get(StorageService);
1320
1871
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1322,8 +1873,17 @@ export class Matterbridge extends EventEmitter {
1322
1873
  this.log.info('Matter node storage manager "Matterbridge" created');
1323
1874
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1324
1875
  this.log.info('Matter node storage started');
1876
+ // Backup matter storage since it is created/opened correctly
1325
1877
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
1326
1878
  }
1879
+ /**
1880
+ * Makes a backup copy of the specified matter storage directory.
1881
+ *
1882
+ * @param {string} storageName - The name of the storage directory to be backed up.
1883
+ * @param {string} backupName - The name of the backup directory to be created.
1884
+ * @private
1885
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1886
+ */
1327
1887
  async backupMatterStorage(storageName, backupName) {
1328
1888
  this.log.info('Creating matter node storage backup...');
1329
1889
  try {
@@ -1334,6 +1894,11 @@ export class Matterbridge extends EventEmitter {
1334
1894
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1335
1895
  }
1336
1896
  }
1897
+ /**
1898
+ * Stops the matter storage.
1899
+ *
1900
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1901
+ */
1337
1902
  async stopMatterStorage() {
1338
1903
  this.log.info('Closing matter node storage...');
1339
1904
  await this.matterStorageManager?.close();
@@ -1342,6 +1907,20 @@ export class Matterbridge extends EventEmitter {
1342
1907
  this.matterbridgeContext = undefined;
1343
1908
  this.log.info('Matter node storage closed');
1344
1909
  }
1910
+ /**
1911
+ * Creates a server node storage context.
1912
+ *
1913
+ * @param {string} storeId - The storeId.
1914
+ * @param {string} deviceName - The name of the device.
1915
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1916
+ * @param {number} vendorId - The vendor ID.
1917
+ * @param {string} vendorName - The vendor name.
1918
+ * @param {number} productId - The product ID.
1919
+ * @param {string} productName - The product name.
1920
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1921
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
1922
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1923
+ */
1345
1924
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1346
1925
  const { randomBytes } = await import('node:crypto');
1347
1926
  if (!this.matterStorageService)
@@ -1381,6 +1960,15 @@ export class Matterbridge extends EventEmitter {
1381
1960
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1382
1961
  return storageContext;
1383
1962
  }
1963
+ /**
1964
+ * Creates a server node.
1965
+ *
1966
+ * @param {StorageContext} storageContext - The storage context for the server node.
1967
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
1968
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
1969
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
1970
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1971
+ */
1384
1972
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1385
1973
  const storeId = await storageContext.get('storeId');
1386
1974
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1390,25 +1978,35 @@ export class Matterbridge extends EventEmitter {
1390
1978
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1391
1979
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1392
1980
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1981
+ /**
1982
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1983
+ */
1393
1984
  const serverNode = await ServerNode.create({
1985
+ // Required: Give the Node a unique ID which is used to store the state of this node
1394
1986
  id: storeId,
1987
+ // Environment to run the server node in
1395
1988
  environment: this.environment,
1989
+ // Provide Network relevant configuration like the port
1396
1990
  network: {
1397
1991
  listeningAddressIpv4: this.ipv4Address,
1398
1992
  listeningAddressIpv6: this.ipv6Address,
1399
1993
  port,
1400
1994
  },
1995
+ // Provide the certificate for the device
1401
1996
  operationalCredentials: {
1402
1997
  certification: this.certification,
1403
1998
  },
1999
+ // Provide Commissioning relevant settings
1404
2000
  commissioning: {
1405
2001
  passcode,
1406
2002
  discriminator,
1407
2003
  },
2004
+ // Provide Node announcement settings
1408
2005
  productDescription: {
1409
2006
  name: await storageContext.get('deviceName'),
1410
2007
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1411
2008
  },
2009
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1412
2010
  basicInformation: {
1413
2011
  vendorId: VendorId(await storageContext.get('vendorId')),
1414
2012
  vendorName: await storageContext.get('vendorName'),
@@ -1425,17 +2023,23 @@ export class Matterbridge extends EventEmitter {
1425
2023
  reachable: true,
1426
2024
  },
1427
2025
  });
2026
+ /**
2027
+ * This event is triggered when the device is initially commissioned successfully.
2028
+ * This means: It is added to the first fabric.
2029
+ */
1428
2030
  serverNode.lifecycle.commissioned.on(() => {
1429
2031
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1430
2032
  this.advertisingNodes.delete(storeId);
1431
2033
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1432
2034
  });
2035
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1433
2036
  serverNode.lifecycle.decommissioned.on(() => {
1434
2037
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1435
2038
  this.advertisingNodes.delete(storeId);
1436
2039
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1437
2040
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1438
2041
  });
2042
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1439
2043
  serverNode.lifecycle.online.on(async () => {
1440
2044
  this.log.notice(`Server node for ${storeId} is online`);
1441
2045
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1446,13 +2050,16 @@ export class Matterbridge extends EventEmitter {
1446
2050
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1447
2051
  }
1448
2052
  else {
2053
+ // istanbul ignore next
1449
2054
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2055
+ // istanbul ignore next
1450
2056
  this.advertisingNodes.delete(storeId);
1451
2057
  }
1452
2058
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1453
2059
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1454
2060
  this.emit('online', storeId);
1455
2061
  });
2062
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1456
2063
  serverNode.lifecycle.offline.on(() => {
1457
2064
  this.log.notice(`Server node for ${storeId} is offline`);
1458
2065
  this.advertisingNodes.delete(storeId);
@@ -1460,11 +2067,15 @@ export class Matterbridge extends EventEmitter {
1460
2067
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1461
2068
  this.emit('offline', storeId);
1462
2069
  });
2070
+ /**
2071
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2072
+ * information is needed.
2073
+ */
1463
2074
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1464
2075
  let action = '';
1465
2076
  switch (fabricAction) {
1466
2077
  case FabricAction.Added:
1467
- this.advertisingNodes.delete(storeId);
2078
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1468
2079
  action = 'added';
1469
2080
  break;
1470
2081
  case FabricAction.Removed:
@@ -1477,14 +2088,22 @@ export class Matterbridge extends EventEmitter {
1477
2088
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1478
2089
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1479
2090
  });
2091
+ /**
2092
+ * This event is triggered when an operative new session was opened by a Controller.
2093
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2094
+ */
1480
2095
  serverNode.events.sessions.opened.on((session) => {
1481
2096
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1482
2097
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1483
2098
  });
2099
+ /**
2100
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2101
+ */
1484
2102
  serverNode.events.sessions.closed.on((session) => {
1485
2103
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1486
2104
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1487
2105
  });
2106
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1488
2107
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1489
2108
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1490
2109
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
@@ -1492,6 +2111,12 @@ export class Matterbridge extends EventEmitter {
1492
2111
  this.log.info(`Created server node for ${storeId}`);
1493
2112
  return serverNode;
1494
2113
  }
2114
+ /**
2115
+ * Gets the matter sanitized data of the specified server node.
2116
+ *
2117
+ * @param {ServerNode} [serverNode] - The server node to start.
2118
+ * @returns {ApiMatter} The sanitized data of the server node.
2119
+ */
1495
2120
  getServerNodeData(serverNode) {
1496
2121
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1497
2122
  return {
@@ -1508,12 +2133,25 @@ export class Matterbridge extends EventEmitter {
1508
2133
  serialNumber: serverNode.state.basicInformation.serialNumber,
1509
2134
  };
1510
2135
  }
2136
+ /**
2137
+ * Starts the specified server node.
2138
+ *
2139
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2140
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2141
+ */
1511
2142
  async startServerNode(matterServerNode) {
1512
2143
  if (!matterServerNode)
1513
2144
  return;
1514
2145
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1515
2146
  await matterServerNode.start();
1516
2147
  }
2148
+ /**
2149
+ * Stops the specified server node.
2150
+ *
2151
+ * @param {ServerNode} matterServerNode - The server node to stop.
2152
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2153
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2154
+ */
1517
2155
  async stopServerNode(matterServerNode, timeout = 30000) {
1518
2156
  const { withTimeout } = await import('./utils/wait.js');
1519
2157
  if (!matterServerNode)
@@ -1527,12 +2165,25 @@ export class Matterbridge extends EventEmitter {
1527
2165
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1528
2166
  }
1529
2167
  }
2168
+ /**
2169
+ * Creates an aggregator node with the specified storage context.
2170
+ *
2171
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2172
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2173
+ */
1530
2174
  async createAggregatorNode(storageContext) {
1531
2175
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1532
2176
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1533
2177
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1534
2178
  return aggregatorNode;
1535
2179
  }
2180
+ /**
2181
+ * Creates and configures the server node for an accessory plugin for a given device.
2182
+ *
2183
+ * @param {Plugin} plugin - The plugin to configure.
2184
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2185
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2186
+ */
1536
2187
  async createAccessoryPlugin(plugin, device) {
1537
2188
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1538
2189
  plugin.locked = true;
@@ -1544,6 +2195,12 @@ export class Matterbridge extends EventEmitter {
1544
2195
  await plugin.serverNode.add(device);
1545
2196
  }
1546
2197
  }
2198
+ /**
2199
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
2200
+ *
2201
+ * @param {Plugin} plugin - The plugin to configure.
2202
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
2203
+ */
1547
2204
  async createDynamicPlugin(plugin) {
1548
2205
  if (!plugin.locked) {
1549
2206
  plugin.locked = true;
@@ -1556,6 +2213,13 @@ export class Matterbridge extends EventEmitter {
1556
2213
  await plugin.serverNode.add(plugin.aggregatorNode);
1557
2214
  }
1558
2215
  }
2216
+ /**
2217
+ * Creates and configures the server node for a single not bridged device.
2218
+ *
2219
+ * @param {Plugin} plugin - The plugin to configure.
2220
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2221
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2222
+ */
1559
2223
  async createDeviceServerNode(plugin, device) {
1560
2224
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1561
2225
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1566,8 +2230,16 @@ export class Matterbridge extends EventEmitter {
1566
2230
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1567
2231
  }
1568
2232
  }
2233
+ /**
2234
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2235
+ *
2236
+ * @param {string} pluginName - The name of the plugin.
2237
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2238
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2239
+ */
1569
2240
  async addBridgedEndpoint(pluginName, device) {
1570
2241
  const { waiter } = await import('./utils/wait.js');
2242
+ // Check if the plugin is registered
1571
2243
  const plugin = this.plugins.get(pluginName);
1572
2244
  if (!plugin) {
1573
2245
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1587,6 +2259,7 @@ export class Matterbridge extends EventEmitter {
1587
2259
  }
1588
2260
  else if (this.bridgeMode === 'bridge') {
1589
2261
  if (device.mode === 'matter') {
2262
+ // Register and add the device to the matterbridge server node
1590
2263
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1591
2264
  if (!this.serverNode) {
1592
2265
  this.log.error('Server node not found for Matterbridge');
@@ -1603,6 +2276,7 @@ export class Matterbridge extends EventEmitter {
1603
2276
  }
1604
2277
  }
1605
2278
  else {
2279
+ // Register and add the device to the matterbridge aggregator node
1606
2280
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1607
2281
  if (!this.aggregatorNode) {
1608
2282
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1620,6 +2294,7 @@ export class Matterbridge extends EventEmitter {
1620
2294
  }
1621
2295
  }
1622
2296
  else if (this.bridgeMode === 'childbridge') {
2297
+ // Register and add the device to the plugin server node
1623
2298
  if (plugin.type === 'AccessoryPlatform') {
1624
2299
  try {
1625
2300
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1643,10 +2318,12 @@ export class Matterbridge extends EventEmitter {
1643
2318
  return;
1644
2319
  }
1645
2320
  }
2321
+ // Register and add the device to the plugin aggregator node
1646
2322
  if (plugin.type === 'DynamicPlatform') {
1647
2323
  try {
1648
2324
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1649
2325
  await this.createDynamicPlugin(plugin);
2326
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1650
2327
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1651
2328
  if (!plugin.aggregatorNode) {
1652
2329
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1667,17 +2344,28 @@ export class Matterbridge extends EventEmitter {
1667
2344
  }
1668
2345
  if (plugin.registeredDevices !== undefined)
1669
2346
  plugin.registeredDevices++;
2347
+ // Add the device to the DeviceManager
1670
2348
  this.devices.set(device);
2349
+ // Subscribe to the attributes changed event
1671
2350
  await this.subscribeAttributeChanged(plugin, device);
1672
2351
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1673
2352
  }
2353
+ /**
2354
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2355
+ *
2356
+ * @param {string} pluginName - The name of the plugin.
2357
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2358
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2359
+ */
1674
2360
  async removeBridgedEndpoint(pluginName, device) {
1675
2361
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2362
+ // Check if the plugin is registered
1676
2363
  const plugin = this.plugins.get(pluginName);
1677
2364
  if (!plugin) {
1678
2365
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1679
2366
  return;
1680
2367
  }
2368
+ // Unregister and remove the device from the matterbridge aggregator node
1681
2369
  if (this.bridgeMode === 'bridge') {
1682
2370
  if (!this.aggregatorNode) {
1683
2371
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1690,8 +2378,10 @@ export class Matterbridge extends EventEmitter {
1690
2378
  }
1691
2379
  else if (this.bridgeMode === 'childbridge') {
1692
2380
  if (plugin.type === 'AccessoryPlatform') {
2381
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1693
2382
  }
1694
2383
  else if (plugin.type === 'DynamicPlatform') {
2384
+ // Unregister and remove the device from the plugin aggregator node
1695
2385
  if (!plugin.aggregatorNode) {
1696
2386
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
1697
2387
  return;
@@ -1702,8 +2392,21 @@ export class Matterbridge extends EventEmitter {
1702
2392
  if (plugin.registeredDevices !== undefined)
1703
2393
  plugin.registeredDevices--;
1704
2394
  }
2395
+ // Remove the device from the DeviceManager
1705
2396
  this.devices.remove(device);
1706
2397
  }
2398
+ /**
2399
+ * Removes all bridged endpoints from the specified plugin.
2400
+ *
2401
+ * @param {string} pluginName - The name of the plugin.
2402
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2403
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2404
+ *
2405
+ * @remarks
2406
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2407
+ * It also applies a delay between each removal if specified.
2408
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2409
+ */
1707
2410
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1708
2411
  const { wait } = await import('./utils/wait.js');
1709
2412
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
@@ -1715,8 +2418,28 @@ export class Matterbridge extends EventEmitter {
1715
2418
  if (delay > 0)
1716
2419
  await wait(2000);
1717
2420
  }
2421
+ /**
2422
+ * Registers a virtual device.
2423
+ * Virtual devices are only supported in bridge mode and childbridge mode with a DynamicPlatform.
2424
+ *
2425
+ * The virtual device is created as an instance of `Endpoint` with the provided device type.
2426
+ * When the virtual device is turned on, the provided callback function is executed.
2427
+ * The onOff state of the virtual device always reverts to false when the device is turned on.
2428
+ *
2429
+ * @param { string } pluginName - The name of the plugin to register the virtual device under.
2430
+ * @param { string } name - The name of the virtual device.
2431
+ * @param { 'light' | 'outlet' | 'switch' | 'mounted_switch' } type - The type of the virtual device.
2432
+ * @param { () => Promise<void> } callback - The callback to call when the virtual device is turned on.
2433
+ *
2434
+ * @returns {Promise<boolean>} A promise that resolves to true if the virtual device was successfully registered, false otherwise.
2435
+ *
2436
+ * @remarks
2437
+ * The virtual devices don't show up in the device list of the frontend.
2438
+ * Type 'switch' is not supported by Alexa and 'mounted_switch' is not supported by Apple Home.
2439
+ */
1718
2440
  async addVirtualEndpoint(pluginName, name, type, callback) {
1719
2441
  this.log.debug(`Adding virtual endpoint ${plg}${pluginName}${db}:${dev}${name}${db}...`);
2442
+ // Check if the plugin is registered
1720
2443
  const plugin = this.plugins.get(pluginName);
1721
2444
  if (!plugin) {
1722
2445
  this.log.error(`Error adding virtual endpoint ${dev}${name}${er} for plugin ${plg}${pluginName}${er}: plugin not found`);
@@ -1743,13 +2466,24 @@ export class Matterbridge extends EventEmitter {
1743
2466
  this.log.error(`Virtual endpoint ${dev}${name}${er} for plugin ${plg}${pluginName}${er} not created. Virtual endpoints are only supported in bridge mode and childbridge mode with a DynamicPlatform.`);
1744
2467
  return false;
1745
2468
  }
2469
+ /**
2470
+ * Subscribes to the attribute change event for the given device and plugin.
2471
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2472
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2473
+ *
2474
+ * @param {Plugin} plugin - The plugin associated with the device.
2475
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2476
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2477
+ */
1746
2478
  async subscribeAttributeChanged(plugin, device) {
1747
2479
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
1748
2480
  return;
1749
2481
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
2482
+ // Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
1750
2483
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1751
2484
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1752
2485
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
2486
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1753
2487
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
1754
2488
  });
1755
2489
  }
@@ -1799,6 +2533,7 @@ export class Matterbridge extends EventEmitter {
1799
2533
  this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1800
2534
  await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1801
2535
  this.log.debug(`Bridged endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${isValidObject(value) ? debugStringify(value) : value}${db}`);
2536
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1802
2537
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
1803
2538
  });
1804
2539
  }
@@ -1807,12 +2542,19 @@ export class Matterbridge extends EventEmitter {
1807
2542
  this.log.debug(`Subscribing to child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1808
2543
  await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1809
2544
  this.log.debug(`Bridged child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${isValidObject(value) ? debugStringify(value) : value}${db}`);
2545
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1810
2546
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
1811
2547
  });
1812
2548
  }
1813
2549
  }
1814
2550
  }
1815
2551
  }
2552
+ /**
2553
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2554
+ *
2555
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2556
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2557
+ */
1816
2558
  sanitizeFabricInformations(fabricInfo) {
1817
2559
  return fabricInfo.map((info) => {
1818
2560
  return {
@@ -1826,6 +2568,12 @@ export class Matterbridge extends EventEmitter {
1826
2568
  };
1827
2569
  });
1828
2570
  }
2571
+ /**
2572
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2573
+ *
2574
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2575
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2576
+ */
1829
2577
  sanitizeSessionInformation(sessions) {
1830
2578
  return sessions
1831
2579
  .filter((session) => session.isPeerActive)
@@ -1852,7 +2600,21 @@ export class Matterbridge extends EventEmitter {
1852
2600
  };
1853
2601
  });
1854
2602
  }
2603
+ /**
2604
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2605
+ *
2606
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2607
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2608
+ */
2609
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1855
2610
  async setAggregatorReachability(aggregatorNode, reachable) {
2611
+ /*
2612
+ for (const child of aggregatorNode.parts) {
2613
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2614
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2615
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2616
+ }
2617
+ */
1856
2618
  }
1857
2619
  getVendorIdName = (vendorId) => {
1858
2620
  if (!vendorId)
@@ -1892,10 +2654,11 @@ export class Matterbridge extends EventEmitter {
1892
2654
  case 0x1488:
1893
2655
  vendorName = '(ShortcutLabsFlic)';
1894
2656
  break;
1895
- case 65521:
2657
+ case 65521: // 0xFFF1
1896
2658
  vendorName = '(MatterTest)';
1897
2659
  break;
1898
2660
  }
1899
2661
  return vendorName;
1900
2662
  };
1901
2663
  }
2664
+ //# sourceMappingURL=matterbridge.js.map