matterbridge 3.3.8-dev-20251115-ca5ff21 → 3.3.8

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