matterbridge 3.3.7-dev-20251109-a306ab9 → 3.3.7

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 (302) 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 +42 -0
  60. package/dist/devices/extractorHood.js.map +1 -0
  61. package/dist/devices/heatPump.d.ts +47 -0
  62. package/dist/devices/heatPump.d.ts.map +1 -0
  63. package/dist/devices/heatPump.js +50 -2
  64. package/dist/devices/heatPump.js.map +1 -0
  65. package/dist/devices/laundryDryer.d.ts +67 -0
  66. package/dist/devices/laundryDryer.d.ts.map +1 -0
  67. package/dist/devices/laundryDryer.js +62 -3
  68. package/dist/devices/laundryDryer.js.map +1 -0
  69. package/dist/devices/laundryWasher.d.ts +81 -0
  70. package/dist/devices/laundryWasher.d.ts.map +1 -0
  71. package/dist/devices/laundryWasher.js +70 -4
  72. package/dist/devices/laundryWasher.js.map +1 -0
  73. package/dist/devices/microwaveOven.d.ts +168 -0
  74. package/dist/devices/microwaveOven.d.ts.map +1 -0
  75. package/dist/devices/microwaveOven.js +88 -5
  76. package/dist/devices/microwaveOven.js.map +1 -0
  77. package/dist/devices/oven.d.ts +105 -0
  78. package/dist/devices/oven.d.ts.map +1 -0
  79. package/dist/devices/oven.js +85 -0
  80. package/dist/devices/oven.js.map +1 -0
  81. package/dist/devices/refrigerator.d.ts +118 -0
  82. package/dist/devices/refrigerator.d.ts.map +1 -0
  83. package/dist/devices/refrigerator.js +102 -0
  84. package/dist/devices/refrigerator.js.map +1 -0
  85. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  86. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  87. package/dist/devices/roboticVacuumCleaner.js +100 -9
  88. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  89. package/dist/devices/solarPower.d.ts +40 -0
  90. package/dist/devices/solarPower.d.ts.map +1 -0
  91. package/dist/devices/solarPower.js +38 -0
  92. package/dist/devices/solarPower.js.map +1 -0
  93. package/dist/devices/speaker.d.ts +87 -0
  94. package/dist/devices/speaker.d.ts.map +1 -0
  95. package/dist/devices/speaker.js +84 -0
  96. package/dist/devices/speaker.js.map +1 -0
  97. package/dist/devices/temperatureControl.d.ts +166 -0
  98. package/dist/devices/temperatureControl.d.ts.map +1 -0
  99. package/dist/devices/temperatureControl.js +24 -3
  100. package/dist/devices/temperatureControl.js.map +1 -0
  101. package/dist/devices/waterHeater.d.ts +111 -0
  102. package/dist/devices/waterHeater.d.ts.map +1 -0
  103. package/dist/devices/waterHeater.js +82 -2
  104. package/dist/devices/waterHeater.js.map +1 -0
  105. package/dist/dgram/coap.d.ts +205 -0
  106. package/dist/dgram/coap.d.ts.map +1 -0
  107. package/dist/dgram/coap.js +126 -13
  108. package/dist/dgram/coap.js.map +1 -0
  109. package/dist/dgram/dgram.d.ts +141 -0
  110. package/dist/dgram/dgram.d.ts.map +1 -0
  111. package/dist/dgram/dgram.js +114 -2
  112. package/dist/dgram/dgram.js.map +1 -0
  113. package/dist/dgram/mb_coap.d.ts +24 -0
  114. package/dist/dgram/mb_coap.d.ts.map +1 -0
  115. package/dist/dgram/mb_coap.js +41 -3
  116. package/dist/dgram/mb_coap.js.map +1 -0
  117. package/dist/dgram/mb_mdns.d.ts +24 -0
  118. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  119. package/dist/dgram/mb_mdns.js +80 -15
  120. package/dist/dgram/mb_mdns.js.map +1 -0
  121. package/dist/dgram/mdns.d.ts +290 -0
  122. package/dist/dgram/mdns.d.ts.map +1 -0
  123. package/dist/dgram/mdns.js +299 -137
  124. package/dist/dgram/mdns.js.map +1 -0
  125. package/dist/dgram/multicast.d.ts +67 -0
  126. package/dist/dgram/multicast.d.ts.map +1 -0
  127. package/dist/dgram/multicast.js +62 -1
  128. package/dist/dgram/multicast.js.map +1 -0
  129. package/dist/dgram/unicast.d.ts +56 -0
  130. package/dist/dgram/unicast.d.ts.map +1 -0
  131. package/dist/dgram/unicast.js +54 -0
  132. package/dist/dgram/unicast.js.map +1 -0
  133. package/dist/frontend.d.ts +238 -0
  134. package/dist/frontend.d.ts.map +1 -0
  135. package/dist/frontend.js +451 -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 +33 -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/logger/export.d.ts +2 -0
  150. package/dist/logger/export.d.ts.map +1 -0
  151. package/dist/logger/export.js +1 -0
  152. package/dist/logger/export.js.map +1 -0
  153. package/dist/matter/behaviors.d.ts +2 -0
  154. package/dist/matter/behaviors.d.ts.map +1 -0
  155. package/dist/matter/behaviors.js +2 -0
  156. package/dist/matter/behaviors.js.map +1 -0
  157. package/dist/matter/clusters.d.ts +2 -0
  158. package/dist/matter/clusters.d.ts.map +1 -0
  159. package/dist/matter/clusters.js +2 -0
  160. package/dist/matter/clusters.js.map +1 -0
  161. package/dist/matter/devices.d.ts +2 -0
  162. package/dist/matter/devices.d.ts.map +1 -0
  163. package/dist/matter/devices.js +2 -0
  164. package/dist/matter/devices.js.map +1 -0
  165. package/dist/matter/endpoints.d.ts +2 -0
  166. package/dist/matter/endpoints.d.ts.map +1 -0
  167. package/dist/matter/endpoints.js +2 -0
  168. package/dist/matter/endpoints.js.map +1 -0
  169. package/dist/matter/export.d.ts +5 -0
  170. package/dist/matter/export.d.ts.map +1 -0
  171. package/dist/matter/export.js +3 -0
  172. package/dist/matter/export.js.map +1 -0
  173. package/dist/matter/types.d.ts +3 -0
  174. package/dist/matter/types.d.ts.map +1 -0
  175. package/dist/matter/types.js +3 -0
  176. package/dist/matter/types.js.map +1 -0
  177. package/dist/matterbridge.d.ts +478 -0
  178. package/dist/matterbridge.d.ts.map +1 -0
  179. package/dist/matterbridge.js +828 -46
  180. package/dist/matterbridge.js.map +1 -0
  181. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  182. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  183. package/dist/matterbridgeAccessoryPlatform.js +37 -0
  184. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  185. package/dist/matterbridgeBehaviors.d.ts +2404 -0
  186. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  187. package/dist/matterbridgeBehaviors.js +68 -5
  188. package/dist/matterbridgeBehaviors.js.map +1 -0
  189. package/dist/matterbridgeDeviceTypes.d.ts +770 -0
  190. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  191. package/dist/matterbridgeDeviceTypes.js +638 -17
  192. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  193. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  194. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  195. package/dist/matterbridgeDynamicPlatform.js +37 -0
  196. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  197. package/dist/matterbridgeEndpoint.d.ts +1556 -0
  198. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  199. package/dist/matterbridgeEndpoint.js +1408 -52
  200. package/dist/matterbridgeEndpoint.js.map +1 -0
  201. package/dist/matterbridgeEndpointHelpers.d.ts +758 -0
  202. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  203. package/dist/matterbridgeEndpointHelpers.js +464 -19
  204. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  205. package/dist/matterbridgePlatform.d.ts +402 -0
  206. package/dist/matterbridgePlatform.d.ts.map +1 -0
  207. package/dist/matterbridgePlatform.js +341 -1
  208. package/dist/matterbridgePlatform.js.map +1 -0
  209. package/dist/matterbridgeTypes.d.ts +239 -0
  210. package/dist/matterbridgeTypes.d.ts.map +1 -0
  211. package/dist/matterbridgeTypes.js +26 -0
  212. package/dist/matterbridgeTypes.js.map +1 -0
  213. package/dist/pluginManager.d.ts +371 -0
  214. package/dist/pluginManager.d.ts.map +1 -0
  215. package/dist/pluginManager.js +339 -4
  216. package/dist/pluginManager.js.map +1 -0
  217. package/dist/shelly.d.ts +174 -0
  218. package/dist/shelly.d.ts.map +1 -0
  219. package/dist/shelly.js +168 -7
  220. package/dist/shelly.js.map +1 -0
  221. package/dist/storage/export.d.ts +2 -0
  222. package/dist/storage/export.d.ts.map +1 -0
  223. package/dist/storage/export.js +1 -0
  224. package/dist/storage/export.js.map +1 -0
  225. package/dist/update.d.ts +75 -0
  226. package/dist/update.d.ts.map +1 -0
  227. package/dist/update.js +69 -0
  228. package/dist/update.js.map +1 -0
  229. package/dist/utils/colorUtils.d.ts +101 -0
  230. package/dist/utils/colorUtils.d.ts.map +1 -0
  231. package/dist/utils/colorUtils.js +97 -2
  232. package/dist/utils/colorUtils.js.map +1 -0
  233. package/dist/utils/commandLine.d.ts +66 -0
  234. package/dist/utils/commandLine.d.ts.map +1 -0
  235. package/dist/utils/commandLine.js +60 -0
  236. package/dist/utils/commandLine.js.map +1 -0
  237. package/dist/utils/copyDirectory.d.ts +33 -0
  238. package/dist/utils/copyDirectory.d.ts.map +1 -0
  239. package/dist/utils/copyDirectory.js +38 -1
  240. package/dist/utils/copyDirectory.js.map +1 -0
  241. package/dist/utils/createDirectory.d.ts +34 -0
  242. package/dist/utils/createDirectory.d.ts.map +1 -0
  243. package/dist/utils/createDirectory.js +33 -0
  244. package/dist/utils/createDirectory.js.map +1 -0
  245. package/dist/utils/createZip.d.ts +39 -0
  246. package/dist/utils/createZip.d.ts.map +1 -0
  247. package/dist/utils/createZip.js +47 -2
  248. package/dist/utils/createZip.js.map +1 -0
  249. package/dist/utils/deepCopy.d.ts +32 -0
  250. package/dist/utils/deepCopy.d.ts.map +1 -0
  251. package/dist/utils/deepCopy.js +39 -0
  252. package/dist/utils/deepCopy.js.map +1 -0
  253. package/dist/utils/deepEqual.d.ts +54 -0
  254. package/dist/utils/deepEqual.d.ts.map +1 -0
  255. package/dist/utils/deepEqual.js +72 -1
  256. package/dist/utils/deepEqual.js.map +1 -0
  257. package/dist/utils/error.d.ts +44 -0
  258. package/dist/utils/error.d.ts.map +1 -0
  259. package/dist/utils/error.js +41 -0
  260. package/dist/utils/error.js.map +1 -0
  261. package/dist/utils/export.d.ts +13 -0
  262. package/dist/utils/export.d.ts.map +1 -0
  263. package/dist/utils/export.js +1 -0
  264. package/dist/utils/export.js.map +1 -0
  265. package/dist/utils/format.d.ts +53 -0
  266. package/dist/utils/format.d.ts.map +1 -0
  267. package/dist/utils/format.js +49 -0
  268. package/dist/utils/format.js.map +1 -0
  269. package/dist/utils/hex.d.ts +89 -0
  270. package/dist/utils/hex.d.ts.map +1 -0
  271. package/dist/utils/hex.js +124 -0
  272. package/dist/utils/hex.js.map +1 -0
  273. package/dist/utils/inspector.d.ts +87 -0
  274. package/dist/utils/inspector.d.ts.map +1 -0
  275. package/dist/utils/inspector.js +69 -1
  276. package/dist/utils/inspector.js.map +1 -0
  277. package/dist/utils/isvalid.d.ts +103 -0
  278. package/dist/utils/isvalid.d.ts.map +1 -0
  279. package/dist/utils/isvalid.js +101 -0
  280. package/dist/utils/isvalid.js.map +1 -0
  281. package/dist/utils/jestHelpers.d.ts +139 -0
  282. package/dist/utils/jestHelpers.d.ts.map +1 -0
  283. package/dist/utils/jestHelpers.js +153 -3
  284. package/dist/utils/jestHelpers.js.map +1 -0
  285. package/dist/utils/network.d.ts +101 -0
  286. package/dist/utils/network.d.ts.map +1 -0
  287. package/dist/utils/network.js +96 -5
  288. package/dist/utils/network.js.map +1 -0
  289. package/dist/utils/spawn.d.ts +35 -0
  290. package/dist/utils/spawn.d.ts.map +1 -0
  291. package/dist/utils/spawn.js +71 -0
  292. package/dist/utils/spawn.js.map +1 -0
  293. package/dist/utils/tracker.d.ts +108 -0
  294. package/dist/utils/tracker.d.ts.map +1 -0
  295. package/dist/utils/tracker.js +64 -1
  296. package/dist/utils/tracker.js.map +1 -0
  297. package/dist/utils/wait.d.ts +54 -0
  298. package/dist/utils/wait.d.ts.map +1 -0
  299. package/dist/utils/wait.js +60 -8
  300. package/dist/utils/wait.js.map +1 -0
  301. package/npm-shrinkwrap.json +2 -2
  302. 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,62 +247,131 @@ export class Matterbridge extends EventEmitter {
171
247
  }
