matterbridge 3.4.3-dev-20251209-e6cb85f → 3.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/README.md +2 -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 +811 -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
  324. package/scripts/data_model.mjs +2058 -0
@@ -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') || 120;
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') || 120;
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)
@@ -1149,6 +1459,7 @@ export class Matterbridge extends EventEmitter {
1149
1459
  this.log.debug('Starting start matter interval in bridge mode...');
1150
1460
  let failCount = 0;
1151
1461
  this.startMatterInterval = setInterval(async () => {
1462
+ // istanbul ignore if cause is just a logging statement
1152
1463
  if (failCount && failCount % 10 === 0) {
1153
1464
  this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
1154
1465
  this.frontend.wssSendRefreshRequired('plugins');
@@ -1181,13 +1492,16 @@ export class Matterbridge extends EventEmitter {
1181
1492
  clearInterval(this.startMatterInterval);
1182
1493
  this.startMatterInterval = undefined;
1183
1494
  this.log.debug('Cleared startMatterInterval interval in bridge mode');
1184
- this.startServerNode(this.serverNode);
1495
+ // Start the Matter server node
1496
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1497
+ // Start the Matter server node of single devices in mode 'server'
1185
1498
  for (const device of this.devices.array()) {
1186
1499
  if (device.mode === 'server' && device.serverNode) {
1187
1500
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1188
- this.startServerNode(device.serverNode);
1501
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1189
1502
  }
1190
1503
  }
1504
+ // Configure the plugins
1191
1505
  this.configureTimeout = setTimeout(async () => {
1192
1506
  for (const plugin of this.plugins.array()) {
1193
1507
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1205,32 +1519,45 @@ export class Matterbridge extends EventEmitter {
1205
1519
  }
1206
1520
  this.frontend.wssSendRefreshRequired('plugins');
1207
1521
  }, 30 * 1000).unref();
1522
+ // Setting reachability to true
1208
1523
  this.reachabilityTimeout = setTimeout(() => {
1209
1524
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1210
1525
  if (this.aggregatorNode)
1211
1526
  this.setAggregatorReachability(this.aggregatorNode, true);
1212
1527
  }, 60 * 1000).unref();
1528
+ // Logger.get('LogServerNode').info(this.serverNode);
1213
1529
  this.emit('bridge_started');
1214
1530
  this.log.notice('Matterbridge bridge started successfully');
1215
1531
  this.frontend.wssSendRefreshRequired('settings');
1216
1532
  this.frontend.wssSendRefreshRequired('plugins');
1217
1533
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1218
1534
  }
1535
+ /**
1536
+ * Starts the Matterbridge in childbridge mode.
1537
+ *
1538
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1539
+ *
1540
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1541
+ */
1219
1542
  async startChildbridge(delay = 1000) {
1220
1543
  if (!this.matterStorageManager)
1221
1544
  throw new Error('No storage manager initialized');
1222
1545
  const { wait } = await import('./utils/wait.js');
1546
+ // Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
1223
1547
  this.log.debug('Loading all plugins in childbridge mode...');
1224
1548
  await this.startPlugins(true, false);
1549
+ // Create server nodes for DynamicPlatform plugins and start all plugins in the background
1225
1550
  this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
1226
1551
  for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
1227
1552
  if (plugin.type === 'DynamicPlatform')
1228
1553
  await this.createDynamicPlugin(plugin);
1229
- this.plugins.start(plugin, 'Matterbridge is starting');
1554
+ this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
1230
1555
  }
1556
+ // Start the Matterbridge in childbridge mode when all plugins are loaded and started
1231
1557
  this.log.debug('Starting start matter interval in childbridge mode...');
1232
1558
  let failCount = 0;
1233
1559
  this.startMatterInterval = setInterval(async () => {
1560
+ // istanbul ignore if cause is just a logging statement
1234
1561
  if (failCount && failCount % 10 === 0) {
1235
1562
  this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
1236
1563
  this.frontend.wssSendRefreshRequired('plugins');
@@ -1267,8 +1594,9 @@ export class Matterbridge extends EventEmitter {
1267
1594
  clearInterval(this.startMatterInterval);
1268
1595
  this.startMatterInterval = undefined;
1269
1596
  if (delay > 0)
1270
- await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
1597
+ await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1271
1598
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1599
+ // Configure the plugins
1272
1600
  this.configureTimeout = setTimeout(async () => {
1273
1601
  for (const plugin of this.plugins.array()) {
1274
1602
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1293,6 +1621,7 @@ export class Matterbridge extends EventEmitter {
1293
1621
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1294
1622
  continue;
1295
1623
  }
1624
+ // istanbul ignore next if cause is just a safety check
1296
1625
  if (!plugin.serverNode) {
1297
1626
  this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1298
1627
  continue;
@@ -1305,28 +1634,252 @@ export class Matterbridge extends EventEmitter {
1305
1634
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1306
1635
  continue;
1307
1636
  }
1308
- this.startServerNode(plugin.serverNode);
1637
+ // Start the Matter server node
1638
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1639
+ // Setting reachability to true
1309
1640
  plugin.reachabilityTimeout = setTimeout(() => {
1310
1641
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1311
1642
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1312
1643
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1313
1644
  }, 60 * 1000).unref();
1314
1645
  }
1646
+ // Start the Matter server node of single devices in mode 'server'
1315
1647
  for (const device of this.devices.array()) {
1316
1648
  if (device.mode === 'server' && device.serverNode) {
1317
1649
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1318
- this.startServerNode(device.serverNode);
1650
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1319
1651
  }
1320
1652
  }
1653
+ // Logger.get('LogServerNode').info(this.serverNode);
1321
1654
  this.emit('childbridge_started');
1322
1655
  this.log.notice('Matterbridge childbridge started successfully');
1323
1656
  this.frontend.wssSendRefreshRequired('settings');
1324
1657
  this.frontend.wssSendRefreshRequired('plugins');
1325
1658
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1326
1659
  }
1660
+ /**
1661
+ * Starts the Matterbridge controller.
1662
+ *
1663
+ * @private
1664
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1665
+ */
1327
1666
  async startController() {
1667
+ /*
1668
+ if (!this.matterStorageManager) {
1669
+ this.log.error('No storage manager initialized');
1670
+ await this.cleanup('No storage manager initialized');
1671
+ return;
1672
+ }
1673
+ this.log.info('Creating context: mattercontrollerContext');
1674
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1675
+ if (!this.controllerContext) {
1676
+ this.log.error('No storage context mattercontrollerContext initialized');
1677
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1678
+ return;
1679
+ }
1680
+
1681
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1682
+ this.matterServer = await this.createMatterServer(this.storageManager);
1683
+ this.log.info('Creating matter commissioning controller');
1684
+ this.commissioningController = new CommissioningController({
1685
+ autoConnect: false,
1686
+ });
1687
+ this.log.info('Adding matter commissioning controller to matter server');
1688
+ await this.matterServer.addCommissioningController(this.commissioningController);
1689
+
1690
+ this.log.info('Starting matter server');
1691
+ await this.matterServer.start();
1692
+ this.log.info('Matter server started');
1693
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1694
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1695
+ regulatoryCountryCode: 'XX',
1696
+ };
1697
+ const commissioningController = new CommissioningController({
1698
+ environment: {
1699
+ environment,
1700
+ id: uniqueId,
1701
+ },
1702
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1703
+ adminFabricLabel,
1704
+ });
1705
+
1706
+ if (hasParameter('pairingcode')) {
1707
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1708
+ const pairingCode = getParameter('pairingcode');
1709
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1710
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1711
+
1712
+ let longDiscriminator, setupPin, shortDiscriminator;
1713
+ if (pairingCode !== undefined) {
1714
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1715
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1716
+ longDiscriminator = undefined;
1717
+ setupPin = pairingCodeCodec.passcode;
1718
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1719
+ } else {
1720
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1721
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1722
+ setupPin = this.controllerContext.get('pin', 20202021);
1723
+ }
1724
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1725
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1726
+ }
1727
+
1728
+ const options = {
1729
+ commissioning: commissioningOptions,
1730
+ discovery: {
1731
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1732
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1733
+ },
1734
+ passcode: setupPin,
1735
+ } as NodeCommissioningOptions;
1736
+ this.log.info('Commissioning with options:', options);
1737
+ const nodeId = await this.commissioningController.commissionNode(options);
1738
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1739
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1740
+ } // (hasParameter('pairingcode'))
1741
+
1742
+ if (hasParameter('unpairall')) {
1743
+ this.log.info('***Commissioning controller unpairing all nodes...');
1744
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1745
+ for (const nodeId of nodeIds) {
1746
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1747
+ await this.commissioningController.removeNode(nodeId);
1748
+ }
1749
+ return;
1750
+ }
1751
+
1752
+ if (hasParameter('discover')) {
1753
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1754
+ // console.log(discover);
1755
+ }
1756
+
1757
+ if (!this.commissioningController.isCommissioned()) {
1758
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1759
+ return;
1760
+ }
1761
+
1762
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1763
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1764
+ for (const nodeId of nodeIds) {
1765
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1766
+
1767
+ const node = await this.commissioningController.connectNode(nodeId, {
1768
+ autoSubscribe: false,
1769
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1770
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1771
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1772
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1773
+ stateInformationCallback: (peerNodeId, info) => {
1774
+ switch (info) {
1775
+ case NodeStateInformation.Connected:
1776
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1777
+ break;
1778
+ case NodeStateInformation.Disconnected:
1779
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1780
+ break;
1781
+ case NodeStateInformation.Reconnecting:
1782
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1783
+ break;
1784
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1785
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1786
+ break;
1787
+ case NodeStateInformation.StructureChanged:
1788
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1789
+ break;
1790
+ case NodeStateInformation.Decommissioned:
1791
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1792
+ break;
1793
+ default:
1794
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1795
+ break;
1796
+ }
1797
+ },
1798
+ });
1799
+
1800
+ node.logStructure();
1801
+
1802
+ // Get the interaction client
1803
+ this.log.info('Getting the interaction client');
1804
+ const interactionClient = await node.getInteractionClient();
1805
+ let cluster;
1806
+ let attributes;
1807
+
1808
+ // Log BasicInformationCluster
1809
+ cluster = BasicInformationCluster;
1810
+ attributes = await interactionClient.getMultipleAttributes({
1811
+ attributes: [{ clusterId: cluster.id }],
1812
+ });
1813
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1814
+ attributes.forEach((attribute) => {
1815
+ this.log.info(
1816
+ `- 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}`,
1817
+ );
1818
+ });
1819
+
1820
+ // Log PowerSourceCluster
1821
+ cluster = PowerSourceCluster;
1822
+ attributes = await interactionClient.getMultipleAttributes({
1823
+ attributes: [{ clusterId: cluster.id }],
1824
+ });
1825
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1826
+ attributes.forEach((attribute) => {
1827
+ this.log.info(
1828
+ `- 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}`,
1829
+ );
1830
+ });
1831
+
1832
+ // Log ThreadNetworkDiagnostics
1833
+ cluster = ThreadNetworkDiagnosticsCluster;
1834
+ attributes = await interactionClient.getMultipleAttributes({
1835
+ attributes: [{ clusterId: cluster.id }],
1836
+ });
1837
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1838
+ attributes.forEach((attribute) => {
1839
+ this.log.info(
1840
+ `- 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}`,
1841
+ );
1842
+ });
1843
+
1844
+ // Log SwitchCluster
1845
+ cluster = SwitchCluster;
1846
+ attributes = await interactionClient.getMultipleAttributes({
1847
+ attributes: [{ clusterId: cluster.id }],
1848
+ });
1849
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1850
+ attributes.forEach((attribute) => {
1851
+ this.log.info(
1852
+ `- 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}`,
1853
+ );
1854
+ });
1855
+
1856
+ this.log.info('Subscribing to all attributes and events');
1857
+ await node.subscribeAllAttributesAndEvents({
1858
+ ignoreInitialTriggers: false,
1859
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1860
+ this.log.info(
1861
+ `***${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}`,
1862
+ ),
1863
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1864
+ this.log.info(
1865
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1866
+ );
1867
+ },
1868
+ });
1869
+ this.log.info('Subscribed to all attributes and events');
1870
+ }
1871
+ */
1328
1872
  }
1873
+ /** */
1874
+ /** Matter.js methods */
1875
+ /** */
1876
+ /**
1877
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1878
+ *
1879
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1880
+ */
1329
1881
  async startMatterStorage() {
1882
+ // Setup Matter storage
1330
1883
  this.log.info(`Starting matter node storage...`);
1331
1884
  this.matterStorageService = this.environment.get(StorageService);
1332
1885
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1334,8 +1887,17 @@ export class Matterbridge extends EventEmitter {
1334
1887
  this.log.info('Matter node storage manager "Matterbridge" created');
1335
1888
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1336
1889
  this.log.info('Matter node storage started');
1890
+ // Backup matter storage since it is created/opened correctly
1337
1891
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
1338
1892
  }
1893
+ /**
1894
+ * Makes a backup copy of the specified matter storage directory.
1895
+ *
1896
+ * @param {string} storageName - The name of the storage directory to be backed up.
1897
+ * @param {string} backupName - The name of the backup directory to be created.
1898
+ * @private
1899
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1900
+ */
1339
1901
  async backupMatterStorage(storageName, backupName) {
1340
1902
  this.log.info('Creating matter node storage backup...');
1341
1903
  try {
@@ -1346,6 +1908,11 @@ export class Matterbridge extends EventEmitter {
1346
1908
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1347
1909
  }
1348
1910
  }
1911
+ /**
1912
+ * Stops the matter storage.
1913
+ *
1914
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1915
+ */
1349
1916
  async stopMatterStorage() {
1350
1917
  this.log.info('Closing matter node storage...');
1351
1918
  await this.matterStorageManager?.close();
@@ -1354,6 +1921,20 @@ export class Matterbridge extends EventEmitter {
1354
1921
  this.matterbridgeContext = undefined;
1355
1922
  this.log.info('Matter node storage closed');
1356
1923
  }
1924
+ /**
1925
+ * Creates a server node storage context.
1926
+ *
1927
+ * @param {string} storeId - The storeId.
1928
+ * @param {string} deviceName - The name of the device.
1929
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1930
+ * @param {number} vendorId - The vendor ID.
1931
+ * @param {string} vendorName - The vendor name.
1932
+ * @param {number} productId - The product ID.
1933
+ * @param {string} productName - The product name.
1934
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1935
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
1936
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1937
+ */
1357
1938
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1358
1939
  const { randomBytes } = await import('node:crypto');
1359
1940
  if (!this.matterStorageService)
@@ -1393,6 +1974,15 @@ export class Matterbridge extends EventEmitter {
1393
1974
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1394
1975
  return storageContext;
1395
1976
  }
1977
+ /**
1978
+ * Creates a server node.
1979
+ *
1980
+ * @param {StorageContext} storageContext - The storage context for the server node.
1981
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
1982
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
1983
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
1984
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1985
+ */
1396
1986
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1397
1987
  const storeId = await storageContext.get('storeId');
1398
1988
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1402,25 +1992,35 @@ export class Matterbridge extends EventEmitter {
1402
1992
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1403
1993
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1404
1994
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1995
+ /**
1996
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1997
+ */
1405
1998
  const serverNode = await ServerNode.create({
1999
+ // Required: Give the Node a unique ID which is used to store the state of this node
1406
2000
  id: storeId,
2001
+ // Environment to run the server node in
1407
2002
  environment: this.environment,
2003
+ // Provide Network relevant configuration like the port
1408
2004
  network: {
1409
2005
  listeningAddressIpv4: this.ipv4Address,
1410
2006
  listeningAddressIpv6: this.ipv6Address,
1411
2007
  port,
1412
2008
  },
2009
+ // Provide the certificate for the device
1413
2010
  operationalCredentials: {
1414
2011
  certification: this.certification,
1415
2012
  },
2013
+ // Provide Commissioning relevant settings
1416
2014
  commissioning: {
1417
2015
  passcode,
1418
2016
  discriminator,
1419
2017
  },
2018
+ // Provide Node announcement settings
1420
2019
  productDescription: {
1421
2020
  name: await storageContext.get('deviceName'),
1422
2021
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1423
2022
  },
2023
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1424
2024
  basicInformation: {
1425
2025
  vendorId: VendorId(await storageContext.get('vendorId')),
1426
2026
  vendorName: await storageContext.get('vendorName'),
@@ -1437,17 +2037,23 @@ export class Matterbridge extends EventEmitter {
1437
2037
  reachable: true,
1438
2038
  },
1439
2039
  });
2040
+ /**
2041
+ * This event is triggered when the device is initially commissioned successfully.
2042
+ * This means: It is added to the first fabric.
2043
+ */
1440
2044
  serverNode.lifecycle.commissioned.on(() => {
1441
2045
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1442
2046
  this.advertisingNodes.delete(storeId);
1443
2047
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1444
2048
  });
2049
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1445
2050
  serverNode.lifecycle.decommissioned.on(() => {
1446
2051
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1447
2052
  this.advertisingNodes.delete(storeId);
1448
2053
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1449
2054
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1450
2055
  });
2056
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1451
2057
  serverNode.lifecycle.online.on(async () => {
1452
2058
  this.log.notice(`Server node for ${storeId} is online`);
1453
2059
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1458,13 +2064,16 @@ export class Matterbridge extends EventEmitter {
1458
2064
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1459
2065
  }
1460
2066
  else {
2067
+ // istanbul ignore next
1461
2068
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2069
+ // istanbul ignore next
1462
2070
  this.advertisingNodes.delete(storeId);
1463
2071
  }
1464
2072
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1465
2073
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1466
2074
  this.emit('online', storeId);
1467
2075
  });
2076
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1468
2077
  serverNode.lifecycle.offline.on(() => {
1469
2078
  this.log.notice(`Server node for ${storeId} is offline`);
1470
2079
  this.advertisingNodes.delete(storeId);
@@ -1472,11 +2081,15 @@ export class Matterbridge extends EventEmitter {
1472
2081
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1473
2082
  this.emit('offline', storeId);
1474
2083
  });
2084
+ /**
2085
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2086
+ * information is needed.
2087
+ */
1475
2088
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1476
2089
  let action = '';
1477
2090
  switch (fabricAction) {
1478
2091
  case FabricAction.Added:
1479
- this.advertisingNodes.delete(storeId);
2092
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1480
2093
  action = 'added';
1481
2094
  break;
1482
2095
  case FabricAction.Removed:
@@ -1489,14 +2102,22 @@ export class Matterbridge extends EventEmitter {
1489
2102
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1490
2103
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1491
2104
  });
2105
+ /**
2106
+ * This event is triggered when an operative new session was opened by a Controller.
2107
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2108
+ */
1492
2109
  serverNode.events.sessions.opened.on((session) => {
1493
2110
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1494
2111
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1495
2112
  });
2113
+ /**
2114
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2115
+ */
1496
2116
  serverNode.events.sessions.closed.on((session) => {
1497
2117
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1498
2118
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1499
2119
  });
2120
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1500
2121
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1501
2122
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1502
2123
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
@@ -1504,6 +2125,12 @@ export class Matterbridge extends EventEmitter {
1504
2125
  this.log.info(`Created server node for ${storeId}`);
1505
2126
  return serverNode;
1506
2127
  }
2128
+ /**
2129
+ * Gets the matter sanitized data of the specified server node.
2130
+ *
2131
+ * @param {ServerNode} [serverNode] - The server node to start.
2132
+ * @returns {ApiMatter} The sanitized data of the server node.
2133
+ */
1507
2134
  getServerNodeData(serverNode) {
1508
2135
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1509
2136
  return {
@@ -1520,12 +2147,25 @@ export class Matterbridge extends EventEmitter {
1520
2147
  serialNumber: serverNode.state.basicInformation.serialNumber,
1521
2148
  };
1522
2149
  }
2150
+ /**
2151
+ * Starts the specified server node.
2152
+ *
2153
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2154
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2155
+ */
1523
2156
  async startServerNode(matterServerNode) {
1524
2157
  if (!matterServerNode)
1525
2158
  return;
1526
2159
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1527
2160
  await matterServerNode.start();
1528
2161
  }
2162
+ /**
2163
+ * Stops the specified server node.
2164
+ *
2165
+ * @param {ServerNode} matterServerNode - The server node to stop.
2166
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2167
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2168
+ */
1529
2169
  async stopServerNode(matterServerNode, timeout = 30000) {
1530
2170
  const { withTimeout } = await import('./utils/wait.js');
1531
2171
  if (!matterServerNode)
@@ -1539,12 +2179,25 @@ export class Matterbridge extends EventEmitter {
1539
2179
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1540
2180
  }
1541
2181
  }
2182
+ /**
2183
+ * Creates an aggregator node with the specified storage context.
2184
+ *
2185
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2186
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2187
+ */
1542
2188
  async createAggregatorNode(storageContext) {
1543
2189
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1544
2190
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1545
2191
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1546
2192
  return aggregatorNode;
1547
2193
  }
2194
+ /**
2195
+ * Creates and configures the server node for an accessory plugin for a given device.
2196
+ *
2197
+ * @param {Plugin} plugin - The plugin to configure.
2198
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2199
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2200
+ */
1548
2201
  async createAccessoryPlugin(plugin, device) {
1549
2202
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1550
2203
  plugin.locked = true;
@@ -1556,6 +2209,12 @@ export class Matterbridge extends EventEmitter {
1556
2209
  await plugin.serverNode.add(device);
1557
2210
  }
1558
2211
  }
2212
+ /**
2213
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
2214
+ *
2215
+ * @param {Plugin} plugin - The plugin to configure.
2216
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
2217
+ */
1559
2218
  async createDynamicPlugin(plugin) {
1560
2219
  if (!plugin.locked) {
1561
2220
  plugin.locked = true;
@@ -1568,6 +2227,13 @@ export class Matterbridge extends EventEmitter {
1568
2227
  await plugin.serverNode.add(plugin.aggregatorNode);
1569
2228
  }
1570
2229
  }
2230
+ /**
2231
+ * Creates and configures the server node for a single not bridged device.
2232
+ *
2233
+ * @param {Plugin} plugin - The plugin to configure.
2234
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2235
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2236
+ */
1571
2237
  async createDeviceServerNode(plugin, device) {
1572
2238
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1573
2239
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1578,8 +2244,16 @@ export class Matterbridge extends EventEmitter {
1578
2244
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1579
2245
  }
1580
2246
  }
2247
+ /**
2248
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2249
+ *
2250
+ * @param {string} pluginName - The name of the plugin.
2251
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2252
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2253
+ */
1581
2254
  async addBridgedEndpoint(pluginName, device) {
1582
2255
  const { waiter } = await import('./utils/wait.js');
2256
+ // Check if the plugin is registered
1583
2257
  const plugin = this.plugins.get(pluginName);
1584
2258
  if (!plugin) {
1585
2259
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1599,6 +2273,7 @@ export class Matterbridge extends EventEmitter {
1599
2273
  }
1600
2274
  else if (this.bridgeMode === 'bridge') {
1601
2275
  if (device.mode === 'matter') {
2276
+ // Register and add the device to the matterbridge server node
1602
2277
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1603
2278
  if (!this.serverNode) {
1604
2279
  this.log.error('Server node not found for Matterbridge');
@@ -1615,6 +2290,7 @@ export class Matterbridge extends EventEmitter {
1615
2290
  }
1616
2291
  }
1617
2292
  else {
2293
+ // Register and add the device to the matterbridge aggregator node
1618
2294
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1619
2295
  if (!this.aggregatorNode) {
1620
2296
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1632,6 +2308,7 @@ export class Matterbridge extends EventEmitter {
1632
2308
  }
1633
2309
  }
1634
2310
  else if (this.bridgeMode === 'childbridge') {
2311
+ // Register and add the device to the plugin server node
1635
2312
  if (plugin.type === 'AccessoryPlatform') {
1636
2313
  try {
1637
2314
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1655,10 +2332,12 @@ export class Matterbridge extends EventEmitter {
1655
2332
  return;
1656
2333
  }
1657
2334
  }
2335
+ // Register and add the device to the plugin aggregator node
1658
2336
  if (plugin.type === 'DynamicPlatform') {
1659
2337
  try {
1660
2338
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1661
2339
  await this.createDynamicPlugin(plugin);
2340
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1662
2341
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1663
2342
  if (!plugin.aggregatorNode) {
1664
2343
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1679,17 +2358,28 @@ export class Matterbridge extends EventEmitter {
1679
2358
  }
1680
2359
  if (plugin.registeredDevices !== undefined)
1681
2360
  plugin.registeredDevices++;
2361
+ // Add the device to the DeviceManager
1682
2362
  this.devices.set(device);
2363
+ // Subscribe to the attributes changed event
1683
2364
  await this.subscribeAttributeChanged(plugin, device);
1684
2365
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1685
2366
  }
2367
+ /**
2368
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2369
+ *
2370
+ * @param {string} pluginName - The name of the plugin.
2371
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2372
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2373
+ */
1686
2374
  async removeBridgedEndpoint(pluginName, device) {
1687
2375
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2376
+ // Check if the plugin is registered
1688
2377
  const plugin = this.plugins.get(pluginName);
1689
2378
  if (!plugin) {
1690
2379
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1691
2380
  return;
1692
2381
  }
2382
+ // Unregister and remove the device from the matterbridge aggregator node
1693
2383
  if (this.bridgeMode === 'bridge') {
1694
2384
  if (!this.aggregatorNode) {
1695
2385
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1702,8 +2392,10 @@ export class Matterbridge extends EventEmitter {
1702
2392
  }
1703
2393
  else if (this.bridgeMode === 'childbridge') {
1704
2394
  if (plugin.type === 'AccessoryPlatform') {
2395
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1705
2396
  }
1706
2397
  else if (plugin.type === 'DynamicPlatform') {
2398
+ // Unregister and remove the device from the plugin aggregator node
1707
2399
  if (!plugin.aggregatorNode) {
1708
2400
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
1709
2401
  return;
@@ -1714,8 +2406,21 @@ export class Matterbridge extends EventEmitter {
1714
2406
  if (plugin.registeredDevices !== undefined)
1715
2407
  plugin.registeredDevices--;
1716
2408
  }
2409
+ // Remove the device from the DeviceManager
1717
2410
  this.devices.remove(device);
1718
2411
  }
2412
+ /**
2413
+ * Removes all bridged endpoints from the specified plugin.
2414
+ *
2415
+ * @param {string} pluginName - The name of the plugin.
2416
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2417
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2418
+ *
2419
+ * @remarks
2420
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2421
+ * It also applies a delay between each removal if specified.
2422
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2423
+ */
1719
2424
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1720
2425
  const { wait } = await import('./utils/wait.js');
1721
2426
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
@@ -1727,8 +2432,28 @@ export class Matterbridge extends EventEmitter {
1727
2432
  if (delay > 0)
1728
2433
  await wait(2000);
1729
2434
  }
2435
+ /**
2436
+ * Registers a virtual device.
2437
+ * Virtual devices are only supported in bridge mode and childbridge mode with a DynamicPlatform.
2438
+ *
2439
+ * The virtual device is created as an instance of `Endpoint` with the provided device type.
2440
+ * When the virtual device is turned on, the provided callback function is executed.
2441
+ * The onOff state of the virtual device always reverts to false when the device is turned on.
2442
+ *
2443
+ * @param { string } pluginName - The name of the plugin to register the virtual device under.
2444
+ * @param { string } name - The name of the virtual device.
2445
+ * @param { 'light' | 'outlet' | 'switch' | 'mounted_switch' } type - The type of the virtual device.
2446
+ * @param { () => Promise<void> } callback - The callback to call when the virtual device is turned on.
2447
+ *
2448
+ * @returns {Promise<boolean>} A promise that resolves to true if the virtual device was successfully registered, false otherwise.
2449
+ *
2450
+ * @remarks
2451
+ * The virtual devices don't show up in the device list of the frontend.
2452
+ * Type 'switch' is not supported by Alexa and 'mounted_switch' is not supported by Apple Home.
2453
+ */
1730
2454
  async addVirtualEndpoint(pluginName, name, type, callback) {
1731
2455
  this.log.debug(`Adding virtual endpoint ${plg}${pluginName}${db}:${dev}${name}${db}...`);
2456
+ // Check if the plugin is registered
1732
2457
  const plugin = this.plugins.get(pluginName);
1733
2458
  if (!plugin) {
1734
2459
  this.log.error(`Error adding virtual endpoint ${dev}${name}${er} for plugin ${plg}${pluginName}${er}: plugin not found`);
@@ -1755,13 +2480,24 @@ export class Matterbridge extends EventEmitter {
1755
2480
  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.`);
1756
2481
  return false;
1757
2482
  }
2483
+ /**
2484
+ * Subscribes to the attribute change event for the given device and plugin.
2485
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2486
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2487
+ *
2488
+ * @param {Plugin} plugin - The plugin associated with the device.
2489
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2490
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2491
+ */
1758
2492
  async subscribeAttributeChanged(plugin, device) {
1759
2493
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
1760
2494
  return;
1761
2495
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
2496
+ // Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
1762
2497
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1763
2498
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1764
2499
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
2500
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1765
2501
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
1766
2502
  });
1767
2503
  }
@@ -1811,6 +2547,7 @@ export class Matterbridge extends EventEmitter {
1811
2547
  this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1812
2548
  await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1813
2549
  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}`);
2550
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1814
2551
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
1815
2552
  });
1816
2553
  }
@@ -1819,12 +2556,19 @@ export class Matterbridge extends EventEmitter {
1819
2556
  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...`);
1820
2557
  await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1821
2558
  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}`);
2559
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1822
2560
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
1823
2561
  });
1824
2562
  }
1825
2563
  }
1826
2564
  }
1827
2565
  }
2566
+ /**
2567
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2568
+ *
2569
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2570
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2571
+ */
1828
2572
  sanitizeFabricInformations(fabricInfo) {
1829
2573
  return fabricInfo.map((info) => {
1830
2574
  return {
@@ -1838,6 +2582,12 @@ export class Matterbridge extends EventEmitter {
1838
2582
  };
1839
2583
  });
1840
2584
  }
2585
+ /**
2586
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2587
+ *
2588
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2589
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2590
+ */
1841
2591
  sanitizeSessionInformation(sessions) {
1842
2592
  return sessions
1843
2593
  .filter((session) => session.isPeerActive)
@@ -1864,7 +2614,21 @@ export class Matterbridge extends EventEmitter {
1864
2614
  };
1865
2615
  });
1866
2616
  }
2617
+ /**
2618
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2619
+ *
2620
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2621
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2622
+ */
2623
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1867
2624
  async setAggregatorReachability(aggregatorNode, reachable) {
2625
+ /*
2626
+ for (const child of aggregatorNode.parts) {
2627
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2628
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2629
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2630
+ }
2631
+ */
1868
2632
  }
1869
2633
  getVendorIdName = (vendorId) => {
1870
2634
  if (!vendorId)
@@ -1904,10 +2668,11 @@ export class Matterbridge extends EventEmitter {
1904
2668
  case 0x1488:
1905
2669
  vendorName = '(ShortcutLabsFlic)';
1906
2670
  break;
1907
- case 65521:
2671
+ case 65521: // 0xFFF1
1908
2672
  vendorName = '(MatterTest)';
1909
2673
  break;
1910
2674
  }
1911
2675
  return vendorName;
1912
2676
  };
1913
2677
  }
2678
+ //# sourceMappingURL=matterbridge.js.map