matterbridge 3.4.4-dev-20251219-9c117d4 → 3.4.4

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