matterbridge 3.4.0-dev-20251126-5087664 → 3.4.0

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 (314) hide show
  1. package/dist/broadcastServer.d.ts +115 -0
  2. package/dist/broadcastServer.d.ts.map +1 -0
  3. package/dist/broadcastServer.js +93 -1
  4. package/dist/broadcastServer.js.map +1 -0
  5. package/dist/broadcastServerTypes.d.ts +838 -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/defaultConfigSchema.d.ts +28 -0
  26. package/dist/defaultConfigSchema.d.ts.map +1 -0
  27. package/dist/defaultConfigSchema.js +24 -0
  28. package/dist/defaultConfigSchema.js.map +1 -0
  29. package/dist/deviceManager.d.ts +135 -0
  30. package/dist/deviceManager.d.ts.map +1 -0
  31. package/dist/deviceManager.js +113 -1
  32. package/dist/deviceManager.js.map +1 -0
  33. package/dist/devices/airConditioner.d.ts +98 -0
  34. package/dist/devices/airConditioner.d.ts.map +1 -0
  35. package/dist/devices/airConditioner.js +57 -0
  36. package/dist/devices/airConditioner.js.map +1 -0
  37. package/dist/devices/batteryStorage.d.ts +48 -0
  38. package/dist/devices/batteryStorage.d.ts.map +1 -0
  39. package/dist/devices/batteryStorage.js +48 -1
  40. package/dist/devices/batteryStorage.js.map +1 -0
  41. package/dist/devices/cooktop.d.ts +61 -0
  42. package/dist/devices/cooktop.d.ts.map +1 -0
  43. package/dist/devices/cooktop.js +56 -0
  44. package/dist/devices/cooktop.js.map +1 -0
  45. package/dist/devices/dishwasher.d.ts +71 -0
  46. package/dist/devices/dishwasher.d.ts.map +1 -0
  47. package/dist/devices/dishwasher.js +57 -0
  48. package/dist/devices/dishwasher.js.map +1 -0
  49. package/dist/devices/evse.d.ts +76 -0
  50. package/dist/devices/evse.d.ts.map +1 -0
  51. package/dist/devices/evse.js +74 -10
  52. package/dist/devices/evse.js.map +1 -0
  53. package/dist/devices/export.d.ts +17 -0
  54. package/dist/devices/export.d.ts.map +1 -0
  55. package/dist/devices/export.js +5 -0
  56. package/dist/devices/export.js.map +1 -0
  57. package/dist/devices/extractorHood.d.ts +46 -0
  58. package/dist/devices/extractorHood.d.ts.map +1 -0
  59. package/dist/devices/extractorHood.js +43 -0
  60. package/dist/devices/extractorHood.js.map +1 -0
  61. package/dist/devices/heatPump.d.ts +47 -0
  62. package/dist/devices/heatPump.d.ts.map +1 -0
  63. package/dist/devices/heatPump.js +50 -2
  64. package/dist/devices/heatPump.js.map +1 -0
  65. package/dist/devices/laundryDryer.d.ts +67 -0
  66. package/dist/devices/laundryDryer.d.ts.map +1 -0
  67. package/dist/devices/laundryDryer.js +62 -3
  68. package/dist/devices/laundryDryer.js.map +1 -0
  69. package/dist/devices/laundryWasher.d.ts +81 -0
  70. package/dist/devices/laundryWasher.d.ts.map +1 -0
  71. package/dist/devices/laundryWasher.js +70 -4
  72. package/dist/devices/laundryWasher.js.map +1 -0
  73. package/dist/devices/microwaveOven.d.ts +168 -0
  74. package/dist/devices/microwaveOven.d.ts.map +1 -0
  75. package/dist/devices/microwaveOven.js +88 -5
  76. package/dist/devices/microwaveOven.js.map +1 -0
  77. package/dist/devices/oven.d.ts +105 -0
  78. package/dist/devices/oven.d.ts.map +1 -0
  79. package/dist/devices/oven.js +85 -0
  80. package/dist/devices/oven.js.map +1 -0
  81. package/dist/devices/refrigerator.d.ts +118 -0
  82. package/dist/devices/refrigerator.d.ts.map +1 -0
  83. package/dist/devices/refrigerator.js +102 -0
  84. package/dist/devices/refrigerator.js.map +1 -0
  85. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  86. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  87. package/dist/devices/roboticVacuumCleaner.js +100 -9
  88. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  89. package/dist/devices/solarPower.d.ts +40 -0
  90. package/dist/devices/solarPower.d.ts.map +1 -0
  91. package/dist/devices/solarPower.js +38 -0
  92. package/dist/devices/solarPower.js.map +1 -0
  93. package/dist/devices/speaker.d.ts +87 -0
  94. package/dist/devices/speaker.d.ts.map +1 -0
  95. package/dist/devices/speaker.js +84 -0
  96. package/dist/devices/speaker.js.map +1 -0
  97. package/dist/devices/temperatureControl.d.ts +166 -0
  98. package/dist/devices/temperatureControl.d.ts.map +1 -0
  99. package/dist/devices/temperatureControl.js +24 -3
  100. package/dist/devices/temperatureControl.js.map +1 -0
  101. package/dist/devices/waterHeater.d.ts +111 -0
  102. package/dist/devices/waterHeater.d.ts.map +1 -0
  103. package/dist/devices/waterHeater.js +82 -2
  104. package/dist/devices/waterHeater.js.map +1 -0
  105. package/dist/dgram/coap.d.ts +205 -0
  106. package/dist/dgram/coap.d.ts.map +1 -0
  107. package/dist/dgram/coap.js +126 -13
  108. package/dist/dgram/coap.js.map +1 -0
  109. package/dist/dgram/dgram.d.ts +141 -0
  110. package/dist/dgram/dgram.d.ts.map +1 -0
  111. package/dist/dgram/dgram.js +114 -2
  112. package/dist/dgram/dgram.js.map +1 -0
  113. package/dist/dgram/mb_coap.d.ts +24 -0
  114. package/dist/dgram/mb_coap.d.ts.map +1 -0
  115. package/dist/dgram/mb_coap.js +41 -3
  116. package/dist/dgram/mb_coap.js.map +1 -0
  117. package/dist/dgram/mb_mdns.d.ts +24 -0
  118. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  119. package/dist/dgram/mb_mdns.js +80 -15
  120. package/dist/dgram/mb_mdns.js.map +1 -0
  121. package/dist/dgram/mdns.d.ts +290 -0
  122. package/dist/dgram/mdns.d.ts.map +1 -0
  123. package/dist/dgram/mdns.js +299 -137
  124. package/dist/dgram/mdns.js.map +1 -0
  125. package/dist/dgram/multicast.d.ts +67 -0
  126. package/dist/dgram/multicast.d.ts.map +1 -0
  127. package/dist/dgram/multicast.js +62 -1
  128. package/dist/dgram/multicast.js.map +1 -0
  129. package/dist/dgram/unicast.d.ts +56 -0
  130. package/dist/dgram/unicast.d.ts.map +1 -0
  131. package/dist/dgram/unicast.js +54 -0
  132. package/dist/dgram/unicast.js.map +1 -0
  133. package/dist/frontend.d.ts +238 -0
  134. package/dist/frontend.d.ts.map +1 -0
  135. package/dist/frontend.js +455 -35
  136. package/dist/frontend.js.map +1 -0
  137. package/dist/frontendTypes.d.ts +529 -0
  138. package/dist/frontendTypes.d.ts.map +1 -0
  139. package/dist/frontendTypes.js +45 -0
  140. package/dist/frontendTypes.js.map +1 -0
  141. package/dist/helpers.d.ts +48 -0
  142. package/dist/helpers.d.ts.map +1 -0
  143. package/dist/helpers.js +53 -0
  144. package/dist/helpers.js.map +1 -0
  145. package/dist/index.d.ts +34 -0
  146. package/dist/index.d.ts.map +1 -0
  147. package/dist/index.js +25 -0
  148. package/dist/index.js.map +1 -0
  149. package/dist/jestutils/export.d.ts +2 -0
  150. package/dist/jestutils/export.d.ts.map +1 -0
  151. package/dist/jestutils/export.js +1 -0
  152. package/dist/jestutils/export.js.map +1 -0
  153. package/dist/jestutils/jestHelpers.d.ts +303 -0
  154. package/dist/jestutils/jestHelpers.d.ts.map +1 -0
  155. package/dist/jestutils/jestHelpers.js +352 -13
  156. package/dist/jestutils/jestHelpers.js.map +1 -0
  157. package/dist/logger/export.d.ts +2 -0
  158. package/dist/logger/export.d.ts.map +1 -0
  159. package/dist/logger/export.js +1 -0
  160. package/dist/logger/export.js.map +1 -0
  161. package/dist/matter/behaviors.d.ts +2 -0
  162. package/dist/matter/behaviors.d.ts.map +1 -0
  163. package/dist/matter/behaviors.js +2 -0
  164. package/dist/matter/behaviors.js.map +1 -0
  165. package/dist/matter/clusters.d.ts +2 -0
  166. package/dist/matter/clusters.d.ts.map +1 -0
  167. package/dist/matter/clusters.js +2 -0
  168. package/dist/matter/clusters.js.map +1 -0
  169. package/dist/matter/devices.d.ts +2 -0
  170. package/dist/matter/devices.d.ts.map +1 -0
  171. package/dist/matter/devices.js +2 -0
  172. package/dist/matter/devices.js.map +1 -0
  173. package/dist/matter/endpoints.d.ts +2 -0
  174. package/dist/matter/endpoints.d.ts.map +1 -0
  175. package/dist/matter/endpoints.js +2 -0
  176. package/dist/matter/endpoints.js.map +1 -0
  177. package/dist/matter/export.d.ts +5 -0
  178. package/dist/matter/export.d.ts.map +1 -0
  179. package/dist/matter/export.js +3 -0
  180. package/dist/matter/export.js.map +1 -0
  181. package/dist/matter/types.d.ts +3 -0
  182. package/dist/matter/types.d.ts.map +1 -0
  183. package/dist/matter/types.js +3 -0
  184. package/dist/matter/types.js.map +1 -0
  185. package/dist/matterNode.d.ts +342 -0
  186. package/dist/matterNode.d.ts.map +1 -0
  187. package/dist/matterNode.js +369 -8
  188. package/dist/matterNode.js.map +1 -0
  189. package/dist/matterbridge.d.ts +473 -0
  190. package/dist/matterbridge.d.ts.map +1 -0
  191. package/dist/matterbridge.js +787 -46
  192. package/dist/matterbridge.js.map +1 -0
  193. package/dist/matterbridgeAccessoryPlatform.d.ts +41 -0
  194. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  195. package/dist/matterbridgeAccessoryPlatform.js +38 -0
  196. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  197. package/dist/matterbridgeBehaviors.d.ts +2404 -0
  198. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  199. package/dist/matterbridgeBehaviors.js +68 -5
  200. package/dist/matterbridgeBehaviors.js.map +1 -0
  201. package/dist/matterbridgeDeviceTypes.d.ts +698 -0
  202. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  203. package/dist/matterbridgeDeviceTypes.js +635 -14
  204. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  205. package/dist/matterbridgeDynamicPlatform.d.ts +41 -0
  206. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  207. package/dist/matterbridgeDynamicPlatform.js +38 -0
  208. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  209. package/dist/matterbridgeEndpoint.d.ts +1507 -0
  210. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  211. package/dist/matterbridgeEndpoint.js +1444 -53
  212. package/dist/matterbridgeEndpoint.js.map +1 -0
  213. package/dist/matterbridgeEndpointHelpers.d.ts +787 -0
  214. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  215. package/dist/matterbridgeEndpointHelpers.js +483 -20
  216. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  217. package/dist/matterbridgeEndpointTypes.d.ts +166 -0
  218. package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
  219. package/dist/matterbridgeEndpointTypes.js +25 -0
  220. package/dist/matterbridgeEndpointTypes.js.map +1 -0
  221. package/dist/matterbridgePlatform.d.ts +524 -0
  222. package/dist/matterbridgePlatform.d.ts.map +1 -0
  223. package/dist/matterbridgePlatform.js +439 -1
  224. package/dist/matterbridgePlatform.js.map +1 -0
  225. package/dist/matterbridgeTypes.d.ts +251 -0
  226. package/dist/matterbridgeTypes.d.ts.map +1 -0
  227. package/dist/matterbridgeTypes.js +26 -0
  228. package/dist/matterbridgeTypes.js.map +1 -0
  229. package/dist/pluginManager.d.ts +371 -0
  230. package/dist/pluginManager.d.ts.map +1 -0
  231. package/dist/pluginManager.js +340 -5
  232. package/dist/pluginManager.js.map +1 -0
  233. package/dist/shelly.d.ts +174 -0
  234. package/dist/shelly.d.ts.map +1 -0
  235. package/dist/shelly.js +168 -7
  236. package/dist/shelly.js.map +1 -0
  237. package/dist/storage/export.d.ts +2 -0
  238. package/dist/storage/export.d.ts.map +1 -0
  239. package/dist/storage/export.js +1 -0
  240. package/dist/storage/export.js.map +1 -0
  241. package/dist/update.d.ts +75 -0
  242. package/dist/update.d.ts.map +1 -0
  243. package/dist/update.js +69 -0
  244. package/dist/update.js.map +1 -0
  245. package/dist/utils/colorUtils.d.ts +101 -0
  246. package/dist/utils/colorUtils.d.ts.map +1 -0
  247. package/dist/utils/colorUtils.js +97 -2
  248. package/dist/utils/colorUtils.js.map +1 -0
  249. package/dist/utils/commandLine.d.ts +66 -0
  250. package/dist/utils/commandLine.d.ts.map +1 -0
  251. package/dist/utils/commandLine.js +60 -0
  252. package/dist/utils/commandLine.js.map +1 -0
  253. package/dist/utils/copyDirectory.d.ts +35 -0
  254. package/dist/utils/copyDirectory.d.ts.map +1 -0
  255. package/dist/utils/copyDirectory.js +37 -0
  256. package/dist/utils/copyDirectory.js.map +1 -0
  257. package/dist/utils/createDirectory.d.ts +34 -0
  258. package/dist/utils/createDirectory.d.ts.map +1 -0
  259. package/dist/utils/createDirectory.js +33 -0
  260. package/dist/utils/createDirectory.js.map +1 -0
  261. package/dist/utils/createZip.d.ts +39 -0
  262. package/dist/utils/createZip.d.ts.map +1 -0
  263. package/dist/utils/createZip.js +47 -2
  264. package/dist/utils/createZip.js.map +1 -0
  265. package/dist/utils/deepCopy.d.ts +32 -0
  266. package/dist/utils/deepCopy.d.ts.map +1 -0
  267. package/dist/utils/deepCopy.js +39 -0
  268. package/dist/utils/deepCopy.js.map +1 -0
  269. package/dist/utils/deepEqual.d.ts +54 -0
  270. package/dist/utils/deepEqual.d.ts.map +1 -0
  271. package/dist/utils/deepEqual.js +72 -1
  272. package/dist/utils/deepEqual.js.map +1 -0
  273. package/dist/utils/error.d.ts +44 -0
  274. package/dist/utils/error.d.ts.map +1 -0
  275. package/dist/utils/error.js +41 -0
  276. package/dist/utils/error.js.map +1 -0
  277. package/dist/utils/export.d.ts +13 -0
  278. package/dist/utils/export.d.ts.map +1 -0
  279. package/dist/utils/export.js +1 -0
  280. package/dist/utils/export.js.map +1 -0
  281. package/dist/utils/format.d.ts +53 -0
  282. package/dist/utils/format.d.ts.map +1 -0
  283. package/dist/utils/format.js +49 -0
  284. package/dist/utils/format.js.map +1 -0
  285. package/dist/utils/hex.d.ts +89 -0
  286. package/dist/utils/hex.d.ts.map +1 -0
  287. package/dist/utils/hex.js +124 -0
  288. package/dist/utils/hex.js.map +1 -0
  289. package/dist/utils/inspector.d.ts +87 -0
  290. package/dist/utils/inspector.d.ts.map +1 -0
  291. package/dist/utils/inspector.js +69 -1
  292. package/dist/utils/inspector.js.map +1 -0
  293. package/dist/utils/isvalid.d.ts +103 -0
  294. package/dist/utils/isvalid.d.ts.map +1 -0
  295. package/dist/utils/isvalid.js +101 -0
  296. package/dist/utils/isvalid.js.map +1 -0
  297. package/dist/utils/network.d.ts +111 -0
  298. package/dist/utils/network.d.ts.map +1 -0
  299. package/dist/utils/network.js +96 -5
  300. package/dist/utils/network.js.map +1 -0
  301. package/dist/utils/spawn.d.ts +33 -0
  302. package/dist/utils/spawn.d.ts.map +1 -0
  303. package/dist/utils/spawn.js +71 -1
  304. package/dist/utils/spawn.js.map +1 -0
  305. package/dist/utils/tracker.d.ts +108 -0
  306. package/dist/utils/tracker.d.ts.map +1 -0
  307. package/dist/utils/tracker.js +64 -1
  308. package/dist/utils/tracker.js.map +1 -0
  309. package/dist/utils/wait.d.ts +54 -0
  310. package/dist/utils/wait.d.ts.map +1 -0
  311. package/dist/utils/wait.js +60 -8
  312. package/dist/utils/wait.js.map +1 -0
  313. package/npm-shrinkwrap.json +2 -2
  314. 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.0
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2023, 2024, 2025 Luca Liguori.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ // 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';
@@ -28,19 +57,27 @@ import { bridge } from './matterbridgeDeviceTypes.js';
28
57
  import { Frontend } from './frontend.js';