172
248
  return Matterbridge.instance;
173
249
  }
250
+ /**
251
+ * Call cleanup() and dispose MdnsService. Will be removed since matter.js 0.15.6 dispose MdnsService.
252
+ *
253
+ * @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
254
+ * @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
255
+ *
256
+ * @deprecated This method is deprecated and is ONLY used for jest tests.
257
+ */
174
258
  async destroyInstance(timeout = 1000, pause = 250) {
175
259
  this.log.info(`Destroy instance...`);
260
+ // Save server nodes to close
261
+ /*
262
+ const servers: ServerNode<ServerNode.RootEndpoint>[] = [];
263
+ if (this.bridgeMode === 'bridge') {
264
+ if (this.serverNode) servers.push(this.serverNode);
265
+ }
266
+ if (this.bridgeMode === 'childbridge' && this.plugins !== undefined) {
267
+ for (const plugin of this.plugins.array()) {
268
+ if (plugin.serverNode) servers.push(plugin.serverNode);
269
+ }
270
+ }
271
+ if (this.devices !== undefined) {
272
+ for (const device of this.devices.array()) {
273
+ if (device.mode === 'server' && device.serverNode) servers.push(device.serverNode);
274
+ }
275
+ }
276
+ */
277
+ // Let any already‐queued microtasks run first
278
+ // await Promise.resolve();
279
+ // Wait for the cleanup to finish
280
+ // await wait(pause, 'destroyInstance start', true);
281
+ // Cleanup
176
282
  await this.cleanup('destroying instance...', false, timeout);
283
+ // Close servers mdns service
284
+ /*
285
+ this.log.info(`Dispose ${servers.length} MdnsService...`);
286
+ for (const server of servers) {
287
+ // await server.env.get(MdnsService)[Symbol.asyncDispose]();
288
+ this.log.info(`Closed ${server.id} MdnsService`);
289
+ }
290
+ */
291
+ // Let any already‐queued microtasks run first
292
+ // await Promise.resolve();
293
+ // Wait for the cleanup to finish
177
294
  if (pause)
178
295
  await wait(pause, 'destroyInstance stop', true);
179
296
  }
