matterbridge 3.4.2-dev-20251204-a39ec9e → 3.4.2

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