29
58
  import { addVirtualDevices } from './helpers.js';
30
59
  import { BroadcastServer } from './broadcastServer.js';
60
+ /**
61
+ * Represents the Matterbridge application.
62
+ */
31
63
  export class Matterbridge extends EventEmitter {
64
+ /** Matterbridge system information */
32
65
  systemInformation = {
66
+ // Network properties
33
67
  interfaceName: '',
34
68
  macAddress: '',
35
69
  ipv4Address: '',
36
70
  ipv6Address: '',
71
+ // Node.js properties
37
72
  nodeVersion: '',
73
+ // Fixed system properties
38
74
  hostname: '',
39
75
  user: '',
40
76
  osType: '',
41
77
  osRelease: '',
42
78
  osPlatform: '',
43
79
  osArch: '',
80
+ // Cpu and memory properties
44
81
  totalMemory: '',
45
82
  freeMemory: '',
46
83
  systemUptime: '',
@@ -51,6 +88,7 @@ export class Matterbridge extends EventEmitter {
51
88
  heapTotal: '',
52
89
  heapUsed: '',
53
90
  };
91
+ // Matterbridge settings
54
92
  homeDirectory = '';
55
93
  rootDirectory = '';
56
94
  matterbridgeDirectory = '';
@@ -65,12 +103,19 @@ export class Matterbridge extends EventEmitter {
65
103
  restartMode = '';
66
104
  virtualMode = 'outlet';
67
105
  profile = getParameter('profile');
68
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
106
+ /** Matterbridge logger */
107
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
108
+ /** Matterbridge logger level */
69
109
  logLevel = this.log.logLevel;
110
+ /** Whether to log to a file */
70
111
  fileLogger = false;
71
- matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
112
+ /** Matter logger */
113
+ matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
114
+ /** Matter logger level */
72
115
  matterLogLevel = this.matterLog.logLevel;
116
+ /** Whether to log Matter to a file */
73
117
  matterFileLogger = false;
118
+ // Frontend settings
74
119
  readOnly = hasParameter('readonly') || hasParameter('shelly');
75
120
  shellyBoard = hasParameter('shelly');
76
121
  shellySysUpdate = false;
@@ -78,12 +123,18 @@ export class Matterbridge extends EventEmitter {
78
123
  restartRequired = false;
79
124
  fixedRestartRequired = false;
80
125
  updateRequired = false;
126
+ // Managers
81
127
  plugins = new PluginManager(this);
82
128
  devices = new DeviceManager();
129
+ // Frontend
83
130
  frontend = new Frontend(this);
131
+ /** Matterbridge node storage manager created in the directory 'storage' in matterbridgeDirectory */
84
132
  nodeStorage;
133
+ /** Matterbridge node context created with name 'matterbridge' */
85
134
  nodeContext;
135
+ /** The main instance of the Matterbridge class (singleton) */
86
136
  static instance;
137
+ // Instance properties
87
138
  shutdown = false;
88
139
  failCountLimit = hasParameter('shelly') ? 600 : 120;
89
140
  hasCleanupStarted = false;
@@ -98,19 +149,32 @@ export class Matterbridge extends EventEmitter {
98
149
  sigtermHandler;
99
150
  exceptionHandler;
100
151
  rejectionHandler;
152
+ /** Matter environment default */
101
153
  environment = Environment.default;
154
+ /** Matter storage service from environment default */
102
155
  matterStorageService;
156
+ /** Matter storage manager created with name 'Matterbridge' */
103
157
  matterStorageManager;
158
+ /** Matter matterbridge storage context created in the storage manager with name 'persist' */
104
159
  matterbridgeContext;
105
160
  controllerContext;
161
+ /** Matter mdns interface e.g. 'eth0' or 'wlan0' or 'Wi-Fi' */
106
162
  mdnsInterface;
163
+ /** Matter listeningAddressIpv4 address */
107
164
  ipv4Address;
165
+ /** Matter listeningAddressIpv6 address */
108
166
  ipv6Address;
109
- port;
110
- passcode;
111
- discriminator;
112
- certification;
167
+ /** Matter commissioning port */
168
+ port; // first server node port
169
+ /** Matter commissioning passcode */
170
+ passcode; // first server node passcode
171
+ /** Matter commissioning discriminator */
172
+ discriminator; // first server node discriminator
173
+ /** Matter device certification */
174
+ certification; // device certification
175
+ /** Matter server node in bridge mode */
113
176
  serverNode;
177
+ /** Matter aggregator node in bridge mode */
114
178
  aggregatorNode;
115
179
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
116
180
  aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
@@ -119,10 +183,13 @@ export class Matterbridge extends EventEmitter {
119
183
  aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
120
184
  aggregatorSerialNumber = getParameter('serialNumber');
121
185
  aggregatorUniqueId = getParameter('uniqueId');
186
+ /** Advertising nodes map: time advertising started keyed by storeId */
122
187
  advertisingNodes = new Map();
188
+ /** Broadcast server */
123
189
  server;
124
190
  debug = hasParameter('debug') || hasParameter('verbose');
125
191
  verbose = hasParameter('verbose');
192
+ /** We load asyncronously so is private */
126
193
  constructor() {
127
194
  super();
128
195
  this.log.logNameColor = '\x1b[38;5;115m';
@@ -162,8 +229,19 @@ export class Matterbridge extends EventEmitter {
162
229
  }
163
230
  }
164
231
  }
232
+ //* ************************************************************************************************************************************ */
233
+ // loadInstance() and cleanup() methods */
234
+ //* ************************************************************************************************************************************ */
235
+ /**
236
+ * Loads an instance of the Matterbridge class.
237
+ * If an instance already exists, return that instance.
238
+ *
239
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
240
+ * @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
241
+ */
165
242
  static async loadInstance(initialize = false) {
166
243
  if (!Matterbridge.instance) {
244
+ // eslint-disable-next-line no-console
167
245
  if (hasParameter('debug'))
168
246
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
169
247
  Matterbridge.instance = new Matterbridge();
@@ -172,56 +250,84 @@ export class Matterbridge extends EventEmitter {
172
250
  }
173
251
  return Matterbridge.instance;
174
252
  }
253
+ /**
254
+ * Initializes the Matterbridge application.
255
+ *
256
+ * @remarks
257
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
258
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
259
+ * node version, registers signal handlers, initializes storage, and parses the command line.
260
+ *
261
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
262
+ */
175
263
  async initialize() {
264
+ // for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
265
+ // Emit the initialize_started event
176
266
  this.emit('initialize_started');
267
+ // Set the restart mode
177
268
  if (hasParameter('service'))
178
269
  this.restartMode = 'service';
179
270
  if (hasParameter('docker'))
180
271
  this.restartMode = 'docker';
272
+ // Set the matterbridge home directory
181
273
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
182
274
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
275
+ // Set the matterbridge directory
183
276
  this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
184
277
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
185
278
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
186
279
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
280
+ // Set the matterbridge plugin directory
187
281
  this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
188
282
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
283
+ // Set the matterbridge cert directory
189
284
  this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
190
285
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
286
+ // Set the matterbridge root directory
191
287
  const { fileURLToPath } = await import('node:url');
192
288
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
193
- this.rootDirectory = path.resolve(currentFileDirectory, '../');
289
+ this.rootDirectory = path.resolve(currentFileDirectory, '../'); // Adjust the path for dist directory
290
+ // Setup the matter environment with default values
194
291
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
195
292
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
196
293
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
197
294
  this.environment.vars.set('runtime.signals', false);
198
295
  this.environment.vars.set('runtime.exitcode', false);
296
+ // Register process handlers
199
297
  this.registerProcessHandlers();
298
+ // Initialize nodeStorage and nodeContext
200
299
  try {
201
300
  this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
202
301
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
203
302
  this.log.debug('Creating node storage context for matterbridge');
204
303
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
304
+ // TODO: Remove this code when node-persist-manager is updated
305
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
205
306
  const keys = (await this.nodeStorage?.storage.keys());
206
307
  for (const key of keys) {
207
308
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
309
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
208
310
  await this.nodeStorage?.storage.get(key);
209
311
  }
210
312
  const storages = await this.nodeStorage.getStorageNames();
211
313
  for (const storage of storages) {
212
314
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
213
315
  const nodeContext = await this.nodeStorage?.createStorage(storage);
316
+ // TODO: Remove this code when node-persist-manager is updated
317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
214
318
  const keys = (await nodeContext?.storage.keys());
215
319
  keys.forEach(async (key) => {
216
320
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
217
321
  await nodeContext?.get(key);
218
322
  });
219
323
  }
324
+ // Creating a backup of the node storage since it is not corrupted
220
325
  this.log.debug('Creating node storage backup...');
221
326
  await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
222
327
  this.log.debug('Created node storage backup');
223
328
  }
224
329
  catch (error) {
330
+ // Restoring the backup of the node storage since it is corrupted
225
331
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
226
332
  if (hasParameter('norestore')) {
227
333
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -235,14 +341,19 @@ export class Matterbridge extends EventEmitter {
235
341
  if (!this.nodeStorage || !this.nodeContext) {
236
342
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
237
343
  }
344
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
238
345
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
346
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
239
347
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
348
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
240
349
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
350
+ // Certificate management
241
351
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
242
352
  try {
243
353
  await fs.promises.access(pairingFilePath, fs.constants.R_OK);
244
354
  const pairingFileContent = await fs.promises.readFile(pairingFilePath, 'utf8');
245
355
  const pairingFileJson = JSON.parse(pairingFileContent);
356
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
246
357
  if (isValidNumber(pairingFileJson.vendorId)) {
247
358
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
248
359
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -271,11 +382,13 @@ export class Matterbridge extends EventEmitter {
271
382
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
272
383
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
273
384
  }
385
+ // Override the passcode and discriminator if they are present in the pairing file
274
386
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
275
387
  this.passcode = pairingFileJson.passcode;
276
388
  this.discriminator = pairingFileJson.discriminator;
277
389
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
278
390
  }
391
+ // Set the certification for matter.js if it is present in the pairing file
279
392
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
280
393
  const { hexToBuffer } = await import('./utils/hex.js');
281
394
  this.certification = {
@@ -290,41 +403,44 @@ export class Matterbridge extends EventEmitter {
290
403
  catch (error) {
291
404
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
292
405
  }
406
+ // Store the passcode, discriminator and port in the node context
293
407
  await this.nodeContext.set('matterport', this.port);
294
408
  await this.nodeContext.set('matterpasscode', this.passcode);
295
409
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
296
410
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
411
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
297
412
  if (hasParameter('logger')) {
298
413
  const level = getParameter('logger');
299
414
  if (level === 'debug') {
300
- this.log.logLevel = "debug";
415
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
301
416
  }
302
417
  else if (level === 'info') {
303
- this.log.logLevel = "info";
418
+ this.log.logLevel = "info" /* LogLevel.INFO */;
304
419
  }
305
420
  else if (level === 'notice') {
306
- this.log.logLevel = "notice";
421
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
307
422
  }
308
423
  else if (level === 'warn') {
309
- this.log.logLevel = "warn";
424
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
310
425
  }
311
426
  else if (level === 'error') {
312
- this.log.logLevel = "error";
427
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
313
428
  }
314
429
  else if (level === 'fatal') {
315
- this.log.logLevel = "fatal";
430
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
316
431
  }
317
432
  else {
318
433
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
319
- this.log.logLevel = "info";
434
+ this.log.logLevel = "info" /* LogLevel.INFO */;
320
435
  }
321
436
  }
322
437
  else {
323
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
438
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
324
439
  }
325
440
  this.logLevel = this.log.logLevel;
326
441
  this.frontend.logLevel = this.log.logLevel;
327
442
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
443
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
328
444
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
329
445
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
330
446
  this.fileLogger = true;
@@ -333,6 +449,7 @@ export class Matterbridge extends EventEmitter {
333
449
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
334
450
  if (this.profile !== undefined)
335
451
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
452
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
336
453
  if (hasParameter('matterlogger')) {
337
454
  const level = getParameter('matterlogger');
338
455
  if (level === 'debug') {
@@ -363,11 +480,13 @@ export class Matterbridge extends EventEmitter {
363
480
  }
364
481
  Logger.format = MatterLogFormat.ANSI;
365
482
  this.matterLogLevel = MatterLogLevel.names[Logger.level];
483
+ // Create the logger for matter.js with file logging (context: matterFileLog)
366
484
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
367
485
  this.matterFileLogger = true;
368
486
  }
369
487
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
370
488
  this.log.debug(`Matter logLevel: ${this.matterLogLevel} fileLoger: ${this.matterFileLogger}.`);
489
+ // Log network interfaces
371
490
  const networkInterfaces = os.networkInterfaces();
372
491
  const availableAddresses = Object.entries(networkInterfaces);
373
492
  const availableInterfaceNames = Object.keys(networkInterfaces);
@@ -380,6 +499,7 @@ export class Matterbridge extends EventEmitter {
380
499
  });
381
500
  }
382
501
  }
502
+ // Set the interface to use for matter server node mdnsInterface
383
503
  if (hasParameter('mdnsinterface')) {
384
504
  this.mdnsInterface = getParameter('mdnsinterface');
385
505
  }
@@ -388,6 +508,7 @@ export class Matterbridge extends EventEmitter {
388
508
  if (this.mdnsInterface === '')
389
509
  this.mdnsInterface = undefined;
390
510
  }
511
+ // Validate mdnsInterface
391
512
  if (this.mdnsInterface) {
392
513
  if (!availableInterfaceNames.includes(this.mdnsInterface)) {
393
514
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
@@ -400,6 +521,7 @@ export class Matterbridge extends EventEmitter {
400
521
  }
401
522
  if (this.mdnsInterface)
402
523
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
524
+ // Set the listeningAddressIpv4 for the matter commissioning server
403
525
  if (hasParameter('ipv4address')) {
404
526
  this.ipv4Address = getParameter('ipv4address');
405
527
  }
@@ -408,6 +530,7 @@ export class Matterbridge extends EventEmitter {
408
530
  if (this.ipv4Address === '')
409
531
  this.ipv4Address = undefined;
410
532
  }
533
+ // Validate ipv4address
411
534
  if (this.ipv4Address) {
412
535
  let isValid = false;
413
536
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -423,6 +546,7 @@ export class Matterbridge extends EventEmitter {
423
546
  await this.nodeContext.remove('matteripv4address');
424
547
  }
425
548
  }
549
+ // Set the listeningAddressIpv6 for the matter commissioning server
426
550
  if (hasParameter('ipv6address')) {
427
551
  this.ipv6Address = getParameter('ipv6address');
428
552
  }
@@ -431,6 +555,7 @@ export class Matterbridge extends EventEmitter {
431
555
  if (this.ipv6Address === '')
432
556
  this.ipv6Address = undefined;
433
557
  }
558
+ // Validate ipv6address
434
559
  if (this.ipv6Address) {
435
560
  let isValid = false;
436
561
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -439,6 +564,7 @@ export class Matterbridge extends EventEmitter {
439
564
  isValid = true;
440
565
  break;
441
566
  }
567
+ /* istanbul ignore next */
442
568
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
443
569
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
444
570
  isValid = true;
@@ -451,6 +577,7 @@ export class Matterbridge extends EventEmitter {
451
577
  await this.nodeContext.remove('matteripv6address');
452
578
  }
453
579
  }
580
+ // Initialize the virtual mode
454
581
  if (hasParameter('novirtual')) {
455
582
  this.virtualMode = 'disabled';
456
583
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -459,10 +586,15 @@ export class Matterbridge extends EventEmitter {
459
586
  this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
460
587
  }
461
588
  this.log.debug(`Virtual mode ${this.virtualMode}.`);
589
+ // Initialize PluginManager
462
590
  this.plugins.logLevel = this.log.logLevel;
463
591
  await this.plugins.loadFromStorage();
592
+ // Initialize DeviceManager
464
593
  this.devices.logLevel = this.log.logLevel;
594
+ // Get the plugins from node storage and create the plugins node storage contexts
465
595
  for (const plugin of this.plugins) {
596
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
597
+ // We don't do this when the add and other shutdown parameters are set because we shut down the process after adding the plugin
466
598
  if (!fs.existsSync(plugin.path) && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
467
599
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm...`);
468
600
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -492,6 +624,7 @@ export class Matterbridge extends EventEmitter {
492
624
  await plugin.nodeContext.set('description', plugin.description);
493
625
  await plugin.nodeContext.set('author', plugin.author);
494
626
  }
627
+ // Log system info and create .matterbridge directory
495
628
  await this.logNodeAndSystemInfo();
496
629
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
497
630
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -499,6 +632,7 @@ export class Matterbridge extends EventEmitter {
499
632
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
500
633
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
501
634
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
635
+ // Check node version and throw error
502
636
  const minNodeVersion = 20;
503
637
  const nodeVersion = process.versions.node;
504
638
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -506,10 +640,18 @@ export class Matterbridge extends EventEmitter {
506
640
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
507
641
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
508
642
  }
643
+ // Parse command line
509
644
  await this.parseCommandLine();
645
+ // Emit the initialize_completed event
510
646
  this.emit('initialize_completed');
511
647
  this.initialized = true;
512
648
  }
649
+ /**
650
+ * Parses the command line arguments and performs the corresponding actions.
651
+ *
652
+ * @private
653
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
654
+ */
513
655
  async parseCommandLine() {
514
656
  if (hasParameter('list')) {
515
657
  this.log.info(`│ Registered plugins (${this.plugins.length})`);
@@ -525,6 +667,19 @@ export class Matterbridge extends EventEmitter {
525
667
  }
526
668
  index++;
527
669
  }
670
+ /*
671
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
672
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
673
+ serializedRegisteredDevices?.forEach((device, index) => {
674
+ if (index !== serializedRegisteredDevices.length - 1) {
675
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
676
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
677
+ } else {
678
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
679
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
680
+ }
681
+ });
682
+ */
528
683
  this.shutdown = true;
529
684
  return;
530
685
  }
@@ -574,8 +729,10 @@ export class Matterbridge extends EventEmitter {
574
729
  this.shutdown = true;
575
730
  return;
576
731
  }
732
+ // Initialize frontend
577
733
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
578
734
  await this.frontend.start(getIntParameter('frontend'));
735
+ // Start the matter storage and create the matterbridge context
579
736
  try {
580
737
  await this.startMatterStorage();
581
738
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -589,18 +746,21 @@ export class Matterbridge extends EventEmitter {
589
746
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
590
747
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
591
748
  }
749
+ // Clear the matterbridge context if the reset parameter is set (bridge mode)
592
750
  if (hasParameter('reset') && getParameter('reset') === undefined) {
593
751
  this.initialized = true;
594
752
  await this.shutdownProcessAndReset();
595
753
  this.shutdown = true;
596
754
  return;
597
755
  }
756
+ // Clear matterbridge plugin context if the reset parameter is set (childbridge mode)
598
757
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
599
758
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
600
759
  const plugin = this.plugins.get(getParameter('reset'));
601
760
  if (plugin) {
602
761
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
603
762
  if (!matterStorageManager) {
763
+ /* istanbul ignore next */
604
764
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
605
765
  }
606
766
  else {
@@ -619,35 +779,42 @@ export class Matterbridge extends EventEmitter {
619
779
  this.shutdown = true;
620
780
  return;
621
781
  }
782
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
622
783
  clearTimeout(this.checkUpdateTimeout);
623
784
  this.checkUpdateTimeout = setTimeout(async () => {
624
785
  const { checkUpdates } = await import('./update.js');
625
786
  checkUpdates(this);
626
787
  }, 30 * 1000).unref();
788
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
627
789
  clearInterval(this.checkUpdateInterval);
628
790
  this.checkUpdateInterval = setInterval(async () => {
629
791
  const { checkUpdates } = await import('./update.js');
630
792
  checkUpdates(this);
631
793
  }, 12 * 60 * 60 * 1000).unref();
794
+ // Start the matterbridge in mode test
632
795
  if (hasParameter('test')) {
633
796
  this.bridgeMode = 'bridge';
634
797
  return;
635
798
  }
799
+ // Start the matterbridge in mode controller
636
800
  if (hasParameter('controller')) {
637
801
  this.bridgeMode = 'controller';
638
802
  await this.startController();
639
803
  return;
640
804
  }
805
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
641
806
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
642
807
  this.log.info('Setting default matterbridge start mode to bridge');
643
808
  await this.nodeContext?.set('bridgeMode', 'bridge');
644
809
  }
810
+ // Start matterbridge in bridge mode
645
811
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
646
812
  this.bridgeMode = 'bridge';
647
813
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
648
814
  await this.startBridge();
649
815
  return;
650
816
  }
817
+ // Start matterbridge in childbridge mode
651
818
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
652
819
  this.bridgeMode = 'childbridge';
653
820
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -655,10 +822,22 @@ export class Matterbridge extends EventEmitter {
655
822
  return;
656
823
  }
657
824
  }
825
+ /**
826
+ * Asynchronously loads and starts the registered plugins.
827
+ *
828
+ * This method is responsible for initializing and starting all enabled plugins.
829
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
830
+ *
831
+ * @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving. Defaults to false.
832
+ * @param {boolean} [start] - If true, the method will start the plugins after loading them. Defaults to true.
833
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
834
+ */
658
835
  async startPlugins(wait = false, start = true) {
836
+ // Check, load and start the plugins
659
837
  for (const plugin of this.plugins) {
660
838
  plugin.configJson = await this.plugins.loadConfig(plugin);
661
839
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
840
+ // Check if the plugin is available
662
841
  if (!(await this.plugins.resolve(plugin.path))) {
663
842
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
664
843
  plugin.enabled = false;
@@ -678,10 +857,16 @@ export class Matterbridge extends EventEmitter {
678
857
  if (wait)
679
858
  await this.plugins.load(plugin, start, 'Matterbridge is starting');
680
859
  else
681
- this.plugins.load(plugin, start, 'Matterbridge is starting');
860
+ this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
682
861
  }
683
862
  this.frontend.wssSendRefreshRequired('plugins');
684
863
  }
864
+ /**
865
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
866
+ * - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
867
+ * - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
868
+ * - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
869
+ */
685
870
  registerProcessHandlers() {
686
871
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
687
872
  process.removeAllListeners('uncaughtException');
@@ -708,6 +893,9 @@ export class Matterbridge extends EventEmitter {
708
893
  };
709
894
  process.on('SIGTERM', this.sigtermHandler);
710
895
  }
896
+ /**
897
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
898
+ */
711
899
  deregisterProcessHandlers() {
712
900
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
713
901
  if (this.exceptionHandler)
@@ -724,13 +912,18 @@ export class Matterbridge extends EventEmitter {
724
912
  process.off('SIGTERM', this.sigtermHandler);
725
913
  this.sigtermHandler = undefined;
726
914
  }
915
+ /**
916
+ * Logs the node and system information.
917
+ */
727
918
  async logNodeAndSystemInfo() {
919
+ // IP address information
728
920
  const networkInterfaces = os.networkInterfaces();
729
921
  this.systemInformation.interfaceName = '';
730
922
  this.systemInformation.ipv4Address = '';
731
923
  this.systemInformation.ipv6Address = '';
732
924
  this.systemInformation.macAddress = '';
733
925
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
926
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
734
927
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
735
928
  continue;
736
929
  if (!interfaceDetails) {
@@ -756,16 +949,18 @@ export class Matterbridge extends EventEmitter {
756
949
  break;
757
950
  }
758
951
  }
952
+ // Node information
759
953
  this.systemInformation.nodeVersion = process.versions.node;
760
954
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
761
955
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
762
956
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
957
+ // Host system information
763
958
  this.systemInformation.hostname = os.hostname();
764
959
  this.systemInformation.user = os.userInfo().username;
765
- this.systemInformation.osType = os.type();
766
- this.systemInformation.osRelease = os.release();
767
- this.systemInformation.osPlatform = os.platform();
768
- this.systemInformation.osArch = os.arch();
960
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
961
+ this.systemInformation.osRelease = os.release(); // Kernel version
962
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
963
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
769
964
  this.systemInformation.totalMemory = formatBytes(os.totalmem());
770
965
  this.systemInformation.freeMemory = formatBytes(os.freemem());
771
966
  this.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -775,6 +970,7 @@ export class Matterbridge extends EventEmitter {
775
970
  this.systemInformation.rss = formatBytes(process.memoryUsage().rss);
776
971
  this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
777
972
  this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
973
+ // Log the system information
778
974
  this.log.debug('Host System Information:');
779
975
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
780
976
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -794,14 +990,17 @@ export class Matterbridge extends EventEmitter {
794
990
  this.log.debug(`- RSS: ${this.systemInformation.rss}`);
795
991
  this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
796
992
  this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
993
+ // Log directories
797
994
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
798
995
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
799
996
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
800
997
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
801
998
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
999
+ // Global node_modules directory
802
1000
  if (this.nodeContext)
803
1001
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
804
1002
  if (this.globalModulesDirectory === '') {
1003
+ // First run of Matterbridge so the node storage is empty
805
1004
  this.log.debug(`Getting global node_modules directory...`);
806
1005
  try {
807
1006
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -814,6 +1013,7 @@ export class Matterbridge extends EventEmitter {
814
1013
  }
815
1014
  }
816
1015
  else {
1016
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
817
1017
  this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
818
1018
  try {
819
1019
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -825,25 +1025,37 @@ export class Matterbridge extends EventEmitter {
825
1025
  this.log.error(`Error checking global node_modules directory: ${error}`);
826
1026
  }
827
1027
  }
1028
+ // Matterbridge version
828
1029
  this.log.debug(`Reading matterbridge package.json...`);
829
1030
  const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
830
1031
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
831
1032
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1033
+ // Matterbridge latest version (will be set in the checkUpdate function)
832
1034
  if (this.nodeContext)
833
1035
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
834
1036
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1037
+ // Matterbridge dev version (will be set in the checkUpdate function)
835
1038
  if (this.nodeContext)
836
1039
  this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
837
1040
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1041
+ // Frontend version
838
1042
  this.log.debug(`Reading frontend package.json...`);
839
1043
  const frontendPackageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
840
1044
  this.frontendVersion = frontendPackageJson.version;
841
1045
  this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
1046
+ // Current working directory
842
1047
  const currentDir = process.cwd();
843
1048
  this.log.debug(`Current Working Directory: ${currentDir}`);
1049
+ // Command line arguments (excluding 'node' and the script name)
844
1050
  const cmdArgs = process.argv.slice(2).join(' ');
845
1051
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
846
1052
  }
1053
+ /**
1054
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
1055
+ *
1056
+ * @param {LogLevel} logLevel The logger logLevel to set.
1057
+ * @returns {Promise<LogLevel>} A promise that resolves when the logLevel has been set.
1058
+ */
847
1059
  async setLogLevel(logLevel) {
848
1060
  this.logLevel = logLevel;
849
1061
  this.log.logLevel = logLevel;
@@ -854,58 +1066,87 @@ export class Matterbridge extends EventEmitter {
854
1066
  for (const plugin of this.plugins) {
855
1067
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
856
1068
  continue;
857
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
858
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
859
- }
860
- let callbackLogLevel = "notice";
861
- if (logLevel === "info" || Logger.level === MatterLogLevel.INFO)
862
- callbackLogLevel = "info";
863
- if (logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG)
864
- callbackLogLevel = "debug";
1069
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel;
1070
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel);
1071
+ }
1072
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1073
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1074
+ if (logLevel === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1075
+ callbackLogLevel = "info" /* LogLevel.INFO */;
1076
+ if (logLevel === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1077
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
865
1078
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
866
1079
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
867
1080
  return logLevel;
868
1081
  }
1082
+ /**
1083
+ * Get the current logger logLevel.
1084
+ *
1085
+ * @returns {LogLevel} The current logger logLevel.
1086
+ */
869
1087
  getLogLevel() {
870
1088
  return this.log.logLevel;
871
1089
  }
1090
+ /**
1091
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1092
+ * It also logs to file (matter.log) if fileLogger is true.
1093
+ *
1094
+ * @param {boolean} fileLogger - Whether to log to file or not.
1095
+ * @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
1096
+ */
872
1097
  createDestinationMatterLogger(fileLogger) {
873
- this.matterLog.logNameColor = '\x1b[34m';
1098
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
874
1099
  if (fileLogger) {
875
1100
  this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
876
1101
  }
877
1102
  return (text, message) => {
1103
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
878
1104
  const logger = text.slice(44, 44 + 20).trim();
879
1105
  const msg = text.slice(65);
880
1106
  this.matterLog.logName = logger;
881
1107
  switch (message.level) {
882
1108
  case MatterLogLevel.DEBUG:
883
- this.matterLog.log("debug", msg);
1109
+ this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
884
1110
  break;
885
1111
  case MatterLogLevel.INFO:
886
- this.matterLog.log("info", msg);
1112
+ this.matterLog.log("info" /* LogLevel.INFO */, msg);
887
1113
  break;
888
1114
  case MatterLogLevel.NOTICE:
889
- this.matterLog.log("notice", msg);
1115
+ this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
890
1116
  break;
891
1117
  case MatterLogLevel.WARN:
892
- this.matterLog.log("warn", msg);
1118
+ this.matterLog.log("warn" /* LogLevel.WARN */, msg);
893
1119
  break;
894
1120
  case MatterLogLevel.ERROR:
895
- this.matterLog.log("error", msg);
1121
+ this.matterLog.log("error" /* LogLevel.ERROR */, msg);
896
1122
  break;
897
1123
  case MatterLogLevel.FATAL:
898
- this.matterLog.log("fatal", msg);
1124
+ this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
899
1125
  break;
900
1126
  }
901
1127
  };
902
1128
  }
1129
+ /**
1130
+ * Restarts the process by exiting the current instance and loading a new instance (/api/restart).
1131
+ *
1132
+ * @returns {Promise<void>} A promise that resolves when the restart is completed.
1133
+ */
903
1134
  async restartProcess() {
904
1135
  await this.cleanup('restarting...', true);
905
1136
  }
1137
+ /**
1138
+ * Shut down the process (/api/shutdown).
1139
+ *
1140
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1141
+ */
906
1142
  async shutdownProcess() {
907
1143
  await this.cleanup('shutting down...', false);
908
1144
  }
1145
+ /**
1146
+ * Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
1147
+ *
1148
+ * @returns {Promise<void>} A promise that resolves when the update is completed.
1149
+ */
909
1150
  async updateProcess() {
910
1151
  this.log.info('Updating matterbridge...');
911
1152
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -918,6 +1159,13 @@ export class Matterbridge extends EventEmitter {
918
1159
  this.frontend.wssSendRestartRequired();
919
1160
  await this.cleanup('updating...', false);
920
1161
  }
1162
+ /**
1163
+ * Unregister all devices and shut down the process (/api/unregister).
1164
+ *
1165
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1166
+ *
1167
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1168
+ */
921
1169
  async unregisterAndShutdownProcess(timeout = 1000) {
922
1170
  this.log.info('Unregistering all devices and shutting down...');
923
1171
  for (const plugin of this.plugins.array()) {
@@ -929,46 +1177,71 @@ export class Matterbridge extends EventEmitter {
929
1177
  await this.removeAllBridgedEndpoints(plugin.name, 100);
930
1178
  }
931
1179
  this.log.debug('Waiting for the MessageExchange to finish...');
932
- await wait(timeout);
1180
+ await wait(timeout); // Wait for MessageExchange to finish
933
1181
  this.log.debug('Cleaning up and shutting down...');
934
1182
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
935
1183
  }
1184
+ /**
1185
+ * Reset commissioning and shut down the process (/api/reset).
1186
+ *
1187
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1188
+ */
936
1189
  async shutdownProcessAndReset() {
937
1190
  await this.cleanup('shutting down with reset...', false);
938
1191
  }
1192
+ /**
1193
+ * Factory reset and shut down the process (/api/factory-reset).
1194
+ *
1195
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1196
+ */
939
1197
  async shutdownProcessAndFactoryReset() {
940
1198
  await this.cleanup('shutting down with factory reset...', false);
941
1199
  }
1200
+ /**
1201
+ * Cleans up the Matterbridge instance.
1202
+ *
1203
+ * @param {string} message - The cleanup message.
1204
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1205
+ * @param {number} [pause] - The pause in ms to wait for the message exchange to complete in milliseconds. Default is 1000.
1206
+ *
1207
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1208
+ */
942
1209
  async cleanup(message, restart = false, pause = 1000) {
943
1210
  if (this.initialized && !this.hasCleanupStarted) {
944
1211
  this.emit('cleanup_started');
945
1212
  this.hasCleanupStarted = true;
946
1213
  this.log.info(message);
1214
+ // Clear the start matter interval
947
1215
  if (this.startMatterInterval) {
948
1216
  clearInterval(this.startMatterInterval);
949
1217
  this.startMatterInterval = undefined;
950
1218
  this.log.debug('Start matter interval cleared');
951
1219
  }
1220
+ // Clear the check update timeout
952
1221
  if (this.checkUpdateTimeout) {
953
1222
  clearTimeout(this.checkUpdateTimeout);
954
1223
  this.checkUpdateTimeout = undefined;
955
1224
  this.log.debug('Check update timeout cleared');
956
1225
  }
1226
+ // Clear the check update interval
957
1227
  if (this.checkUpdateInterval) {
958
1228
  clearInterval(this.checkUpdateInterval);
959
1229
  this.checkUpdateInterval = undefined;
960
1230
  this.log.debug('Check update interval cleared');
961
1231
  }
1232
+ // Clear the configure timeout
962
1233
  if (this.configureTimeout) {
963
1234
  clearTimeout(this.configureTimeout);
964
1235
  this.configureTimeout = undefined;
965
1236
  this.log.debug('Matterbridge configure timeout cleared');
966
1237
  }
1238
+ // Clear the reachability timeout
967
1239
  if (this.reachabilityTimeout) {
968
1240
  clearTimeout(this.reachabilityTimeout);
969
1241
  this.reachabilityTimeout = undefined;
970
1242
  this.log.debug('Matterbridge reachability timeout cleared');
971
1243
  }
1244
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
972
1245
  for (const plugin of this.plugins) {
973
1246
  if (!plugin.enabled || plugin.error)
974
1247
  continue;
@@ -979,6 +1252,7 @@ export class Matterbridge extends EventEmitter {
979
1252
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
980
1253
  }
981
1254
  }
1255
+ // Stop matter server nodes
982
1256
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
983
1257
  if (pause > 0) {
984
1258
  this.log.debug(`Waiting ${pause}ms for the MessageExchange to finish...`);
@@ -1005,6 +1279,7 @@ export class Matterbridge extends EventEmitter {
1005
1279
  }
1006
1280
  }
1007
1281
  this.log.notice('Stopped matter server nodes');
1282
+ // Matter commisioning reset
1008
1283
  if (message === 'shutting down with reset...') {
1009
1284
  this.log.info('Resetting Matterbridge commissioning information...');
1010
1285
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1014,6 +1289,7 @@ export class Matterbridge extends EventEmitter {
1014
1289
  await this.matterbridgeContext?.clearAll();
1015
1290
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1016
1291
  }
1292
+ // Unregister all devices
1017
1293
  if (message === 'unregistered all devices and shutting down...') {
1018
1294
  if (this.bridgeMode === 'bridge') {
1019
1295
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1031,16 +1307,35 @@ export class Matterbridge extends EventEmitter {
1031
1307
  }
1032
1308
  this.log.info('Matter storage reset done!');
1033
1309
  }
1310
+ // Stop matter storage
1034
1311
  await this.stopMatterStorage();
1312
+ // Stop the frontend
1035
1313
  await this.frontend.stop();
1036
1314
  this.frontend.destroy();
1315
+ // Close PluginManager and DeviceManager
1037
1316
  this.plugins.destroy();
1038
1317
  this.devices.destroy();
1318
+ // Stop thread messaging server
1039
1319
  this.server.close();
1320
+ // Close the matterbridge node storage and context
1040
1321
  if (this.nodeStorage && this.nodeContext) {
1322
+ /*
1323
+ TODO: Implement serialization of registered devices
1324
+ this.log.info('Saving registered devices...');
1325
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1326
+ this.devices.forEach(async (device) => {
1327
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1328
+ this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1329
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1330
+ });
1331
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1332
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1333
+ */
1334
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1041
1335
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1042
1336
  await this.nodeContext.close();
1043
1337
  this.nodeContext = undefined;
1338
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1044
1339
  for (const plugin of this.plugins) {
1045
1340
  if (plugin.nodeContext) {
1046
1341
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1057,8 +1352,10 @@ export class Matterbridge extends EventEmitter {
1057
1352
  }
1058
1353
  this.plugins.clear();
1059
1354
  this.devices.clear();
1355
+ // Factory reset
1060
1356
  if (message === 'shutting down with factory reset...') {
1061
1357
  try {
1358
+ // Delete matter storage directory with its subdirectories and backup
1062
1359
  const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
1063
1360
  this.log.info(`Removing matter storage directory: ${dir}`);
1064
1361
  await fs.promises.rm(dir, { recursive: true });
@@ -1067,11 +1364,13 @@ export class Matterbridge extends EventEmitter {
1067
1364
  await fs.promises.rm(backup, { recursive: true });
1068
1365
  }
1069
1366
  catch (error) {
1367
+ // istanbul ignore next if
1070
1368
  if (error instanceof Error && error.code !== 'ENOENT') {
1071
1369
  this.log.error(`Error removing matter storage directory: ${error}`);
1072
1370
  }
1073
1371
  }
1074
1372
  try {
1373
+ // Delete matterbridge storage directory with its subdirectories and backup
1075
1374
  const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
1076
1375
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1077
1376
  await fs.promises.rm(dir, { recursive: true });
@@ -1080,18 +1379,20 @@ export class Matterbridge extends EventEmitter {
1080
1379
  await fs.promises.rm(backup, { recursive: true });
1081
1380
  }
1082
1381
  catch (error) {
1382
+ // istanbul ignore next if
1083
1383
  if (error instanceof Error && error.code !== 'ENOENT') {
1084
1384
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1085
1385
  }
1086
1386
  }
1087
1387
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1088
1388
  }
1389
+ // Deregisters the process handlers
1089
1390
  this.deregisterProcessHandlers();
1090
1391
  if (restart) {
1091
1392
  if (message === 'updating...') {
1092
1393
  this.log.info('Cleanup completed. Updating...');
1093
1394
  Matterbridge.instance = undefined;
1094
- this.emit('update');
1395
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1095
1396
  }
1096
1397
  else if (message === 'restarting...') {
1097
1398
  this.log.info('Cleanup completed. Restarting...');
@@ -1120,7 +1421,14 @@ export class Matterbridge extends EventEmitter {
1120
1421
  this.log.debug('Cleanup already started...');
1121
1422
  }
1122
1423
  }
1424
+ /**
1425
+ * Starts the Matterbridge in bridge mode.
1426
+ *
1427
+ * @private
1428
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1429
+ */
1123
1430
  async startBridge() {
1431
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1124
1432
  if (!this.matterStorageManager)
1125
1433
  throw new Error('No storage manager initialized');
1126
1434
  if (!this.matterbridgeContext)
@@ -1159,13 +1467,16 @@ export class Matterbridge extends EventEmitter {
1159
1467
  clearInterval(this.startMatterInterval);
1160
1468
  this.startMatterInterval = undefined;
1161
1469
  this.log.debug('Cleared startMatterInterval interval in bridge mode');
1162
- this.startServerNode(this.serverNode);
1470
+ // Start the Matter server node
1471
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1472
+ // Start the Matter server node of single devices in mode 'server'
1163
1473
  for (const device of this.devices.array()) {
1164
1474
  if (device.mode === 'server' && device.serverNode) {
1165
1475
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1166
- this.startServerNode(device.serverNode);
1476
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1167
1477
  }
1168
1478
  }
1479
+ // Configure the plugins
1169
1480
  this.configureTimeout = setTimeout(async () => {
1170
1481
  for (const plugin of this.plugins.array()) {
1171
1482
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1183,28 +1494,40 @@ export class Matterbridge extends EventEmitter {
1183
1494
  }
1184
1495
  this.frontend.wssSendRefreshRequired('plugins');
1185
1496
  }, 30 * 1000).unref();
1497
+ // Setting reachability to true
1186
1498
  this.reachabilityTimeout = setTimeout(() => {
1187
1499
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1188
1500
  if (this.aggregatorNode)
1189
1501
  this.setAggregatorReachability(this.aggregatorNode, true);
1190
1502
  }, 60 * 1000).unref();
1503
+ // Logger.get('LogServerNode').info(this.serverNode);
1191
1504
  this.emit('bridge_started');
1192
1505
  this.log.notice('Matterbridge bridge started successfully');
1193
1506
  this.frontend.wssSendRefreshRequired('settings');
1194
1507
  this.frontend.wssSendRefreshRequired('plugins');
1195
1508
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1196
1509
  }
1510
+ /**
1511
+ * Starts the Matterbridge in childbridge mode.
1512
+ *
1513
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1514
+ *
1515
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1516
+ */
1197
1517
  async startChildbridge(delay = 1000) {
1198
1518
  if (!this.matterStorageManager)
1199
1519
  throw new Error('No storage manager initialized');
1520
+ // Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
1200
1521
  this.log.debug('Loading all plugins in childbridge mode...');
1201
1522
  await this.startPlugins(true, false);
1523
+ // Create server nodes for DynamicPlatform plugins and start all plugins in the background
1202
1524
  this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
1203
1525
  for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
1204
1526
  if (plugin.type === 'DynamicPlatform')
1205
1527
  await this.createDynamicPlugin(plugin);
1206
- this.plugins.start(plugin, 'Matterbridge is starting');
1528
+ this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
1207
1529
  }
1530
+ // Start the Matterbridge in childbridge mode when all plugins are loaded and started
1208
1531
  this.log.debug('Starting start matter interval in childbridge mode...');
1209
1532
  let failCount = 0;
1210
1533
  this.startMatterInterval = setInterval(async () => {
@@ -1238,8 +1561,9 @@ export class Matterbridge extends EventEmitter {
1238
1561
  clearInterval(this.startMatterInterval);
1239
1562
  this.startMatterInterval = undefined;
1240
1563
  if (delay > 0)
1241
- await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
1564
+ await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1242
1565
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1566
+ // Configure the plugins
1243
1567
  this.configureTimeout = setTimeout(async () => {
1244
1568
  for (const plugin of this.plugins.array()) {
1245
1569
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1264,6 +1588,7 @@ export class Matterbridge extends EventEmitter {
1264
1588
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1265
1589
  continue;
1266
1590
  }
1591
+ // istanbul ignore next if cause is just a safety check
1267
1592
  if (!plugin.serverNode) {
1268
1593
  this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1269
1594
  continue;
@@ -1276,28 +1601,252 @@ export class Matterbridge extends EventEmitter {
1276
1601
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1277
1602
  continue;
1278
1603
  }
1279
- this.startServerNode(plugin.serverNode);
1604
+ // Start the Matter server node
1605
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1606
+ // Setting reachability to true
1280
1607
  plugin.reachabilityTimeout = setTimeout(() => {
1281
1608
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1282
1609
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1283
1610
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1284
1611
  }, 60 * 1000).unref();
1285
1612
  }
1613
+ // Start the Matter server node of single devices in mode 'server'
1286
1614
  for (const device of this.devices.array()) {
1287
1615
  if (device.mode === 'server' && device.serverNode) {
1288
1616
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1289
- this.startServerNode(device.serverNode);
1617
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1290
1618
  }
1291
1619
  }
1620
+ // Logger.get('LogServerNode').info(this.serverNode);
1292
1621
  this.emit('childbridge_started');
1293
1622
  this.log.notice('Matterbridge childbridge started successfully');
1294
1623
  this.frontend.wssSendRefreshRequired('settings');
1295
1624
  this.frontend.wssSendRefreshRequired('plugins');
1296
1625
  }, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
1297
1626
  }
1627
+ /**
1628
+ * Starts the Matterbridge controller.
1629
+ *
1630
+ * @private
1631
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1632
+ */
1298
1633
  async startController() {
1634
+ /*
1635
+ if (!this.matterStorageManager) {
1636
+ this.log.error('No storage manager initialized');
1637
+ await this.cleanup('No storage manager initialized');
1638
+ return;
1639
+ }
1640
+ this.log.info('Creating context: mattercontrollerContext');
1641
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1642
+ if (!this.controllerContext) {
1643
+ this.log.error('No storage context mattercontrollerContext initialized');
1644
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1645
+ return;
1646
+ }
1647
+
1648
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1649
+ this.matterServer = await this.createMatterServer(this.storageManager);
1650
+ this.log.info('Creating matter commissioning controller');
1651
+ this.commissioningController = new CommissioningController({
1652
+ autoConnect: false,
1653
+ });
1654
+ this.log.info('Adding matter commissioning controller to matter server');
1655
+ await this.matterServer.addCommissioningController(this.commissioningController);
1656
+
1657
+ this.log.info('Starting matter server');
1658
+ await this.matterServer.start();
1659
+ this.log.info('Matter server started');
1660
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1661
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1662
+ regulatoryCountryCode: 'XX',
1663
+ };
1664
+ const commissioningController = new CommissioningController({
1665
+ environment: {
1666
+ environment,
1667
+ id: uniqueId,
1668
+ },
1669
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1670
+ adminFabricLabel,
1671
+ });
1672
+
1673
+ if (hasParameter('pairingcode')) {
1674
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1675
+ const pairingCode = getParameter('pairingcode');
1676
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1677
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1678
+
1679
+ let longDiscriminator, setupPin, shortDiscriminator;
1680
+ if (pairingCode !== undefined) {
1681
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1682
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1683
+ longDiscriminator = undefined;
1684
+ setupPin = pairingCodeCodec.passcode;
1685
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1686
+ } else {
1687
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1688
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1689
+ setupPin = this.controllerContext.get('pin', 20202021);
1690
+ }
1691
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1692
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1693
+ }
1694
+
1695
+ const options = {
1696
+ commissioning: commissioningOptions,
1697
+ discovery: {
1698
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1699
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1700
+ },
1701
+ passcode: setupPin,
1702
+ } as NodeCommissioningOptions;
1703
+ this.log.info('Commissioning with options:', options);
1704
+ const nodeId = await this.commissioningController.commissionNode(options);
1705
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1706
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1707
+ } // (hasParameter('pairingcode'))
1708
+
1709
+ if (hasParameter('unpairall')) {
1710
+ this.log.info('***Commissioning controller unpairing all nodes...');
1711
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1712
+ for (const nodeId of nodeIds) {
1713
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1714
+ await this.commissioningController.removeNode(nodeId);
1715
+ }
1716
+ return;
1717
+ }
1718
+
1719
+ if (hasParameter('discover')) {
1720
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1721
+ // console.log(discover);
1722
+ }
1723
+
1724
+ if (!this.commissioningController.isCommissioned()) {
1725
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1726
+ return;
1727
+ }
1728
+
1729
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1730
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1731
+ for (const nodeId of nodeIds) {
1732
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1733
+
1734
+ const node = await this.commissioningController.connectNode(nodeId, {
1735
+ autoSubscribe: false,
1736
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1737
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1738
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1739
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1740
+ stateInformationCallback: (peerNodeId, info) => {
1741
+ switch (info) {
1742
+ case NodeStateInformation.Connected:
1743
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1744
+ break;
1745
+ case NodeStateInformation.Disconnected:
1746
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1747
+ break;
1748
+ case NodeStateInformation.Reconnecting:
1749
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1750
+ break;
1751
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1752
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1753
+ break;
1754
+ case NodeStateInformation.StructureChanged:
1755
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1756
+ break;
1757
+ case NodeStateInformation.Decommissioned:
1758
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1759
+ break;
1760
+ default:
1761
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1762
+ break;
1763
+ }
1764
+ },
1765
+ });
1766
+
1767
+ node.logStructure();
1768
+
1769
+ // Get the interaction client
1770
+ this.log.info('Getting the interaction client');
1771
+ const interactionClient = await node.getInteractionClient();
1772
+ let cluster;
1773
+ let attributes;
1774
+
1775
+ // Log BasicInformationCluster
1776
+ cluster = BasicInformationCluster;
1777
+ attributes = await interactionClient.getMultipleAttributes({
1778
+ attributes: [{ clusterId: cluster.id }],
1779
+ });
1780
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1781
+ attributes.forEach((attribute) => {
1782
+ this.log.info(
1783
+ `- 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}`,
1784
+ );
1785
+ });
1786
+
1787
+ // Log PowerSourceCluster
1788
+ cluster = PowerSourceCluster;
1789
+ attributes = await interactionClient.getMultipleAttributes({
1790
+ attributes: [{ clusterId: cluster.id }],
1791
+ });
1792
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1793
+ attributes.forEach((attribute) => {
1794
+ this.log.info(
1795
+ `- 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}`,
1796
+ );
1797
+ });
1798
+
1799
+ // Log ThreadNetworkDiagnostics
1800
+ cluster = ThreadNetworkDiagnosticsCluster;
1801
+ attributes = await interactionClient.getMultipleAttributes({
1802
+ attributes: [{ clusterId: cluster.id }],
1803
+ });
1804
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1805
+ attributes.forEach((attribute) => {
1806
+ this.log.info(
1807
+ `- 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}`,
1808
+ );
1809
+ });
1810
+
1811
+ // Log SwitchCluster
1812
+ cluster = SwitchCluster;
1813
+ attributes = await interactionClient.getMultipleAttributes({
1814
+ attributes: [{ clusterId: cluster.id }],
1815
+ });
1816
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1817
+ attributes.forEach((attribute) => {
1818
+ this.log.info(
1819
+ `- 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}`,
1820
+ );
1821
+ });
1822
+
1823
+ this.log.info('Subscribing to all attributes and events');
1824
+ await node.subscribeAllAttributesAndEvents({
1825
+ ignoreInitialTriggers: false,
1826
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1827
+ this.log.info(
1828
+ `***${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}`,
1829
+ ),
1830
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1831
+ this.log.info(
1832
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1833
+ );
1834
+ },
1835
+ });
1836
+ this.log.info('Subscribed to all attributes and events');
1837
+ }
1838
+ */
1299
1839
  }
1840
+ /** */
1841
+ /** Matter.js methods */
1842
+ /** */
1843
+ /**
1844
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1845
+ *
1846
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1847
+ */
1300
1848
  async startMatterStorage() {
1849
+ // Setup Matter storage
1301
1850
  this.log.info(`Starting matter node storage...`);
1302
1851
  this.matterStorageService = this.environment.get(StorageService);
1303
1852
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1305,8 +1854,17 @@ export class Matterbridge extends EventEmitter {
1305
1854
  this.log.info('Matter node storage manager "Matterbridge" created');
1306
1855
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1307
1856
  this.log.info('Matter node storage started');
1857
+ // Backup matter storage since it is created/opened correctly
1308
1858
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
1309
1859
  }
1860
+ /**
1861
+ * Makes a backup copy of the specified matter storage directory.
1862
+ *
1863
+ * @param {string} storageName - The name of the storage directory to be backed up.
1864
+ * @param {string} backupName - The name of the backup directory to be created.
1865
+ * @private
1866
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1867
+ */
1310
1868
  async backupMatterStorage(storageName, backupName) {
1311
1869
  this.log.info('Creating matter node storage backup...');
1312
1870
  try {
@@ -1317,6 +1875,11 @@ export class Matterbridge extends EventEmitter {
1317
1875
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1318
1876
  }
1319
1877
  }
1878
+ /**
1879
+ * Stops the matter storage.
1880
+ *
1881
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1882
+ */
1320
1883
  async stopMatterStorage() {
1321
1884
  this.log.info('Closing matter node storage...');
1322
1885
  await this.matterStorageManager?.close();
@@ -1325,6 +1888,20 @@ export class Matterbridge extends EventEmitter {
1325
1888
  this.matterbridgeContext = undefined;
1326
1889
  this.log.info('Matter node storage closed');
1327
1890
  }
1891
+ /**
1892
+ * Creates a server node storage context.
1893
+ *
1894
+ * @param {string} storeId - The storeId.
1895
+ * @param {string} deviceName - The name of the device.
1896
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1897
+ * @param {number} vendorId - The vendor ID.
1898
+ * @param {string} vendorName - The vendor name.
1899
+ * @param {number} productId - The product ID.
1900
+ * @param {string} productName - The product name.
1901
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1902
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
1903
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1904
+ */
1328
1905
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1329
1906
  const { randomBytes } = await import('node:crypto');
1330
1907
  if (!this.matterStorageService)
@@ -1364,6 +1941,15 @@ export class Matterbridge extends EventEmitter {
1364
1941
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1365
1942
  return storageContext;
1366
1943
  }
1944
+ /**
1945
+ * Creates a server node.
1946
+ *
1947
+ * @param {StorageContext} storageContext - The storage context for the server node.
1948
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
1949
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
1950
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
1951
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1952
+ */
1367
1953
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1368
1954
  const storeId = await storageContext.get('storeId');
1369
1955
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1373,25 +1959,35 @@ export class Matterbridge extends EventEmitter {
1373
1959
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1374
1960
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1375
1961
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1962
+ /**
1963
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1964
+ */
1376
1965
  const serverNode = await ServerNode.create({
1966
+ // Required: Give the Node a unique ID which is used to store the state of this node
1377
1967
  id: storeId,
1968
+ // Environment to run the server node in
1378
1969
  environment: this.environment,
1970
+ // Provide Network relevant configuration like the port
1379
1971
  network: {
1380
1972
  listeningAddressIpv4: this.ipv4Address,
1381
1973
  listeningAddressIpv6: this.ipv6Address,
1382
1974
  port,
1383
1975
  },
1976
+ // Provide the certificate for the device
1384
1977
  operationalCredentials: {
1385
1978
  certification: this.certification,
1386
1979
  },
1980
+ // Provide Commissioning relevant settings
1387
1981
  commissioning: {
1388
1982
  passcode,
1389
1983
  discriminator,
1390
1984
  },
1985
+ // Provide Node announcement settings
1391
1986
  productDescription: {
1392
1987
  name: await storageContext.get('deviceName'),
1393
1988
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1394
1989
  },
1990
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1395
1991
  basicInformation: {
1396
1992
  vendorId: VendorId(await storageContext.get('vendorId')),
1397
1993
  vendorName: await storageContext.get('vendorName'),
@@ -1408,17 +2004,23 @@ export class Matterbridge extends EventEmitter {
1408
2004
  reachable: true,
1409
2005
  },
1410
2006
  });
2007
+ /**
2008
+ * This event is triggered when the device is initially commissioned successfully.
2009
+ * This means: It is added to the first fabric.
2010
+ */
1411
2011
  serverNode.lifecycle.commissioned.on(() => {
1412
2012
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1413
2013
  this.advertisingNodes.delete(storeId);
1414
2014
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1415
2015
  });
2016
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1416
2017
  serverNode.lifecycle.decommissioned.on(() => {
1417
2018
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1418
2019
  this.advertisingNodes.delete(storeId);
1419
2020
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1420
2021
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1421
2022
  });
2023
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1422
2024
  serverNode.lifecycle.online.on(async () => {
1423
2025
  this.log.notice(`Server node for ${storeId} is online`);
1424
2026
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1429,13 +2031,16 @@ export class Matterbridge extends EventEmitter {
1429
2031
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1430
2032
  }
1431
2033
  else {
2034
+ // istanbul ignore next
1432
2035
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2036
+ // istanbul ignore next
1433
2037
  this.advertisingNodes.delete(storeId);
1434
2038
  }
1435
2039
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1436
2040
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1437
2041
  this.emit('online', storeId);
1438
2042
  });
2043
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1439
2044
  serverNode.lifecycle.offline.on(() => {
1440
2045
  this.log.notice(`Server node for ${storeId} is offline`);
1441
2046
  this.advertisingNodes.delete(storeId);
@@ -1443,11 +2048,15 @@ export class Matterbridge extends EventEmitter {
1443
2048
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1444
2049
  this.emit('offline', storeId);
1445
2050
  });
2051
+ /**
2052
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2053
+ * information is needed.
2054
+ */
1446
2055
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1447
2056
  let action = '';
1448
2057
  switch (fabricAction) {
1449
2058
  case FabricAction.Added:
1450
- this.advertisingNodes.delete(storeId);
2059
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1451
2060
  action = 'added';
1452
2061
  break;
1453
2062
  case FabricAction.Removed:
@@ -1460,14 +2069,22 @@ export class Matterbridge extends EventEmitter {
1460
2069
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1461
2070
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1462
2071
  });
2072
+ /**
2073
+ * This event is triggered when an operative new session was opened by a Controller.
2074
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2075
+ */
1463
2076
  serverNode.events.sessions.opened.on((session) => {
1464
2077
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1465
2078
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1466
2079
  });
2080
+ /**
2081
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2082
+ */
1467
2083
  serverNode.events.sessions.closed.on((session) => {
1468
2084
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1469
2085
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1470
2086
  });
2087
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1471
2088
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1472
2089
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1473
2090
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
@@ -1475,6 +2092,12 @@ export class Matterbridge extends EventEmitter {
1475
2092
  this.log.info(`Created server node for ${storeId}`);
1476
2093
  return serverNode;
1477
2094
  }
2095
+ /**
2096
+ * Gets the matter sanitized data of the specified server node.
2097
+ *
2098
+ * @param {ServerNode} [serverNode] - The server node to start.
2099
+ * @returns {ApiMatter} The sanitized data of the server node.
2100
+ */
1478
2101
  getServerNodeData(serverNode) {
1479
2102
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1480
2103
  return {
@@ -1491,12 +2114,25 @@ export class Matterbridge extends EventEmitter {
1491
2114
  serialNumber: serverNode.state.basicInformation.serialNumber,
1492
2115
  };
1493
2116
  }
2117
+ /**
2118
+ * Starts the specified server node.
2119
+ *
2120
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2121
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2122
+ */
1494
2123
  async startServerNode(matterServerNode) {
1495
2124
  if (!matterServerNode)
1496
2125
  return;
1497
2126
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1498
2127
  await matterServerNode.start();
1499
2128
  }
2129
+ /**
2130
+ * Stops the specified server node.
2131
+ *
2132
+ * @param {ServerNode} matterServerNode - The server node to stop.
2133
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2134
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2135
+ */
1500
2136
  async stopServerNode(matterServerNode, timeout = 30000) {
1501
2137
  if (!matterServerNode)
1502
2138
  return;
@@ -1509,12 +2145,25 @@ export class Matterbridge extends EventEmitter {
1509
2145
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1510
2146
  }
1511
2147
  }
2148
+ /**
2149
+ * Creates an aggregator node with the specified storage context.
2150
+ *
2151
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2152
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2153
+ */
1512
2154
  async createAggregatorNode(storageContext) {
1513
2155
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1514
2156
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1515
2157
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1516
2158
  return aggregatorNode;
1517
2159
  }
2160
+ /**
2161
+ * Creates and configures the server node for an accessory plugin for a given device.
2162
+ *
2163
+ * @param {Plugin} plugin - The plugin to configure.
2164
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2165
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2166
+ */
1518
2167
  async createAccessoryPlugin(plugin, device) {
1519
2168
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1520
2169
  plugin.locked = true;
@@ -1526,6 +2175,12 @@ export class Matterbridge extends EventEmitter {
1526
2175
  await plugin.serverNode.add(device);
1527
2176
  }
1528
2177
  }
2178
+ /**
2179
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
2180
+ *
2181
+ * @param {Plugin} plugin - The plugin to configure.
2182
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
2183
+ */
1529
2184
  async createDynamicPlugin(plugin) {
1530
2185
  if (!plugin.locked) {
1531
2186
  plugin.locked = true;
@@ -1538,6 +2193,13 @@ export class Matterbridge extends EventEmitter {
1538
2193
  await plugin.serverNode.add(plugin.aggregatorNode);
1539
2194
  }
1540
2195
  }
2196
+ /**
2197
+ * Creates and configures the server node for a single not bridged device.
2198
+ *
2199
+ * @param {Plugin} plugin - The plugin to configure.
2200
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2201
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2202
+ */
1541
2203
  async createDeviceServerNode(plugin, device) {
1542
2204
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1543
2205
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1548,7 +2210,15 @@ export class Matterbridge extends EventEmitter {
1548
2210
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1549
2211
  }
1550
2212
  }
2213
+ /**
2214
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2215
+ *
2216
+ * @param {string} pluginName - The name of the plugin.
2217
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2218
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2219
+ */
1551
2220
  async addBridgedEndpoint(pluginName, device) {
2221
+ // Check if the plugin is registered
1552
2222
  const plugin = this.plugins.get(pluginName);
1553
2223
  if (!plugin) {
1554
2224
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1568,6 +2238,7 @@ export class Matterbridge extends EventEmitter {
1568
2238
  }
1569
2239
  else if (this.bridgeMode === 'bridge') {
1570
2240
  if (device.mode === 'matter') {
2241
+ // Register and add the device to the matterbridge server node
1571
2242
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1572
2243
  if (!this.serverNode) {
1573
2244
  this.log.error('Server node not found for Matterbridge');
@@ -1584,6 +2255,7 @@ export class Matterbridge extends EventEmitter {
1584
2255
  }
1585
2256
  }
1586
2257
  else {
2258
+ // Register and add the device to the matterbridge aggregator node
1587
2259
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1588
2260
  if (!this.aggregatorNode) {
1589
2261
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1601,6 +2273,7 @@ export class Matterbridge extends EventEmitter {
1601
2273
  }
1602
2274
  }
1603
2275
  else if (this.bridgeMode === 'childbridge') {
2276
+ // Register and add the device to the plugin server node
1604
2277
  if (plugin.type === 'AccessoryPlatform') {
1605
2278
  try {
1606
2279
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1624,10 +2297,12 @@ export class Matterbridge extends EventEmitter {
1624
2297
  return;
1625
2298
  }
1626
2299
  }
2300
+ // Register and add the device to the plugin aggregator node
1627
2301
  if (plugin.type === 'DynamicPlatform') {
1628
2302
  try {
1629
2303
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1630
2304
  await this.createDynamicPlugin(plugin);
2305
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1631
2306
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1632
2307
  if (!plugin.aggregatorNode) {
1633
2308
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1648,17 +2323,28 @@ export class Matterbridge extends EventEmitter {
1648
2323
  }
1649
2324
  if (plugin.registeredDevices !== undefined)
1650
2325
  plugin.registeredDevices++;
2326
+ // Add the device to the DeviceManager
1651
2327
  this.devices.set(device);
2328
+ // Subscribe to the attributes changed event
1652
2329
  await this.subscribeAttributeChanged(plugin, device);
1653
2330
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1654
2331
  }
2332
+ /**
2333
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2334
+ *
2335
+ * @param {string} pluginName - The name of the plugin.
2336
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2337
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2338
+ */
1655
2339
  async removeBridgedEndpoint(pluginName, device) {
1656
2340
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2341
+ // Check if the plugin is registered
1657
2342
  const plugin = this.plugins.get(pluginName);
1658
2343
  if (!plugin) {
1659
2344
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1660
2345
  return;
1661
2346
  }
2347
+ // Unregister and remove the device from the matterbridge aggregator node
1662
2348
  if (this.bridgeMode === 'bridge') {
1663
2349
  if (!this.aggregatorNode) {
1664
2350
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1671,8 +2357,10 @@ export class Matterbridge extends EventEmitter {
1671
2357
  }
1672
2358
  else if (this.bridgeMode === 'childbridge') {
1673
2359
  if (plugin.type === 'AccessoryPlatform') {
2360
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1674
2361
  }
1675
2362
  else if (plugin.type === 'DynamicPlatform') {
2363
+ // Unregister and remove the device from the plugin aggregator node
1676
2364
  if (!plugin.aggregatorNode) {
1677
2365
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
1678
2366
  return;
@@ -1683,8 +2371,21 @@ export class Matterbridge extends EventEmitter {
1683
2371
  if (plugin.registeredDevices !== undefined)
1684
2372
  plugin.registeredDevices--;
1685
2373
  }
2374
+ // Remove the device from the DeviceManager
1686
2375
  this.devices.remove(device);
1687
2376
  }
2377
+ /**
2378
+ * Removes all bridged endpoints from the specified plugin.
2379
+ *
2380
+ * @param {string} pluginName - The name of the plugin.
2381
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2382
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2383
+ *
2384
+ * @remarks
2385
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2386
+ * It also applies a delay between each removal if specified.
2387
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2388
+ */
1688
2389
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1689
2390
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1690
2391
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1695,13 +2396,24 @@ export class Matterbridge extends EventEmitter {
1695
2396
  if (delay > 0)
1696
2397
  await wait(2000);
1697
2398
  }
2399
+ /**
2400
+ * Subscribes to the attribute change event for the given device and plugin.
2401
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2402
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2403
+ *
2404
+ * @param {Plugin} plugin - The plugin associated with the device.
2405
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2406
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2407
+ */
1698
2408
  async subscribeAttributeChanged(plugin, device) {
1699
2409
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
1700
2410
  return;
1701
2411
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
2412
+ // Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
1702
2413
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1703
2414
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1704
2415
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
2416
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1705
2417
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
1706
2418
  });
1707
2419
  }
@@ -1751,6 +2463,7 @@ export class Matterbridge extends EventEmitter {
1751
2463
  this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1752
2464
  await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1753
2465
  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}`);
2466
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1754
2467
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
1755
2468
  });
1756
2469
  }
@@ -1759,12 +2472,19 @@ export class Matterbridge extends EventEmitter {
1759
2472
  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...`);
1760
2473
  await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1761
2474
  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}`);
2475
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1762
2476
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
1763
2477
  });
1764
2478
  }
1765
2479
  }
1766
2480
  }
1767
2481
  }
2482
+ /**
2483
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2484
+ *
2485
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2486
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2487
+ */
1768
2488
  sanitizeFabricInformations(fabricInfo) {
1769
2489
  return fabricInfo.map((info) => {
1770
2490
  return {
@@ -1778,6 +2498,12 @@ export class Matterbridge extends EventEmitter {
1778
2498
  };
1779
2499
  });
1780
2500
  }
2501
+ /**
2502
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2503
+ *
2504
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2505
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2506
+ */
1781
2507
  sanitizeSessionInformation(sessions) {
1782
2508
  return sessions
1783
2509
  .filter((session) => session.isPeerActive)
@@ -1804,7 +2530,21 @@ export class Matterbridge extends EventEmitter {
1804
2530
  };
1805
2531
  });
1806
2532
  }
2533
+ /**
2534
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2535
+ *
2536
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2537
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2538
+ */
2539
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1807
2540
  async setAggregatorReachability(aggregatorNode, reachable) {
2541
+ /*
2542
+ for (const child of aggregatorNode.parts) {
2543
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2544
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2545
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2546
+ }
2547
+ */
1808
2548
  }
1809
2549
  getVendorIdName = (vendorId) => {
1810
2550
  if (!vendorId)
@@ -1844,10 +2584,11 @@ export class Matterbridge extends EventEmitter {
1844
2584
  case 0x1488:
1845
2585
  vendorName = '(ShortcutLabsFlic)';
1846
2586
  break;
1847
- case 65521:
2587
+ case 65521: // 0xFFF1
1848
2588
  vendorName = '(MatterTest)';
1849
2589
  break;
1850
2590
  }
1851
2591
  return vendorName;
1852
2592
  };
1853
2593
  }
2594
+ //# sourceMappingURL=matterbridge.js.map