297
+ /**
298
+ * Initializes the Matterbridge application.
299
+ *
300
+ * @remarks
301
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
302
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
303
+ * node version, registers signal handlers, initializes storage, and parses the command line.
304
+ *
305
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
306
+ */
180
307
  async initialize() {
308
+ // for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
309
+ // Emit the initialize_started event
181
310
  this.emit('initialize_started');
311
+ // Set the restart mode
182
312
  if (hasParameter('service'))
183
313
  this.restartMode = 'service';
184
314
  if (hasParameter('docker'))
185
315
  this.restartMode = 'docker';
316
+ // Set the matterbridge home directory
186
317
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
187
318
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
319
+ // Set the matterbridge directory
188
320
  this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
189
321
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
190
322
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
191
323
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
324
+ // Set the matterbridge plugin directory
192
325
  this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
193
326
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
327
+ // Set the matterbridge cert directory
194
328
  this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
195
329
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
330
+ // Set the matterbridge root directory
196
331
  const { fileURLToPath } = await import('node:url');
197
332
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
198
- this.rootDirectory = path.resolve(currentFileDirectory, '../');
333
+ this.rootDirectory = path.resolve(currentFileDirectory, '../'); // Adjust the path for dist directory
334
+ // Setup the matter environment with default values
199
335
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
200
336
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
201
337
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
202
338
  this.environment.vars.set('runtime.signals', false);
203
339
  this.environment.vars.set('runtime.exitcode', false);
340
+ // Register process handlers
204
341
  this.registerProcessHandlers();
342
+ // Initialize nodeStorage and nodeContext
205
343
  try {
206
344
  this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
207
345
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
208
346
  this.log.debug('Creating node storage context for matterbridge');
209
347
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
348
+ // TODO: Remove this code when node-persist-manager is updated
349
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
350
  const keys = (await this.nodeStorage?.storage.keys());
211
351
  for (const key of keys) {
212
352
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
353
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
354
  await this.nodeStorage?.storage.get(key);
214
355
  }
215
356
  const storages = await this.nodeStorage.getStorageNames();
216
357
  for (const storage of storages) {
217
358
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
218
359
  const nodeContext = await this.nodeStorage?.createStorage(storage);
360
+ // TODO: Remove this code when node-persist-manager is updated
361
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
362
  const keys = (await nodeContext?.storage.keys());
220
363
  keys.forEach(async (key) => {
221
364
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
222
365
  await nodeContext?.get(key);
223
366
  });
224
367
  }
368
+ // Creating a backup of the node storage since it is not corrupted
225
369
  this.log.debug('Creating node storage backup...');
226
370
  await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
227
371
  this.log.debug('Created node storage backup');
228
372
  }
229
373
  catch (error) {
374
+ // Restoring the backup of the node storage since it is corrupted
230
375
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
231
376
  if (hasParameter('norestore')) {
232
377
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -240,14 +385,19 @@ export class Matterbridge extends EventEmitter {
240
385
  if (!this.nodeStorage || !this.nodeContext) {
241
386
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
242
387
  }
388
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
243
389
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
390
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
244
391
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
392
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
245
393
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
394
+ // Certificate management
246
395
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
247
396
  try {
248
397
  await fs.promises.access(pairingFilePath, fs.constants.R_OK);
249
398
  const pairingFileContent = await fs.promises.readFile(pairingFilePath, 'utf8');
250
399
  const pairingFileJson = JSON.parse(pairingFileContent);
400
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
251
401
  if (isValidNumber(pairingFileJson.vendorId)) {
252
402
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
253
403
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -276,11 +426,13 @@ export class Matterbridge extends EventEmitter {
276
426
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
277
427
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
278
428
  }
429
+ // Override the passcode and discriminator if they are present in the pairing file
279
430
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
280
431
  this.passcode = pairingFileJson.passcode;
281
432
  this.discriminator = pairingFileJson.discriminator;
282
433
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
283
434
  }
435
+ // Set the certification for matter.js if it is present in the pairing file
284
436
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
285
437
  const { hexToBuffer } = await import('./utils/hex.js');
286
438
  this.certification = {
@@ -295,40 +447,43 @@ export class Matterbridge extends EventEmitter {
295
447
  catch (error) {
296
448
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
297
449
  }
450
+ // Store the passcode, discriminator and port in the node context
298
451
  await this.nodeContext.set('matterport', this.port);
299
452
  await this.nodeContext.set('matterpasscode', this.passcode);
300
453
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
301
454
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
455
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
302
456
  if (hasParameter('logger')) {
303
457
  const level = getParameter('logger');
304
458
  if (level === 'debug') {
305
- this.log.logLevel = "debug";
459
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
306
460
  }
307
461
  else if (level === 'info') {
308
- this.log.logLevel = "info";
462
+ this.log.logLevel = "info" /* LogLevel.INFO */;
309
463
  }
310
464
  else if (level === 'notice') {
311
- this.log.logLevel = "notice";
465
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
312
466
  }
313
467
  else if (level === 'warn') {
314
- this.log.logLevel = "warn";
468
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
315
469
  }
316
470
  else if (level === 'error') {
317
- this.log.logLevel = "error";
471
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
318
472
  }
319
473
  else if (level === 'fatal') {
320
- this.log.logLevel = "fatal";
474
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
321
475
  }
322
476
  else {
323
477
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
324
- this.log.logLevel = "info";
478
+ this.log.logLevel = "info" /* LogLevel.INFO */;
325
479
  }
326
480
  }
327
481
  else {
328
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
482
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
329
483
  }
330
484
  this.frontend.logLevel = this.log.logLevel;
331
485
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
486
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
332
487
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
333
488
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
334
489
  this.fileLogger = true;
@@ -337,6 +492,7 @@ export class Matterbridge extends EventEmitter {
337
492
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
338
493
  if (this.profile !== undefined)
339
494
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
495
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
340
496
  if (hasParameter('matterlogger')) {
341
497
  const level = getParameter('matterlogger');
342
498
  if (level === 'debug') {
@@ -366,11 +522,13 @@ export class Matterbridge extends EventEmitter {
366
522
  Logger.level = (await this.nodeContext.get('matterLogLevel', this.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
367
523
  }
368
524
  Logger.format = MatterLogFormat.ANSI;
525
+ // Create the logger for matter.js with file logging (context: matterFileLog)
369
526
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
370
527
  this.matterFileLogger = true;
371
528
  }
372
529
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
373
530
  this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterFileLogger}.`);
531
+ // Log network interfaces
374
532
  const networkInterfaces = os.networkInterfaces();
375
533
  const availableAddresses = Object.entries(networkInterfaces);
376
534
  const availableInterfaceNames = Object.keys(networkInterfaces);
@@ -383,6 +541,7 @@ export class Matterbridge extends EventEmitter {
383
541
  });
384
542
  }
385
543
  }
544
+ // Set the interface to use for matter server node mdnsInterface
386
545
  if (hasParameter('mdnsinterface')) {
387
546
  this.mdnsInterface = getParameter('mdnsinterface');
388
547
  }
@@ -391,6 +550,7 @@ export class Matterbridge extends EventEmitter {
391
550
  if (this.mdnsInterface === '')
392
551
  this.mdnsInterface = undefined;
393
552
  }
553
+ // Validate mdnsInterface
394
554
  if (this.mdnsInterface) {
395
555
  if (!availableInterfaceNames.includes(this.mdnsInterface)) {
396
556
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
@@ -403,6 +563,7 @@ export class Matterbridge extends EventEmitter {
403
563
  }
404
564
  if (this.mdnsInterface)
405
565
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
566
+ // Set the listeningAddressIpv4 for the matter commissioning server
406
567
  if (hasParameter('ipv4address')) {
407
568
  this.ipv4Address = getParameter('ipv4address');
408
569
  }
@@ -411,6 +572,7 @@ export class Matterbridge extends EventEmitter {
411
572
  if (this.ipv4Address === '')
412
573
  this.ipv4Address = undefined;
413
574
  }
575
+ // Validate ipv4address
414
576
  if (this.ipv4Address) {
415
577
  let isValid = false;
416
578
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -426,6 +588,7 @@ export class Matterbridge extends EventEmitter {
426
588
  await this.nodeContext.remove('matteripv4address');
427
589
  }
428
590
  }
591
+ // Set the listeningAddressIpv6 for the matter commissioning server
429
592
  if (hasParameter('ipv6address')) {
430
593
  this.ipv6Address = getParameter('ipv6address');
431
594
  }
@@ -434,6 +597,7 @@ export class Matterbridge extends EventEmitter {
434
597
  if (this.ipv6Address === '')
435
598
  this.ipv6Address = undefined;
436
599
  }
600
+ // Validate ipv6address
437
601
  if (this.ipv6Address) {
438
602
  let isValid = false;
439
603
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -442,6 +606,7 @@ export class Matterbridge extends EventEmitter {
442
606
  isValid = true;
443
607
  break;
444
608
  }
609
+ /* istanbul ignore next */
445
610
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
446
611
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
447
612
  isValid = true;
@@ -454,6 +619,7 @@ export class Matterbridge extends EventEmitter {
454
619
  await this.nodeContext.remove('matteripv6address');
455
620
  }
456
621
  }
622
+ // Initialize the virtual mode
457
623
  if (hasParameter('novirtual')) {
458
624
  this.virtualMode = 'disabled';
459
625
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -462,10 +628,15 @@ export class Matterbridge extends EventEmitter {
462
628
  this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
463
629
  }
464
630
  this.log.debug(`Virtual mode ${this.virtualMode}.`);
631
+ // Initialize PluginManager
465
632
  this.plugins.logLevel = this.log.logLevel;
466
633
  await this.plugins.loadFromStorage();
634
+ // Initialize DeviceManager
467
635
  this.devices.logLevel = this.log.logLevel;
636
+ // Get the plugins from node storage and create the plugins node storage contexts
468
637
  for (const plugin of this.plugins) {
638
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
639
+ // We don't do this when the add and other shutdown parameters are set because we shut down the process after adding the plugin
469
640
  if (!fs.existsSync(plugin.path) && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
470
641
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm...`);
471
642
  try {
@@ -496,6 +667,7 @@ export class Matterbridge extends EventEmitter {
496
667
  await plugin.nodeContext.set('description', plugin.description);
497
668
  await plugin.nodeContext.set('author', plugin.author);
498
669
  }
670
+ // Log system info and create .matterbridge directory
499
671
  await this.logNodeAndSystemInfo();
500
672
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
501
673
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -503,6 +675,7 @@ export class Matterbridge extends EventEmitter {
503
675
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
504
676
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
505
677
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
678
+ // Check node version and throw error
506
679
  const minNodeVersion = 20;
507
680
  const nodeVersion = process.versions.node;
508
681
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -510,10 +683,18 @@ export class Matterbridge extends EventEmitter {
510
683
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
511
684
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
512
685
  }
686
+ // Parse command line
513
687
  await this.parseCommandLine();
688
+ // Emit the initialize_completed event
514
689
  this.emit('initialize_completed');
515
690
  this.initialized = true;
516
691
  }
692
+ /**
693
+ * Parses the command line arguments and performs the corresponding actions.
694
+ *
695
+ * @private
696
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
697
+ */
517
698
  async parseCommandLine() {
518
699
  if (hasParameter('list')) {
519
700
  this.log.info(`│ Registered plugins (${this.plugins.length})`);
@@ -529,6 +710,19 @@ export class Matterbridge extends EventEmitter {
529
710
  }
530
711
  index++;
531
712
  }
713
+ /*
714
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
715
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
716
+ serializedRegisteredDevices?.forEach((device, index) => {
717
+ if (index !== serializedRegisteredDevices.length - 1) {
718
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
719
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
720
+ } else {
721
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
722
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
723
+ }
724
+ });
725
+ */
532
726
  this.shutdown = true;
533
727
  return;
534
728
  }
@@ -578,8 +772,10 @@ export class Matterbridge extends EventEmitter {
578
772
  this.shutdown = true;
579
773
  return;
580
774
  }
775
+ // Initialize frontend
581
776
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
582
777
  await this.frontend.start(getIntParameter('frontend'));
778
+ // Start the matter storage and create the matterbridge context
583
779
  try {
584
780
  await this.startMatterStorage();
585
781
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -593,18 +789,21 @@ export class Matterbridge extends EventEmitter {
593
789
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
594
790
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
595
791
  }
792
+ // Clear the matterbridge context if the reset parameter is set (bridge mode)
596
793
  if (hasParameter('reset') && getParameter('reset') === undefined) {
597
794
  this.initialized = true;
598
795
  await this.shutdownProcessAndReset();
599
796
  this.shutdown = true;
600
797
  return;
601
798
  }
799
+ // Clear matterbridge plugin context if the reset parameter is set (childbridge mode)
602
800
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
603
801
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
604
802
  const plugin = this.plugins.get(getParameter('reset'));
605
803
  if (plugin) {
606
804
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
607
805
  if (!matterStorageManager) {
806
+ /* istanbul ignore next */
608
807
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
609
808
  }
610
809
  else {
@@ -623,35 +822,42 @@ export class Matterbridge extends EventEmitter {
623
822
  this.shutdown = true;
624
823
  return;
625
824
  }
825
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
626
826
  clearTimeout(this.checkUpdateTimeout);
627
827
  this.checkUpdateTimeout = setTimeout(async () => {
628
828
  const { checkUpdates } = await import('./update.js');
629
829
  checkUpdates(this);
630
830
  }, 30 * 1000).unref();
831
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
631
832
  clearInterval(this.checkUpdateInterval);
632
833
  this.checkUpdateInterval = setInterval(async () => {
633
834
  const { checkUpdates } = await import('./update.js');
634
835
  checkUpdates(this);
635
836
  }, 12 * 60 * 60 * 1000).unref();
837
+ // Start the matterbridge in mode test
636
838
  if (hasParameter('test')) {
637
839
  this.bridgeMode = 'bridge';
638
840
  return;
639
841
  }
842
+ // Start the matterbridge in mode controller
640
843
  if (hasParameter('controller')) {
641
844
  this.bridgeMode = 'controller';
642
845
  await this.startController();
643
846
  return;
644
847
  }
848
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
645
849
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
646
850
  this.log.info('Setting default matterbridge start mode to bridge');
647
851
  await this.nodeContext?.set('bridgeMode', 'bridge');
648
852
  }
853
+ // Start matterbridge in bridge mode
649
854
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
650
855
  this.bridgeMode = 'bridge';
651
856
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
652
857
  await this.startBridge();
653
858
  return;
654
859
  }
860
+ // Start matterbridge in childbridge mode
655
861
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
656
862
  this.bridgeMode = 'childbridge';
657
863
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -659,10 +865,22 @@ export class Matterbridge extends EventEmitter {
659
865
  return;
660
866
  }
661
867
  }
868
+ /**
869
+ * Asynchronously loads and starts the registered plugins.
870
+ *
871
+ * This method is responsible for initializing and starting all enabled plugins.
872
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
873
+ *
874
+ * @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving.
875
+ * @param {boolean} [start] - If true, the method will start the plugins after loading them.
876
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
877
+ */
662
878
  async startPlugins(wait = false, start = true) {
879
+ // Check, load and start the plugins
663
880
  for (const plugin of this.plugins) {
664
881
  plugin.configJson = await this.plugins.loadConfig(plugin);
665
882
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
883
+ // Check if the plugin is available
666
884
  if (!(await this.plugins.resolve(plugin.path))) {
667
885
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
668
886
  plugin.enabled = false;
@@ -682,10 +900,16 @@ export class Matterbridge extends EventEmitter {
682
900
  if (wait)
683
901
  await this.plugins.load(plugin, start, 'Matterbridge is starting');
684
902
  else
685
- this.plugins.load(plugin, start, 'Matterbridge is starting');
903
+ this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
686
904
  }
687
905
  this.frontend.wssSendRefreshRequired('plugins');
688
906
  }
907
+ /**
908
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
909
+ * - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
910
+ * - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
911
+ * - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
912
+ */
689
913
  registerProcessHandlers() {
690
914
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
691
915
  process.removeAllListeners('uncaughtException');
@@ -712,6 +936,9 @@ export class Matterbridge extends EventEmitter {
712
936
  };
713
937
  process.on('SIGTERM', this.sigtermHandler);
714
938
  }
939
+ /**
940
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
941
+ */
715
942
  deregisterProcessHandlers() {
716
943
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
717
944
  if (this.exceptionHandler)
@@ -728,13 +955,18 @@ export class Matterbridge extends EventEmitter {
728
955
  process.off('SIGTERM', this.sigtermHandler);
729
956
  this.sigtermHandler = undefined;
730
957
  }
958
+ /**
959
+ * Logs the node and system information.
960
+ */
731
961
  async logNodeAndSystemInfo() {
962
+ // IP address information
732
963
  const networkInterfaces = os.networkInterfaces();
733
964
  this.systemInformation.interfaceName = '';
734
965
  this.systemInformation.ipv4Address = '';
735
966
  this.systemInformation.ipv6Address = '';
736
967
  this.systemInformation.macAddress = '';
737
968
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
969
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
738
970
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
739
971
  continue;
740
972
  if (!interfaceDetails) {
@@ -760,16 +992,18 @@ export class Matterbridge extends EventEmitter {
760
992
  break;
761
993
  }
762
994
  }
995
+ // Node information
763
996
  this.systemInformation.nodeVersion = process.versions.node;
764
997
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
765
998
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
766
999
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1000
+ // Host system information
767
1001
  this.systemInformation.hostname = os.hostname();
768
1002
  this.systemInformation.user = os.userInfo().username;
769
- this.systemInformation.osType = os.type();
770
- this.systemInformation.osRelease = os.release();
771
- this.systemInformation.osPlatform = os.platform();
772
- this.systemInformation.osArch = os.arch();
1003
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1004
+ this.systemInformation.osRelease = os.release(); // Kernel version
1005
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1006
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
773
1007
  this.systemInformation.totalMemory = formatBytes(os.totalmem());
774
1008
  this.systemInformation.freeMemory = formatBytes(os.freemem());
775
1009
  this.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -779,6 +1013,7 @@ export class Matterbridge extends EventEmitter {
779
1013
  this.systemInformation.rss = formatBytes(process.memoryUsage().rss);
780
1014
  this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
781
1015
  this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
1016
+ // Log the system information
782
1017
  this.log.debug('Host System Information:');
783
1018
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
784
1019
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -798,14 +1033,17 @@ export class Matterbridge extends EventEmitter {
798
1033
  this.log.debug(`- RSS: ${this.systemInformation.rss}`);
799
1034
  this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
800
1035
  this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
1036
+ // Log directories
801
1037
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
802
1038
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
803
1039
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
804
1040
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
805
1041
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1042
+ // Global node_modules directory
806
1043
  if (this.nodeContext)
807
1044
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
808
1045
  if (this.globalModulesDirectory === '') {
1046
+ // First run of Matterbridge so the node storage is empty
809
1047
  this.log.debug(`Getting global node_modules directory...`);
810
1048
  try {
811
1049
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -818,6 +1056,7 @@ export class Matterbridge extends EventEmitter {
818
1056
  }
819
1057
  }
820
1058
  else {
1059
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
821
1060
  this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
822
1061
  try {
823
1062
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -829,25 +1068,37 @@ export class Matterbridge extends EventEmitter {
829
1068
  this.log.error(`Error checking global node_modules directory: ${error}`);
830
1069
  }
831
1070
  }
1071
+ // Matterbridge version
832
1072
  this.log.debug(`Reading matterbridge package.json...`);
833
1073
  const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
834
1074
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
835
1075
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1076
+ // Matterbridge latest version (will be set in the checkUpdate function)
836
1077
  if (this.nodeContext)
837
1078
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
838
1079
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1080
+ // Matterbridge dev version (will be set in the checkUpdate function)
839
1081
  if (this.nodeContext)
840
1082
  this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
841
1083
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1084
+ // Frontend version
842
1085
  this.log.debug(`Reading frontend package.json...`);
843
1086
  const frontendPackageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
844
1087
  this.frontendVersion = frontendPackageJson.version;
845
1088
  this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
1089
+ // Current working directory
846
1090
  const currentDir = process.cwd();
847
1091
  this.log.debug(`Current Working Directory: ${currentDir}`);
1092
+ // Command line arguments (excluding 'node' and the script name)
848
1093
  const cmdArgs = process.argv.slice(2).join(' ');
849
1094
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
850
1095
  }
1096
+ /**
1097
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
1098
+ *
1099
+ * @param {LogLevel} logLevel The logger logLevel to set.
1100
+ * @returns {Promise<LogLevel>} A promise that resolves when the logLevel has been set.
1101
+ */
851
1102
  async setLogLevel(logLevel) {
852
1103
  this.log.logLevel = logLevel;
853
1104
  this.frontend.logLevel = logLevel;
@@ -857,58 +1108,87 @@ export class Matterbridge extends EventEmitter {
857
1108
  for (const plugin of this.plugins) {
858
1109
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
859
1110
  continue;
860
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
861
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
862
- }
863
- let callbackLogLevel = "notice";
864
- if (logLevel === "info" || Logger.level === MatterLogLevel.INFO)
865
- callbackLogLevel = "info";
866
- if (logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG)
867
- callbackLogLevel = "debug";
1111
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel;
1112
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel);
1113
+ }
1114
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1115
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1116
+ if (logLevel === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1117
+ callbackLogLevel = "info" /* LogLevel.INFO */;
1118
+ if (logLevel === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1119
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
868
1120
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
869
1121
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
870
1122
  return logLevel;
871
1123
  }
1124
+ /**
1125
+ * Get the current logger logLevel.
1126
+ *
1127
+ * @returns {LogLevel} The current logger logLevel.
1128
+ */
872
1129
  getLogLevel() {
873
1130
  return this.log.logLevel;
874
1131
  }
1132
+ /**
1133
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1134
+ * It also logs to file (matter.log) if fileLogger is true.
1135
+ *
1136
+ * @param {boolean} fileLogger - Whether to log to file or not.
1137
+ * @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
1138
+ */
875
1139
  createDestinationMatterLogger(fileLogger) {
876
- this.matterLog.logNameColor = '\x1b[34m';
1140
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
877
1141
  if (fileLogger) {
878
1142
  this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
879
1143
  }
880
1144
  return (text, message) => {
1145
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
881
1146
  const logger = text.slice(44, 44 + 20).trim();
882
1147
  const msg = text.slice(65);
883
1148
  this.matterLog.logName = logger;
884
1149
  switch (message.level) {
885
1150
  case MatterLogLevel.DEBUG:
886
- this.matterLog.log("debug", msg);
1151
+ this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
887
1152
  break;
888
1153
  case MatterLogLevel.INFO:
889
- this.matterLog.log("info", msg);
1154
+ this.matterLog.log("info" /* LogLevel.INFO */, msg);
890
1155
  break;
891
1156
  case MatterLogLevel.NOTICE:
892
- this.matterLog.log("notice", msg);
1157
+ this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
893
1158
  break;
894
1159
  case MatterLogLevel.WARN:
895
- this.matterLog.log("warn", msg);
1160
+ this.matterLog.log("warn" /* LogLevel.WARN */, msg);
896
1161
  break;
897
1162
  case MatterLogLevel.ERROR:
898
- this.matterLog.log("error", msg);
1163
+ this.matterLog.log("error" /* LogLevel.ERROR */, msg);
899
1164
  break;
900
1165
  case MatterLogLevel.FATAL:
901
- this.matterLog.log("fatal", msg);
1166
+ this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
902
1167
  break;
903
1168
  }
904
1169
  };
905
1170
  }
1171
+ /**
1172
+ * Restarts the process by exiting the current instance and loading a new instance (/api/restart).
1173
+ *
1174
+ * @returns {Promise<void>} A promise that resolves when the restart is completed.
1175
+ */
906
1176
  async restartProcess() {
907
1177
  await this.cleanup('restarting...', true);
908
1178
  }
1179
+ /**
1180
+ * Shut down the process (/api/shutdown).
1181
+ *
1182
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1183
+ */
909
1184
  async shutdownProcess() {
910
1185
  await this.cleanup('shutting down...', false);
911
1186
  }
1187
+ /**
1188
+ * Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
1189
+ *
1190
+ * @returns {Promise<void>} A promise that resolves when the update is completed.
1191
+ */
912
1192
  async updateProcess() {
913
1193
  this.log.info('Updating matterbridge...');
914
1194
  try {
@@ -922,6 +1202,13 @@ export class Matterbridge extends EventEmitter {
922
1202
  this.frontend.wssSendRestartRequired();
923
1203
  await this.cleanup('updating...', false);
924
1204
  }
1205
+ /**
1206
+ * Unregister all devices and shut down the process (/api/unregister).
1207
+ *
1208
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1209
+ *
1210
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1211
+ */
925
1212
  async unregisterAndShutdownProcess(timeout = 1000) {
926
1213
  this.log.info('Unregistering all devices and shutting down...');
927
1214
  for (const plugin of this.plugins.array()) {
@@ -933,46 +1220,71 @@ export class Matterbridge extends EventEmitter {
933
1220
  await this.removeAllBridgedEndpoints(plugin.name, 100);
934
1221
  }
935
1222
  this.log.debug('Waiting for the MessageExchange to finish...');
936
- await wait(timeout);
1223
+ await wait(timeout); // Wait for MessageExchange to finish
937
1224
  this.log.debug('Cleaning up and shutting down...');
938
1225
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
939
1226
  }
1227
+ /**
1228
+ * Reset commissioning and shut down the process (/api/reset).
1229
+ *
1230
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1231
+ */
940
1232
  async shutdownProcessAndReset() {
941
1233
  await this.cleanup('shutting down with reset...', false);
942
1234
  }
1235
+ /**
1236
+ * Factory reset and shut down the process (/api/factory-reset).
1237
+ *
1238
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1239
+ */
943
1240
  async shutdownProcessAndFactoryReset() {
944
1241
  await this.cleanup('shutting down with factory reset...', false);
945
1242
  }
1243
+ /**
1244
+ * Cleans up the Matterbridge instance.
1245
+ *
1246
+ * @param {string} message - The cleanup message.
1247
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1248
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1249
+ *
1250
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1251
+ */
946
1252
  async cleanup(message, restart = false, timeout = 1000) {
947
1253
  if (this.initialized && !this.hasCleanupStarted) {
948
1254
  this.emit('cleanup_started');
949
1255
  this.hasCleanupStarted = true;
950
1256
  this.log.info(message);
1257
+ // Clear the start matter interval
951
1258
  if (this.startMatterInterval) {
952
1259
  clearInterval(this.startMatterInterval);
953
1260
  this.startMatterInterval = undefined;
954
1261
  this.log.debug('Start matter interval cleared');
955
1262
  }
1263
+ // Clear the check update timeout
956
1264
  if (this.checkUpdateTimeout) {
957
1265
  clearTimeout(this.checkUpdateTimeout);
958
1266
  this.checkUpdateTimeout = undefined;
959
1267
  this.log.debug('Check update timeout cleared');
960
1268
  }
1269
+ // Clear the check update interval
961
1270
  if (this.checkUpdateInterval) {
962
1271
  clearInterval(this.checkUpdateInterval);
963
1272
  this.checkUpdateInterval = undefined;
964
1273
  this.log.debug('Check update interval cleared');
965
1274
  }
1275
+ // Clear the configure timeout
966
1276
  if (this.configureTimeout) {
967
1277
  clearTimeout(this.configureTimeout);
968
1278
  this.configureTimeout = undefined;
969
1279
  this.log.debug('Matterbridge configure timeout cleared');
970
1280
  }
1281
+ // Clear the reachability timeout
971
1282
  if (this.reachabilityTimeout) {
972
1283
  clearTimeout(this.reachabilityTimeout);
973
1284
  this.reachabilityTimeout = undefined;
974
1285
  this.log.debug('Matterbridge reachability timeout cleared');
975
1286
  }
1287
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
976
1288
  for (const plugin of this.plugins) {
977
1289
  if (!plugin.enabled || plugin.error)
978
1290
  continue;
@@ -983,6 +1295,7 @@ export class Matterbridge extends EventEmitter {
983
1295
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
984
1296
  }
985
1297
  }
1298
+ // Stop matter server nodes
986
1299
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
987
1300
  if (timeout > 0) {
988
1301
  this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
@@ -1009,6 +1322,7 @@ export class Matterbridge extends EventEmitter {
1009
1322
  }
1010
1323
  }
1011
1324
  this.log.notice('Stopped matter server nodes');
1325
+ // Matter commisioning reset
1012
1326
  if (message === 'shutting down with reset...') {
1013
1327
  this.log.info('Resetting Matterbridge commissioning information...');
1014
1328
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1018,6 +1332,7 @@ export class Matterbridge extends EventEmitter {
1018
1332
  await this.matterbridgeContext?.clearAll();
1019
1333
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1020
1334
  }
1335
+ // Unregister all devices
1021
1336
  if (message === 'unregistered all devices and shutting down...') {
1022
1337
  if (this.bridgeMode === 'bridge') {
1023
1338
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1035,16 +1350,35 @@ export class Matterbridge extends EventEmitter {
1035
1350
  }
1036
1351
  this.log.info('Matter storage reset done!');
1037
1352
  }
1353
+ // Stop matter storage
1038
1354
  await this.stopMatterStorage();
1355
+ // Stop the frontend
1039
1356
  await this.frontend.stop();
1040
1357
  this.frontend.destroy();
1358
+ // Close PluginManager and DeviceManager
1041
1359
  this.plugins.destroy();
1042
1360
  this.devices.destroy();
1361
+ // Stop thread messaging server
1043
1362
  this.server.close();
1363
+ // Close the matterbridge node storage and context
1044
1364
  if (this.nodeStorage && this.nodeContext) {
1365
+ /*
1366
+ TODO: Implement serialization of registered devices
1367
+ this.log.info('Saving registered devices...');
1368
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1369
+ this.devices.forEach(async (device) => {
1370
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1371
+ this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1372
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1373
+ });
1374
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1375
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1376
+ */
1377
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1045
1378
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1046
1379
  await this.nodeContext.close();
1047
1380
  this.nodeContext = undefined;
1381
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1048
1382
  for (const plugin of this.plugins) {
1049
1383
  if (plugin.nodeContext) {
1050
1384
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1061,8 +1395,10 @@ export class Matterbridge extends EventEmitter {
1061
1395
  }
1062
1396
  this.plugins.clear();
1063
1397
  this.devices.clear();
1398
+ // Factory reset
1064
1399
  if (message === 'shutting down with factory reset...') {
1065
1400
  try {
1401
+ // Delete matter storage directory with its subdirectories and backup
1066
1402
  const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
1067
1403
  this.log.info(`Removing matter storage directory: ${dir}`);
1068
1404
  await fs.promises.rm(dir, { recursive: true });
@@ -1071,11 +1407,13 @@ export class Matterbridge extends EventEmitter {
1071
1407
  await fs.promises.rm(backup, { recursive: true });
1072
1408
  }
1073
1409
  catch (error) {
1410
+ // istanbul ignore next if
1074
1411
  if (error instanceof Error && error.code !== 'ENOENT') {
1075
1412
  this.log.error(`Error removing matter storage directory: ${error}`);
1076
1413
  }
1077
1414
  }
1078
1415
  try {
1416
+ // Delete matterbridge storage directory with its subdirectories and backup
1079
1417
  const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
1080
1418
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1081
1419
  await fs.promises.rm(dir, { recursive: true });
@@ -1084,18 +1422,20 @@ export class Matterbridge extends EventEmitter {
1084
1422
  await fs.promises.rm(backup, { recursive: true });
1085
1423
  }
1086
1424
  catch (error) {
1425
+ // istanbul ignore next if
1087
1426
  if (error instanceof Error && error.code !== 'ENOENT') {
1088
1427
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1089
1428
  }
1090
1429
  }
1091
1430
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1092
1431
  }
1432
+ // Deregisters the process handlers
1093
1433
  this.deregisterProcessHandlers();
1094
1434
  if (restart) {
1095
1435
  if (message === 'updating...') {
1096
1436
  this.log.info('Cleanup completed. Updating...');
1097
1437
  Matterbridge.instance = undefined;
1098
- this.emit('update');
1438
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1099
1439
  }
1100
1440
  else if (message === 'restarting...') {
1101
1441
  this.log.info('Cleanup completed. Restarting...');
@@ -1124,7 +1464,14 @@ export class Matterbridge extends EventEmitter {
1124
1464
  this.log.debug('Cleanup already started...');
1125
1465
  }
1126
1466
  }
1467
+ /**
1468
+ * Starts the Matterbridge in bridge mode.
1469
+ *
1470
+ * @private
1471
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1472
+ */
1127
1473
  async startBridge() {
1474
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1128
1475
  if (!this.matterStorageManager)
1129
1476
  throw new Error('No storage manager initialized');
1130
1477
  if (!this.matterbridgeContext)
@@ -1163,13 +1510,16 @@ export class Matterbridge extends EventEmitter {
1163
1510
  clearInterval(this.startMatterInterval);
1164
1511
  this.startMatterInterval = undefined;
1165
1512
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1166
- this.startServerNode(this.serverNode);
1513
+ // Start the Matter server node
1514
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1515
+ // Start the Matter server node of single devices in mode 'server'
1167
1516
  for (const device of this.devices.array()) {
1168
1517
  if (device.mode === 'server' && device.serverNode) {
1169
1518
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1170
- this.startServerNode(device.serverNode);
1519
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1171
1520
  }
1172
1521
  }
1522
+ // Configure the plugins
1173
1523
  this.configureTimeout = setTimeout(async () => {
1174
1524
  for (const plugin of this.plugins.array()) {
1175
1525
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1187,28 +1537,40 @@ export class Matterbridge extends EventEmitter {
1187
1537
  }
1188
1538
  this.frontend.wssSendRefreshRequired('plugins');
1189
1539
  }, 30 * 1000).unref();
1540
+ // Setting reachability to true
1190
1541
  this.reachabilityTimeout = setTimeout(() => {
1191
1542
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1192
1543
  if (this.aggregatorNode)
1193
1544
  this.setAggregatorReachability(this.aggregatorNode, true);
1194
1545
  }, 60 * 1000).unref();
1546
+ // Logger.get('LogServerNode').info(this.serverNode);
1195
1547
  this.emit('bridge_started');
1196
1548
  this.log.notice('Matterbridge bridge started successfully');
1197
1549
  this.frontend.wssSendRefreshRequired('settings');
1198
1550
  this.frontend.wssSendRefreshRequired('plugins');
1199
1551
  }, this.startMatterIntervalMs);
1200
1552
  }
1553
+ /**
1554
+ * Starts the Matterbridge in childbridge mode.
1555
+ *
1556
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1557
+ *
1558
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1559
+ */
1201
1560
  async startChildbridge(delay = 1000) {
1202
1561
  if (!this.matterStorageManager)
1203
1562
  throw new Error('No storage manager initialized');
1563
+ // Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
1204
1564
  this.log.debug('Loading all plugins in childbridge mode...');
1205
1565
  await this.startPlugins(true, false);
1566
+ // Create server nodes for DynamicPlatform plugins and start all plugins in the background
1206
1567
  this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
1207
1568
  for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
1208
1569
  if (plugin.type === 'DynamicPlatform')
1209
1570
  await this.createDynamicPlugin(plugin);
1210
- this.plugins.start(plugin, 'Matterbridge is starting');
1571
+ this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
1211
1572
  }
1573
+ // Start the Matterbridge in childbridge mode when all plugins are loaded and started
1212
1574
  this.log.debug('Starting start matter interval in childbridge mode...');
1213
1575
  let failCount = 0;
1214
1576
  this.startMatterInterval = setInterval(async () => {
@@ -1242,8 +1604,9 @@ export class Matterbridge extends EventEmitter {
1242
1604
  clearInterval(this.startMatterInterval);
1243
1605
  this.startMatterInterval = undefined;
1244
1606
  if (delay > 0)
1245
- await wait(delay);
1607
+ await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1246
1608
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1609
+ // Configure the plugins
1247
1610
  this.configureTimeout = setTimeout(async () => {
1248
1611
  for (const plugin of this.plugins.array()) {
1249
1612
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1268,6 +1631,7 @@ export class Matterbridge extends EventEmitter {
1268
1631
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1269
1632
  continue;
1270
1633
  }
1634
+ // istanbul ignore next if cause is just a safety check
1271
1635
  if (!plugin.serverNode) {
1272
1636
  this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1273
1637
  continue;
@@ -1280,28 +1644,252 @@ export class Matterbridge extends EventEmitter {
1280
1644
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1281
1645
  continue;
1282
1646
  }
1283
- this.startServerNode(plugin.serverNode);
1647
+ // Start the Matter server node
1648
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1649
+ // Setting reachability to true
1284
1650
  plugin.reachabilityTimeout = setTimeout(() => {
1285
1651
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1286
1652
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1287
1653
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1288
1654
  }, 60 * 1000).unref();
1289
1655
  }
1656
+ // Start the Matter server node of single devices in mode 'server'
1290
1657
  for (const device of this.devices.array()) {
1291
1658
  if (device.mode === 'server' && device.serverNode) {
1292
1659
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1293
- this.startServerNode(device.serverNode);
1660
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1294
1661
  }
1295
1662
  }
1663
+ // Logger.get('LogServerNode').info(this.serverNode);
1296
1664
  this.emit('childbridge_started');
1297
1665
  this.log.notice('Matterbridge childbridge started successfully');
1298
1666
  this.frontend.wssSendRefreshRequired('settings');
1299
1667
  this.frontend.wssSendRefreshRequired('plugins');
1300
1668
  }, this.startMatterIntervalMs);
1301
1669
  }
1670
+ /**
1671
+ * Starts the Matterbridge controller.
1672
+ *
1673
+ * @private
1674
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1675
+ */
1302
1676
  async startController() {
1677
+ /*
1678
+ if (!this.matterStorageManager) {
1679
+ this.log.error('No storage manager initialized');
1680
+ await this.cleanup('No storage manager initialized');
1681
+ return;
1682
+ }
1683
+ this.log.info('Creating context: mattercontrollerContext');
1684
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1685
+ if (!this.controllerContext) {
1686
+ this.log.error('No storage context mattercontrollerContext initialized');
1687
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1688
+ return;
1689
+ }
1690
+
1691
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1692
+ this.matterServer = await this.createMatterServer(this.storageManager);
1693
+ this.log.info('Creating matter commissioning controller');
1694
+ this.commissioningController = new CommissioningController({
1695
+ autoConnect: false,
1696
+ });
1697
+ this.log.info('Adding matter commissioning controller to matter server');
1698
+ await this.matterServer.addCommissioningController(this.commissioningController);
1699
+
1700
+ this.log.info('Starting matter server');
1701
+ await this.matterServer.start();
1702
+ this.log.info('Matter server started');
1703
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1704
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1705
+ regulatoryCountryCode: 'XX',
1706
+ };
1707
+ const commissioningController = new CommissioningController({
1708
+ environment: {
1709
+ environment,
1710
+ id: uniqueId,
1711
+ },
1712
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1713
+ adminFabricLabel,
1714
+ });
1715
+
1716
+ if (hasParameter('pairingcode')) {
1717
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1718
+ const pairingCode = getParameter('pairingcode');
1719
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1720
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1721
+
1722
+ let longDiscriminator, setupPin, shortDiscriminator;
1723
+ if (pairingCode !== undefined) {
1724
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1725
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1726
+ longDiscriminator = undefined;
1727
+ setupPin = pairingCodeCodec.passcode;
1728
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1729
+ } else {
1730
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1731
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1732
+ setupPin = this.controllerContext.get('pin', 20202021);
1733
+ }
1734
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1735
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1736
+ }
1737
+
1738
+ const options = {
1739
+ commissioning: commissioningOptions,
1740
+ discovery: {
1741
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1742
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1743
+ },
1744
+ passcode: setupPin,
1745
+ } as NodeCommissioningOptions;
1746
+ this.log.info('Commissioning with options:', options);
1747
+ const nodeId = await this.commissioningController.commissionNode(options);
1748
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1749
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1750
+ } // (hasParameter('pairingcode'))
1751
+
1752
+ if (hasParameter('unpairall')) {
1753
+ this.log.info('***Commissioning controller unpairing all nodes...');
1754
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1755
+ for (const nodeId of nodeIds) {
1756
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1757
+ await this.commissioningController.removeNode(nodeId);
1758
+ }
1759
+ return;
1760
+ }
1761
+
1762
+ if (hasParameter('discover')) {
1763
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1764
+ // console.log(discover);
1765
+ }
1766
+
1767
+ if (!this.commissioningController.isCommissioned()) {
1768
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1769
+ return;
1770
+ }
1771
+
1772
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1773
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1774
+ for (const nodeId of nodeIds) {
1775
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1776
+
1777
+ const node = await this.commissioningController.connectNode(nodeId, {
1778
+ autoSubscribe: false,
1779
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1780
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1781
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1782
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1783
+ stateInformationCallback: (peerNodeId, info) => {
1784
+ switch (info) {
1785
+ case NodeStateInformation.Connected:
1786
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1787
+ break;
1788
+ case NodeStateInformation.Disconnected:
1789
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1790
+ break;
1791
+ case NodeStateInformation.Reconnecting:
1792
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1793
+ break;
1794
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1795
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1796
+ break;
1797
+ case NodeStateInformation.StructureChanged:
1798
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1799
+ break;
1800
+ case NodeStateInformation.Decommissioned:
1801
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1802
+ break;
1803
+ default:
1804
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1805
+ break;
1806
+ }
1807
+ },
1808
+ });
1809
+
1810
+ node.logStructure();
1811
+
1812
+ // Get the interaction client
1813
+ this.log.info('Getting the interaction client');
1814
+ const interactionClient = await node.getInteractionClient();
1815
+ let cluster;
1816
+ let attributes;
1817
+
1818
+ // Log BasicInformationCluster
1819
+ cluster = BasicInformationCluster;
1820
+ attributes = await interactionClient.getMultipleAttributes({
1821
+ attributes: [{ clusterId: cluster.id }],
1822
+ });
1823
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1824
+ attributes.forEach((attribute) => {
1825
+ this.log.info(
1826
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1827
+ );
1828
+ });
1829
+
1830
+ // Log PowerSourceCluster
1831
+ cluster = PowerSourceCluster;
1832
+ attributes = await interactionClient.getMultipleAttributes({
1833
+ attributes: [{ clusterId: cluster.id }],
1834
+ });
1835
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1836
+ attributes.forEach((attribute) => {
1837
+ this.log.info(
1838
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1839
+ );
1840
+ });
1841
+
1842
+ // Log ThreadNetworkDiagnostics
1843
+ cluster = ThreadNetworkDiagnosticsCluster;
1844
+ attributes = await interactionClient.getMultipleAttributes({
1845
+ attributes: [{ clusterId: cluster.id }],
1846
+ });
1847
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1848
+ attributes.forEach((attribute) => {
1849
+ this.log.info(
1850
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1851
+ );
1852
+ });
1853
+
1854
+ // Log SwitchCluster
1855
+ cluster = SwitchCluster;
1856
+ attributes = await interactionClient.getMultipleAttributes({
1857
+ attributes: [{ clusterId: cluster.id }],
1858
+ });
1859
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1860
+ attributes.forEach((attribute) => {
1861
+ this.log.info(
1862
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1863
+ );
1864
+ });
1865
+
1866
+ this.log.info('Subscribing to all attributes and events');
1867
+ await node.subscribeAllAttributesAndEvents({
1868
+ ignoreInitialTriggers: false,
1869
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1870
+ this.log.info(
1871
+ `***${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}`,
1872
+ ),
1873
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1874
+ this.log.info(
1875
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1876
+ );
1877
+ },
1878
+ });
1879
+ this.log.info('Subscribed to all attributes and events');
1880
+ }
1881
+ */
1303
1882
  }
1883
+ /** */
1884
+ /** Matter.js methods */
1885
+ /** */
1886
+ /**
1887
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1888
+ *
1889
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1890
+ */
1304
1891
  async startMatterStorage() {
1892
+ // Setup Matter storage
1305
1893
  this.log.info(`Starting matter node storage...`);
1306
1894
  this.matterStorageService = this.environment.get(StorageService);
1307
1895
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1309,8 +1897,17 @@ export class Matterbridge extends EventEmitter {
1309
1897
  this.log.info('Matter node storage manager "Matterbridge" created');
1310
1898
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1311
1899
  this.log.info('Matter node storage started');
1900
+ // Backup matter storage since it is created/opened correctly
1312
1901
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
1313
1902
  }
1903
+ /**
1904
+ * Makes a backup copy of the specified matter storage directory.
1905
+ *
1906
+ * @param {string} storageName - The name of the storage directory to be backed up.
1907
+ * @param {string} backupName - The name of the backup directory to be created.
1908
+ * @private
1909
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1910
+ */
1314
1911
  async backupMatterStorage(storageName, backupName) {
1315
1912
  this.log.info('Creating matter node storage backup...');
1316
1913
  try {
@@ -1321,6 +1918,11 @@ export class Matterbridge extends EventEmitter {
1321
1918
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1322
1919
  }
1323
1920
  }
1921
+ /**
1922
+ * Stops the matter storage.
1923
+ *
1924
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1925
+ */
1324
1926
  async stopMatterStorage() {
1325
1927
  this.log.info('Closing matter node storage...');
1326
1928
  await this.matterStorageManager?.close();
@@ -1329,6 +1931,20 @@ export class Matterbridge extends EventEmitter {
1329
1931
  this.matterbridgeContext = undefined;
1330
1932
  this.log.info('Matter node storage closed');
1331
1933
  }
1934
+ /**
1935
+ * Creates a server node storage context.
1936
+ *
1937
+ * @param {string} storeId - The storeId.
1938
+ * @param {string} deviceName - The name of the device.
1939
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1940
+ * @param {number} vendorId - The vendor ID.
1941
+ * @param {string} vendorName - The vendor name.
1942
+ * @param {number} productId - The product ID.
1943
+ * @param {string} productName - The product name.
1944
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1945
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
1946
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1947
+ */
1332
1948
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1333
1949
  const { randomBytes } = await import('node:crypto');
1334
1950
  if (!this.matterStorageService)
@@ -1368,6 +1984,15 @@ export class Matterbridge extends EventEmitter {
1368
1984
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1369
1985
  return storageContext;
1370
1986
  }
1987
+ /**
1988
+ * Creates a server node.
1989
+ *
1990
+ * @param {StorageContext} storageContext - The storage context for the server node.
1991
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
1992
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
1993
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
1994
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1995
+ */
1371
1996
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1372
1997
  const storeId = await storageContext.get('storeId');
1373
1998
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1377,24 +2002,37 @@ export class Matterbridge extends EventEmitter {
1377
2002
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1378
2003
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1379
2004
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2005
+ /**
2006
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
2007
+ */
1380
2008
  const serverNode = await ServerNode.create({
2009
+ // Required: Give the Node a unique ID which is used to store the state of this node
1381
2010
  id: storeId,
2011
+ // Provide Network relevant configuration like the port
2012
+ // Optional when operating only one device on a host, Default port is 5540
1382
2013
  network: {
1383
2014
  listeningAddressIpv4: this.ipv4Address,
1384
2015
  listeningAddressIpv6: this.ipv6Address,
1385
2016
  port,
1386
2017
  },
2018
+ // Provide the certificate for the device
1387
2019
  operationalCredentials: {
1388
2020
  certification: this.certification,
1389
2021
  },
2022
+ // Provide Commissioning relevant settings
2023
+ // Optional for development/testing purposes
1390
2024
  commissioning: {
1391
2025
  passcode,
1392
2026
  discriminator,
1393
2027
  },
2028
+ // Provide Node announcement settings
2029
+ // Optional: If Ommitted some development defaults are used
1394
2030
  productDescription: {
1395
2031
  name: await storageContext.get('deviceName'),
1396
2032
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1397
2033
  },
2034
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
2035
+ // Optional: If Omitted some development defaults are used
1398
2036
  basicInformation: {
1399
2037
  vendorId: VendorId(await storageContext.get('vendorId')),
1400
2038
  vendorName: await storageContext.get('vendorName'),
@@ -1411,17 +2049,23 @@ export class Matterbridge extends EventEmitter {
1411
2049
  reachable: true,
1412
2050
  },
1413
2051
  });
2052
+ /**
2053
+ * This event is triggered when the device is initially commissioned successfully.
2054
+ * This means: It is added to the first fabric.
2055
+ */
1414
2056
  serverNode.lifecycle.commissioned.on(() => {
1415
2057
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1416
2058
  this.advertisingNodes.delete(storeId);
1417
2059
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1418
2060
  });
2061
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1419
2062
  serverNode.lifecycle.decommissioned.on(() => {
1420
2063
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1421
2064
  this.advertisingNodes.delete(storeId);
1422
2065
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1423
2066
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1424
2067
  });
2068
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1425
2069
  serverNode.lifecycle.online.on(async () => {
1426
2070
  this.log.notice(`Server node for ${storeId} is online`);
1427
2071
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1432,13 +2076,16 @@ export class Matterbridge extends EventEmitter {
1432
2076
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1433
2077
  }
1434
2078
  else {
2079
+ // istanbul ignore next
1435
2080
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2081
+ // istanbul ignore next
1436
2082
  this.advertisingNodes.delete(storeId);
1437
2083
  }
1438
2084
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1439
2085
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1440
2086
  this.emit('online', storeId);
1441
2087
  });
2088
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1442
2089
  serverNode.lifecycle.offline.on(() => {
1443
2090
  this.log.notice(`Server node for ${storeId} is offline`);
1444
2091
  this.advertisingNodes.delete(storeId);
@@ -1446,11 +2093,15 @@ export class Matterbridge extends EventEmitter {
1446
2093
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1447
2094
  this.emit('offline', storeId);
1448
2095
  });
2096
+ /**
2097
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2098
+ * information is needed.
2099
+ */
1449
2100
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1450
2101
  let action = '';
1451
2102
  switch (fabricAction) {
1452
2103
  case FabricAction.Added:
1453
- this.advertisingNodes.delete(storeId);
2104
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1454
2105
  action = 'added';
1455
2106
  break;
1456
2107
  case FabricAction.Removed:
@@ -1463,14 +2114,22 @@ export class Matterbridge extends EventEmitter {
1463
2114
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1464
2115
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1465
2116
  });
2117
+ /**
2118
+ * This event is triggered when an operative new session was opened by a Controller.
2119
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2120
+ */
1466
2121
  serverNode.events.sessions.opened.on((session) => {
1467
2122
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1468
2123
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1469
2124
  });
2125
+ /**
2126
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2127
+ */
1470
2128
  serverNode.events.sessions.closed.on((session) => {
1471
2129
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1472
2130
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1473
2131
  });
2132
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1474
2133
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1475
2134
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1476
2135
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
@@ -1478,6 +2137,12 @@ export class Matterbridge extends EventEmitter {
1478
2137
  this.log.info(`Created server node for ${storeId}`);
1479
2138
  return serverNode;
1480
2139
  }
2140
+ /**
2141
+ * Gets the matter sanitized data of the specified server node.
2142
+ *
2143
+ * @param {ServerNode} [serverNode] - The server node to start.
2144
+ * @returns {ApiMatter} The sanitized data of the server node.
2145
+ */
1481
2146
  getServerNodeData(serverNode) {
1482
2147
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1483
2148
  return {
@@ -1494,12 +2159,25 @@ export class Matterbridge extends EventEmitter {
1494
2159
  serialNumber: serverNode.state.basicInformation.serialNumber,
1495
2160
  };
1496
2161
  }
2162
+ /**
2163
+ * Starts the specified server node.
2164
+ *
2165
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2166
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2167
+ */
1497
2168
  async startServerNode(matterServerNode) {
1498
2169
  if (!matterServerNode)
1499
2170
  return;
1500
2171
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1501
2172
  await matterServerNode.start();
1502
2173
  }
2174
+ /**
2175
+ * Stops the specified server node.
2176
+ *
2177
+ * @param {ServerNode} matterServerNode - The server node to stop.
2178
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2179
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2180
+ */
1503
2181
  async stopServerNode(matterServerNode, timeout = 30000) {
1504
2182
  if (!matterServerNode)
1505
2183
  return;
@@ -1512,12 +2190,25 @@ export class Matterbridge extends EventEmitter {
1512
2190
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1513
2191
  }
1514
2192
  }
2193
+ /**
2194
+ * Creates an aggregator node with the specified storage context.
2195
+ *
2196
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2197
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2198
+ */
1515
2199
  async createAggregatorNode(storageContext) {
1516
2200
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1517
2201
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1518
2202
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1519
2203
  return aggregatorNode;
1520
2204
  }
2205
+ /**
2206
+ * Creates and configures the server node for an accessory plugin for a given device.
2207
+ *
2208
+ * @param {Plugin} plugin - The plugin to configure.
2209
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2210
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2211
+ */
1521
2212
  async createAccessoryPlugin(plugin, device) {
1522
2213
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1523
2214
  plugin.locked = true;
@@ -1529,6 +2220,12 @@ export class Matterbridge extends EventEmitter {
1529
2220
  await plugin.serverNode.add(device);
1530
2221
  }
1531
2222
  }
2223
+ /**
2224
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
2225
+ *
2226
+ * @param {Plugin} plugin - The plugin to configure.
2227
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
2228
+ */
1532
2229
  async createDynamicPlugin(plugin) {
1533
2230
  if (!plugin.locked) {
1534
2231
  plugin.locked = true;
@@ -1541,6 +2238,13 @@ export class Matterbridge extends EventEmitter {
1541
2238
  await plugin.serverNode.add(plugin.aggregatorNode);
1542
2239
  }
1543
2240
  }
2241
+ /**
2242
+ * Creates and configures the server node for a single not bridged device.
2243
+ *
2244
+ * @param {Plugin} plugin - The plugin to configure.
2245
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2246
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2247
+ */
1544
2248
  async createDeviceServerNode(plugin, device) {
1545
2249
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1546
2250
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1551,7 +2255,15 @@ export class Matterbridge extends EventEmitter {
1551
2255
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1552
2256
  }
1553
2257
  }
2258
+ /**
2259
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2260
+ *
2261
+ * @param {string} pluginName - The name of the plugin.
2262
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2263
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2264
+ */
1554
2265
  async addBridgedEndpoint(pluginName, device) {
2266
+ // Check if the plugin is registered
1555
2267
  const plugin = this.plugins.get(pluginName);
1556
2268
  if (!plugin) {
1557
2269
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1571,6 +2283,7 @@ export class Matterbridge extends EventEmitter {
1571
2283
  }
1572
2284
  else if (this.bridgeMode === 'bridge') {
1573
2285
  if (device.mode === 'matter') {
2286
+ // Register and add the device to the matterbridge server node
1574
2287
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1575
2288
  if (!this.serverNode) {
1576
2289
  this.log.error('Server node not found for Matterbridge');
@@ -1587,6 +2300,7 @@ export class Matterbridge extends EventEmitter {
1587
2300
  }
1588
2301
  }
1589
2302
  else {
2303
+ // Register and add the device to the matterbridge aggregator node
1590
2304
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1591
2305
  if (!this.aggregatorNode) {
1592
2306
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1604,6 +2318,7 @@ export class Matterbridge extends EventEmitter {
1604
2318
  }
1605
2319
  }
1606
2320
  else if (this.bridgeMode === 'childbridge') {
2321
+ // Register and add the device to the plugin server node
1607
2322
  if (plugin.type === 'AccessoryPlatform') {
1608
2323
  try {
1609
2324
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1627,10 +2342,12 @@ export class Matterbridge extends EventEmitter {
1627
2342
  return;
1628
2343
  }
1629
2344
  }
2345
+ // Register and add the device to the plugin aggregator node
1630
2346
  if (plugin.type === 'DynamicPlatform') {
1631
2347
  try {
1632
2348
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1633
2349
  await this.createDynamicPlugin(plugin);
2350
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1634
2351
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1635
2352
  if (!plugin.aggregatorNode) {
1636
2353
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1651,17 +2368,28 @@ export class Matterbridge extends EventEmitter {
1651
2368
  }
1652
2369
  if (plugin.registeredDevices !== undefined)
1653
2370
  plugin.registeredDevices++;
2371
+ // Add the device to the DeviceManager
1654
2372
  this.devices.set(device);
2373
+ // Subscribe to the attributes changed event
1655
2374
  await this.subscribeAttributeChanged(plugin, device);
1656
2375
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1657
2376
  }
2377
+ /**
2378
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2379
+ *
2380
+ * @param {string} pluginName - The name of the plugin.
2381
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2382
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2383
+ */
1658
2384
  async removeBridgedEndpoint(pluginName, device) {
1659
2385
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2386
+ // Check if the plugin is registered
1660
2387
  const plugin = this.plugins.get(pluginName);
1661
2388
  if (!plugin) {
1662
2389
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1663
2390
  return;
1664
2391
  }
2392
+ // Register and add the device to the matterbridge aggregator node
1665
2393
  if (this.bridgeMode === 'bridge') {
1666
2394
  if (!this.aggregatorNode) {
1667
2395
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1674,6 +2402,7 @@ export class Matterbridge extends EventEmitter {
1674
2402
  }
1675
2403
  else if (this.bridgeMode === 'childbridge') {
1676
2404
  if (plugin.type === 'AccessoryPlatform') {
2405
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1677
2406
  }
1678
2407
  else if (plugin.type === 'DynamicPlatform') {
1679
2408
  if (!plugin.aggregatorNode) {
@@ -1686,8 +2415,21 @@ export class Matterbridge extends EventEmitter {
1686
2415
  if (plugin.registeredDevices !== undefined)
1687
2416
  plugin.registeredDevices--;
1688
2417
  }
2418
+ // Remove the device from the DeviceManager
1689
2419
  this.devices.remove(device);
1690
2420
  }
2421
+ /**
2422
+ * Removes all bridged endpoints from the specified plugin.
2423
+ *
2424
+ * @param {string} pluginName - The name of the plugin.
2425
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2426
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2427
+ *
2428
+ * @remarks
2429
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2430
+ * It also applies a delay between each removal if specified.
2431
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2432
+ */
1691
2433
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1692
2434
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1693
2435
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1698,13 +2440,24 @@ export class Matterbridge extends EventEmitter {
1698
2440
  if (delay > 0)
1699
2441
  await wait(2000);
1700
2442
  }
2443
+ /**
2444
+ * Subscribes to the attribute change event for the given device and plugin.
2445
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2446
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2447
+ *
2448
+ * @param {Plugin} plugin - The plugin associated with the device.
2449
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2450
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2451
+ */
1701
2452
  async subscribeAttributeChanged(plugin, device) {
1702
2453
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
1703
2454
  return;
1704
2455
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
2456
+ // Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
1705
2457
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1706
2458
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1707
2459
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
2460
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1708
2461
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
1709
2462
  });
1710
2463
  }
@@ -1754,6 +2507,7 @@ export class Matterbridge extends EventEmitter {
1754
2507
  this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1755
2508
  await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1756
2509
  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}`);
2510
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1757
2511
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
1758
2512
  });
1759
2513
  }
@@ -1762,12 +2516,19 @@ export class Matterbridge extends EventEmitter {
1762
2516
  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...`);
1763
2517
  await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1764
2518
  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}`);
2519
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1765
2520
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
1766
2521
  });
1767
2522
  }
1768
2523
  }
1769
2524
  }
1770
2525
  }
2526
+ /**
2527
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2528
+ *
2529
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2530
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2531
+ */
1771
2532
  sanitizeFabricInformations(fabricInfo) {
1772
2533
  return fabricInfo.map((info) => {
1773
2534
  return {
@@ -1781,6 +2542,12 @@ export class Matterbridge extends EventEmitter {
1781
2542
  };
1782
2543
  });
1783
2544
  }
2545
+ /**
2546
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2547
+ *
2548
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2549
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2550
+ */
1784
2551
  sanitizeSessionInformation(sessions) {
1785
2552
  return sessions
1786
2553
  .filter((session) => session.isPeerActive)
@@ -1807,7 +2574,21 @@ export class Matterbridge extends EventEmitter {
1807
2574
  };
1808
2575
  });
1809
2576
  }
2577
+ /**
2578
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2579
+ *
2580
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2581
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2582
+ */
2583
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1810
2584
  async setAggregatorReachability(aggregatorNode, reachable) {
2585
+ /*
2586
+ for (const child of aggregatorNode.parts) {
2587
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2588
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2589
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2590
+ }
2591
+ */
1811
2592
  }
1812
2593
  getVendorIdName = (vendorId) => {
1813
2594
  if (!vendorId)
@@ -1847,10 +2628,11 @@ export class Matterbridge extends EventEmitter {
1847
2628
  case 0x1488:
1848
2629
  vendorName = '(ShortcutLabsFlic)';
1849
2630
  break;
1850
- case 65521:
2631
+ case 65521: // 0xFFF1
1851
2632
  vendorName = '(MatterTest)';
1852
2633
  break;
1853
2634
  }
1854
2635
  return vendorName;
1855
2636
  };
1856
2637
  }
2638
+ //# sourceMappingURL=matterbridge.js.map