matterbridge 3.3.5-dev-20251029-a0d9d11 → 3.3.5

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 (305) hide show
  1. package/CHANGELOG.md +7 -2
  2. package/README-SERVICE-LOCAL.md +13 -13
  3. package/README.md +4 -2
  4. package/dist/broadcastServer.d.ts +112 -0
  5. package/dist/broadcastServer.d.ts.map +1 -0
  6. package/dist/broadcastServer.js +92 -1
  7. package/dist/broadcastServer.js.map +1 -0
  8. package/dist/broadcastServerTypes.d.ts +803 -0
  9. package/dist/broadcastServerTypes.d.ts.map +1 -0
  10. package/dist/broadcastServerTypes.js +24 -0
  11. package/dist/broadcastServerTypes.js.map +1 -0
  12. package/dist/cli.d.ts +30 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +97 -1
  15. package/dist/cli.js.map +1 -0
  16. package/dist/cliEmitter.d.ts +50 -0
  17. package/dist/cliEmitter.d.ts.map +1 -0
  18. package/dist/cliEmitter.js +37 -0
  19. package/dist/cliEmitter.js.map +1 -0
  20. package/dist/cliHistory.d.ts +48 -0
  21. package/dist/cliHistory.d.ts.map +1 -0
  22. package/dist/cliHistory.js +38 -0
  23. package/dist/cliHistory.js.map +1 -0
  24. package/dist/clusters/export.d.ts +2 -0
  25. package/dist/clusters/export.d.ts.map +1 -0
  26. package/dist/clusters/export.js +2 -0
  27. package/dist/clusters/export.js.map +1 -0
  28. package/dist/defaultConfigSchema.d.ts +28 -0
  29. package/dist/defaultConfigSchema.d.ts.map +1 -0
  30. package/dist/defaultConfigSchema.js +24 -0
  31. package/dist/defaultConfigSchema.js.map +1 -0
  32. package/dist/deviceManager.d.ts +117 -0
  33. package/dist/deviceManager.d.ts.map +1 -0
  34. package/dist/deviceManager.js +124 -1
  35. package/dist/deviceManager.js.map +1 -0
  36. package/dist/devices/airConditioner.d.ts +98 -0
  37. package/dist/devices/airConditioner.d.ts.map +1 -0
  38. package/dist/devices/airConditioner.js +57 -0
  39. package/dist/devices/airConditioner.js.map +1 -0
  40. package/dist/devices/batteryStorage.d.ts +48 -0
  41. package/dist/devices/batteryStorage.d.ts.map +1 -0
  42. package/dist/devices/batteryStorage.js +48 -1
  43. package/dist/devices/batteryStorage.js.map +1 -0
  44. package/dist/devices/cooktop.d.ts +60 -0
  45. package/dist/devices/cooktop.d.ts.map +1 -0
  46. package/dist/devices/cooktop.js +55 -0
  47. package/dist/devices/cooktop.js.map +1 -0
  48. package/dist/devices/dishwasher.d.ts +71 -0
  49. package/dist/devices/dishwasher.d.ts.map +1 -0
  50. package/dist/devices/dishwasher.js +57 -0
  51. package/dist/devices/dishwasher.js.map +1 -0
  52. package/dist/devices/evse.d.ts +76 -0
  53. package/dist/devices/evse.d.ts.map +1 -0
  54. package/dist/devices/evse.js +74 -10
  55. package/dist/devices/evse.js.map +1 -0
  56. package/dist/devices/export.d.ts +17 -0
  57. package/dist/devices/export.d.ts.map +1 -0
  58. package/dist/devices/export.js +5 -0
  59. package/dist/devices/export.js.map +1 -0
  60. package/dist/devices/extractorHood.d.ts +46 -0
  61. package/dist/devices/extractorHood.d.ts.map +1 -0
  62. package/dist/devices/extractorHood.js +42 -0
  63. package/dist/devices/extractorHood.js.map +1 -0
  64. package/dist/devices/heatPump.d.ts +47 -0
  65. package/dist/devices/heatPump.d.ts.map +1 -0
  66. package/dist/devices/heatPump.js +50 -2
  67. package/dist/devices/heatPump.js.map +1 -0
  68. package/dist/devices/laundryDryer.d.ts +67 -0
  69. package/dist/devices/laundryDryer.d.ts.map +1 -0
  70. package/dist/devices/laundryDryer.js +62 -3
  71. package/dist/devices/laundryDryer.js.map +1 -0
  72. package/dist/devices/laundryWasher.d.ts +81 -0
  73. package/dist/devices/laundryWasher.d.ts.map +1 -0
  74. package/dist/devices/laundryWasher.js +70 -4
  75. package/dist/devices/laundryWasher.js.map +1 -0
  76. package/dist/devices/microwaveOven.d.ts +168 -0
  77. package/dist/devices/microwaveOven.d.ts.map +1 -0
  78. package/dist/devices/microwaveOven.js +88 -5
  79. package/dist/devices/microwaveOven.js.map +1 -0
  80. package/dist/devices/oven.d.ts +105 -0
  81. package/dist/devices/oven.d.ts.map +1 -0
  82. package/dist/devices/oven.js +85 -0
  83. package/dist/devices/oven.js.map +1 -0
  84. package/dist/devices/refrigerator.d.ts +118 -0
  85. package/dist/devices/refrigerator.d.ts.map +1 -0
  86. package/dist/devices/refrigerator.js +102 -0
  87. package/dist/devices/refrigerator.js.map +1 -0
  88. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  89. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  90. package/dist/devices/roboticVacuumCleaner.js +100 -9
  91. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  92. package/dist/devices/solarPower.d.ts +40 -0
  93. package/dist/devices/solarPower.d.ts.map +1 -0
  94. package/dist/devices/solarPower.js +38 -0
  95. package/dist/devices/solarPower.js.map +1 -0
  96. package/dist/devices/speaker.d.ts +87 -0
  97. package/dist/devices/speaker.d.ts.map +1 -0
  98. package/dist/devices/speaker.js +84 -0
  99. package/dist/devices/speaker.js.map +1 -0
  100. package/dist/devices/temperatureControl.d.ts +166 -0
  101. package/dist/devices/temperatureControl.d.ts.map +1 -0
  102. package/dist/devices/temperatureControl.js +24 -3
  103. package/dist/devices/temperatureControl.js.map +1 -0
  104. package/dist/devices/waterHeater.d.ts +111 -0
  105. package/dist/devices/waterHeater.d.ts.map +1 -0
  106. package/dist/devices/waterHeater.js +82 -2
  107. package/dist/devices/waterHeater.js.map +1 -0
  108. package/dist/dgram/coap.d.ts +205 -0
  109. package/dist/dgram/coap.d.ts.map +1 -0
  110. package/dist/dgram/coap.js +126 -13
  111. package/dist/dgram/coap.js.map +1 -0
  112. package/dist/dgram/dgram.d.ts +141 -0
  113. package/dist/dgram/dgram.d.ts.map +1 -0
  114. package/dist/dgram/dgram.js +114 -2
  115. package/dist/dgram/dgram.js.map +1 -0
  116. package/dist/dgram/mb_coap.d.ts +24 -0
  117. package/dist/dgram/mb_coap.d.ts.map +1 -0
  118. package/dist/dgram/mb_coap.js +41 -3
  119. package/dist/dgram/mb_coap.js.map +1 -0
  120. package/dist/dgram/mb_mdns.d.ts +24 -0
  121. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  122. package/dist/dgram/mb_mdns.js +80 -15
  123. package/dist/dgram/mb_mdns.js.map +1 -0
  124. package/dist/dgram/mdns.d.ts +290 -0
  125. package/dist/dgram/mdns.d.ts.map +1 -0
  126. package/dist/dgram/mdns.js +299 -137
  127. package/dist/dgram/mdns.js.map +1 -0
  128. package/dist/dgram/multicast.d.ts +67 -0
  129. package/dist/dgram/multicast.d.ts.map +1 -0
  130. package/dist/dgram/multicast.js +62 -1
  131. package/dist/dgram/multicast.js.map +1 -0
  132. package/dist/dgram/unicast.d.ts +56 -0
  133. package/dist/dgram/unicast.d.ts.map +1 -0
  134. package/dist/dgram/unicast.js +54 -0
  135. package/dist/dgram/unicast.js.map +1 -0
  136. package/dist/frontend.d.ts +236 -0
  137. package/dist/frontend.d.ts.map +1 -0
  138. package/dist/frontend.js +431 -34
  139. package/dist/frontend.js.map +1 -0
  140. package/dist/frontendTypes.d.ts +529 -0
  141. package/dist/frontendTypes.d.ts.map +1 -0
  142. package/dist/frontendTypes.js +45 -0
  143. package/dist/frontendTypes.js.map +1 -0
  144. package/dist/helpers.d.ts +48 -0
  145. package/dist/helpers.d.ts.map +1 -0
  146. package/dist/helpers.js +53 -0
  147. package/dist/helpers.js.map +1 -0
  148. package/dist/index.d.ts +33 -0
  149. package/dist/index.d.ts.map +1 -0
  150. package/dist/index.js +25 -0
  151. package/dist/index.js.map +1 -0
  152. package/dist/logger/export.d.ts +2 -0
  153. package/dist/logger/export.d.ts.map +1 -0
  154. package/dist/logger/export.js +1 -0
  155. package/dist/logger/export.js.map +1 -0
  156. package/dist/matter/behaviors.d.ts +2 -0
  157. package/dist/matter/behaviors.d.ts.map +1 -0
  158. package/dist/matter/behaviors.js +2 -0
  159. package/dist/matter/behaviors.js.map +1 -0
  160. package/dist/matter/clusters.d.ts +2 -0
  161. package/dist/matter/clusters.d.ts.map +1 -0
  162. package/dist/matter/clusters.js +2 -0
  163. package/dist/matter/clusters.js.map +1 -0
  164. package/dist/matter/devices.d.ts +2 -0
  165. package/dist/matter/devices.d.ts.map +1 -0
  166. package/dist/matter/devices.js +2 -0
  167. package/dist/matter/devices.js.map +1 -0
  168. package/dist/matter/endpoints.d.ts +2 -0
  169. package/dist/matter/endpoints.d.ts.map +1 -0
  170. package/dist/matter/endpoints.js +2 -0
  171. package/dist/matter/endpoints.js.map +1 -0
  172. package/dist/matter/export.d.ts +5 -0
  173. package/dist/matter/export.d.ts.map +1 -0
  174. package/dist/matter/export.js +3 -0
  175. package/dist/matter/export.js.map +1 -0
  176. package/dist/matter/types.d.ts +3 -0
  177. package/dist/matter/types.d.ts.map +1 -0
  178. package/dist/matter/types.js +3 -0
  179. package/dist/matter/types.js.map +1 -0
  180. package/dist/matterbridge.d.ts +476 -0
  181. package/dist/matterbridge.d.ts.map +1 -0
  182. package/dist/matterbridge.js +828 -46
  183. package/dist/matterbridge.js.map +1 -0
  184. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  185. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  186. package/dist/matterbridgeAccessoryPlatform.js +37 -0
  187. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  188. package/dist/matterbridgeBehaviors.d.ts +2404 -0
  189. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  190. package/dist/matterbridgeBehaviors.js +68 -5
  191. package/dist/matterbridgeBehaviors.js.map +1 -0
  192. package/dist/matterbridgeDeviceTypes.d.ts +770 -0
  193. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  194. package/dist/matterbridgeDeviceTypes.js +638 -17
  195. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  196. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  197. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  198. package/dist/matterbridgeDynamicPlatform.js +37 -0
  199. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  200. package/dist/matterbridgeEndpoint.d.ts +1556 -0
  201. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  202. package/dist/matterbridgeEndpoint.js +1414 -58
  203. package/dist/matterbridgeEndpoint.js.map +1 -0
  204. package/dist/matterbridgeEndpointHelpers.d.ts +758 -0
  205. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  206. package/dist/matterbridgeEndpointHelpers.js +464 -19
  207. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  208. package/dist/matterbridgePlatform.d.ts +402 -0
  209. package/dist/matterbridgePlatform.d.ts.map +1 -0
  210. package/dist/matterbridgePlatform.js +341 -1
  211. package/dist/matterbridgePlatform.js.map +1 -0
  212. package/dist/matterbridgeTypes.d.ts +226 -0
  213. package/dist/matterbridgeTypes.d.ts.map +1 -0
  214. package/dist/matterbridgeTypes.js +26 -0
  215. package/dist/matterbridgeTypes.js.map +1 -0
  216. package/dist/pluginManager.d.ts +347 -0
  217. package/dist/pluginManager.d.ts.map +1 -0
  218. package/dist/pluginManager.js +319 -3
  219. package/dist/pluginManager.js.map +1 -0
  220. package/dist/shelly.d.ts +174 -0
  221. package/dist/shelly.d.ts.map +1 -0
  222. package/dist/shelly.js +168 -7
  223. package/dist/shelly.js.map +1 -0
  224. package/dist/storage/export.d.ts +2 -0
  225. package/dist/storage/export.d.ts.map +1 -0
  226. package/dist/storage/export.js +1 -0
  227. package/dist/storage/export.js.map +1 -0
  228. package/dist/update.d.ts +75 -0
  229. package/dist/update.d.ts.map +1 -0
  230. package/dist/update.js +69 -0
  231. package/dist/update.js.map +1 -0
  232. package/dist/utils/colorUtils.d.ts +101 -0
  233. package/dist/utils/colorUtils.d.ts.map +1 -0
  234. package/dist/utils/colorUtils.js +99 -2
  235. package/dist/utils/colorUtils.js.map +1 -0
  236. package/dist/utils/commandLine.d.ts +66 -0
  237. package/dist/utils/commandLine.d.ts.map +1 -0
  238. package/dist/utils/commandLine.js +60 -0
  239. package/dist/utils/commandLine.js.map +1 -0
  240. package/dist/utils/copyDirectory.d.ts +33 -0
  241. package/dist/utils/copyDirectory.d.ts.map +1 -0
  242. package/dist/utils/copyDirectory.js +38 -1
  243. package/dist/utils/copyDirectory.js.map +1 -0
  244. package/dist/utils/createDirectory.d.ts +34 -0
  245. package/dist/utils/createDirectory.d.ts.map +1 -0
  246. package/dist/utils/createDirectory.js +33 -0
  247. package/dist/utils/createDirectory.js.map +1 -0
  248. package/dist/utils/createZip.d.ts +39 -0
  249. package/dist/utils/createZip.d.ts.map +1 -0
  250. package/dist/utils/createZip.js +47 -2
  251. package/dist/utils/createZip.js.map +1 -0
  252. package/dist/utils/deepCopy.d.ts +32 -0
  253. package/dist/utils/deepCopy.d.ts.map +1 -0
  254. package/dist/utils/deepCopy.js +39 -0
  255. package/dist/utils/deepCopy.js.map +1 -0
  256. package/dist/utils/deepEqual.d.ts +54 -0
  257. package/dist/utils/deepEqual.d.ts.map +1 -0
  258. package/dist/utils/deepEqual.js +72 -1
  259. package/dist/utils/deepEqual.js.map +1 -0
  260. package/dist/utils/error.d.ts +44 -0
  261. package/dist/utils/error.d.ts.map +1 -0
  262. package/dist/utils/error.js +41 -0
  263. package/dist/utils/error.js.map +1 -0
  264. package/dist/utils/export.d.ts +13 -0
  265. package/dist/utils/export.d.ts.map +1 -0
  266. package/dist/utils/export.js +1 -0
  267. package/dist/utils/export.js.map +1 -0
  268. package/dist/utils/format.d.ts +53 -0
  269. package/dist/utils/format.d.ts.map +1 -0
  270. package/dist/utils/format.js +49 -0
  271. package/dist/utils/format.js.map +1 -0
  272. package/dist/utils/hex.d.ts +89 -0
  273. package/dist/utils/hex.d.ts.map +1 -0
  274. package/dist/utils/hex.js +124 -0
  275. package/dist/utils/hex.js.map +1 -0
  276. package/dist/utils/inspector.d.ts +87 -0
  277. package/dist/utils/inspector.d.ts.map +1 -0
  278. package/dist/utils/inspector.js +69 -1
  279. package/dist/utils/inspector.js.map +1 -0
  280. package/dist/utils/isvalid.d.ts +103 -0
  281. package/dist/utils/isvalid.d.ts.map +1 -0
  282. package/dist/utils/isvalid.js +101 -0
  283. package/dist/utils/isvalid.js.map +1 -0
  284. package/dist/utils/jestHelpers.d.ts +139 -0
  285. package/dist/utils/jestHelpers.d.ts.map +1 -0
  286. package/dist/utils/jestHelpers.js +153 -3
  287. package/dist/utils/jestHelpers.js.map +1 -0
  288. package/dist/utils/network.d.ts +101 -0
  289. package/dist/utils/network.d.ts.map +1 -0
  290. package/dist/utils/network.js +96 -5
  291. package/dist/utils/network.js.map +1 -0
  292. package/dist/utils/spawn.d.ts +35 -0
  293. package/dist/utils/spawn.d.ts.map +1 -0
  294. package/dist/utils/spawn.js +71 -0
  295. package/dist/utils/spawn.js.map +1 -0
  296. package/dist/utils/tracker.d.ts +108 -0
  297. package/dist/utils/tracker.d.ts.map +1 -0
  298. package/dist/utils/tracker.js +64 -1
  299. package/dist/utils/tracker.js.map +1 -0
  300. package/dist/utils/wait.d.ts +54 -0
  301. package/dist/utils/wait.d.ts.map +1 -0
  302. package/dist/utils/wait.js +60 -8
  303. package/dist/utils/wait.js.map +1 -0
  304. package/npm-shrinkwrap.json +6 -6
  305. 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 { promises as fs } from 'node:fs';
6
31
  import EventEmitter from 'node:events';
7
32
  import { inspect } from 'node:util';
33
+ // AnsiLogger module
8
34
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE, or } from 'node-ansi-logger';
35
+ // NodeStorage module
9
36
  import { NodeStorageManager } from 'node-persist-manager';
10
- import '@matter/nodejs';
37
+ // @matter
38
+ import '@matter/nodejs'; // Set up Node.js environment for matter.js
11
39
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, UINT32_MAX, UINT16_MAX, Crypto, Environment, StorageService } from '@matter/general';
12
40
  import { FabricAction, PaseClient } from '@matter/protocol';
13
41
  import { Endpoint, ServerNode } from '@matter/node';
14
42
  import { DeviceTypeId, VendorId } from '@matter/types/datatype';
15
43
  import { AggregatorEndpoint } from '@matter/node/endpoints';
16
44
  import { BasicInformationServer } from '@matter/node/behaviors/basic-information';
45
+ // Matterbridge
17
46
  import { getParameter, getIntParameter, hasParameter } from './utils/commandLine.js';
18
47
  import { copyDirectory } from './utils/copyDirectory.js';
19
48
  import { createDirectory } from './utils/createDirectory.js';
@@ -28,19 +57,27 @@ import { bridge } from './matterbridgeDeviceTypes.js';
28
57
  import { Frontend } from './frontend.js';
29
58
  import { addVirtualDevices } from './helpers.js';
30
59
  import { BroadcastServer } from './broadcastServer.js';
60
+ /**
61
+ * Represents the Matterbridge application.
62
+ */
31
63
  export class Matterbridge extends EventEmitter {
64
+ /** Matterbridge system information */
32
65
  systemInformation = {
66
+ // Network properties
33
67
  interfaceName: '',
34
68
  macAddress: '',
35
69
  ipv4Address: '',
36
70
  ipv6Address: '',
71
+ // Node.js properties
37
72
  nodeVersion: '',
73
+ // Fixed system properties
38
74
  hostname: '',
39
75
  user: '',
40
76
  osType: '',
41
77
  osRelease: '',
42
78
  osPlatform: '',
43
79
  osArch: '',
80
+ // Cpu and memory properties
44
81
  totalMemory: '',
45
82
  freeMemory: '',
46
83
  systemUptime: '',
@@ -51,6 +88,7 @@ export class Matterbridge extends EventEmitter {
51
88
  heapTotal: '',
52
89
  heapUsed: '',
53
90
  };
91
+ // Matterbridge settings
54
92
  homeDirectory = '';
55
93
  rootDirectory = '';
56
94
  matterbridgeDirectory = '';
@@ -65,10 +103,15 @@ export class Matterbridge extends EventEmitter {
65
103
  restartMode = '';
66
104
  virtualMode = 'outlet';
67
105
  profile = getParameter('profile');
68
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
106
+ /** Matterbridge logger */
107
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
108
+ /** Whether to log to a file */
69
109
  fileLogger = false;
70
- matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
110
+ /** Matter logger */
111
+ matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
112
+ /** Whether to log Matter to a file */
71
113
  matterFileLogger = false;
114
+ // Frontend settings
72
115
  readOnly = hasParameter('readonly') || hasParameter('shelly');
73
116
  shellyBoard = hasParameter('shelly');
74
117
  shellySysUpdate = false;
@@ -76,12 +119,18 @@ export class Matterbridge extends EventEmitter {
76
119
  restartRequired = false;
77
120
  fixedRestartRequired = false;
78
121
  updateRequired = false;
122
+ // Managers
79
123
  plugins = new PluginManager(this);
80
124
  devices = new DeviceManager();
125
+ // Frontend
81
126
  frontend = new Frontend(this);
127
+ /** Matterbridge node storage manager created in the directory 'storage' in matterbridgeDirectory */
82
128
  nodeStorage;
129
+ /** Matterbridge node context created with name 'matterbridge' */
83
130
  nodeContext;
131
+ /** The main instance of the Matterbridge class (singleton) */
84
132
  static instance;
133
+ // Instance properties
85
134
  shutdown = false;
86
135
  failCountLimit = hasParameter('shelly') ? 600 : 120;
87
136
  hasCleanupStarted = false;
@@ -96,19 +145,32 @@ export class Matterbridge extends EventEmitter {
96
145
  sigtermHandler;
97
146
  exceptionHandler;
98
147
  rejectionHandler;
148
+ /** Matter environment default */
99
149
  environment = Environment.default;
150
+ /** Matter storage service from environment default */
100
151
  matterStorageService;
152
+ /** Matter storage manager created with name 'Matterbridge' */
101
153
  matterStorageManager;
154
+ /** Matter matterbridge storage context created in the storage manager with name 'persist' */
102
155
  matterbridgeContext;
103
156
  controllerContext;
157
+ /** Matter mdns interface e.g. 'eth0' or 'wlan0' or 'Wi-Fi' */
104
158
  mdnsInterface;
159
+ /** Matter listeningAddressIpv4 address */
105
160
  ipv4Address;
161
+ /** Matter listeningAddressIpv6 address */
106
162
  ipv6Address;
107
- port;
108
- passcode;
109
- discriminator;
110
- certification;
163
+ /** Matter commissioning port */
164
+ port; // first server node port
165
+ /** Matter commissioning passcode */
166
+ passcode; // first server node passcode
167
+ /** Matter commissioning discriminator */
168
+ discriminator; // first server node discriminator
169
+ /** Matter device certification */
170
+ certification; // device certification
171
+ /** Matter server node in bridge mode */
111
172
  serverNode;
173
+ /** Matter aggregator node in bridge mode */
112
174
  aggregatorNode;
113
175
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
114
176
  aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
@@ -117,8 +179,11 @@ export class Matterbridge extends EventEmitter {
117
179
  aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
118
180
  aggregatorSerialNumber = getParameter('serialNumber');
119
181
  aggregatorUniqueId = getParameter('uniqueId');
182
+ /** Advertising nodes map: time advertising started keyed by storeId */
120
183
  advertisingNodes = new Map();
184
+ /** Broadcast server */
121
185
  server;
186
+ /** We load asyncronously so is private */
122
187
  constructor() {
123
188
  super();
124
189
  this.log.logNameColor = '\x1b[38;5;115m';
@@ -154,8 +219,19 @@ export class Matterbridge extends EventEmitter {
154
219
  }
155
220
  }
156
221
  }
222
+ //* ************************************************************************************************************************************ */
223
+ // loadInstance() and cleanup() methods */
224
+ //* ************************************************************************************************************************************ */
225
+ /**
226
+ * Loads an instance of the Matterbridge class.
227
+ * If an instance already exists, return that instance.
228
+ *
229
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
230
+ * @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
231
+ */
157
232
  static async loadInstance(initialize = false) {
158
233
  if (!Matterbridge.instance) {
234
+ // eslint-disable-next-line no-console
159
235
  if (hasParameter('debug'))
160
236
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
161
237
  Matterbridge.instance = new Matterbridge();
@@ -164,62 +240,131 @@ export class Matterbridge extends EventEmitter {
164
240
  }
165
241
  return Matterbridge.instance;
166
242
  }
243
+ /**
244
+ * Call cleanup() and dispose MdnsService. Will be removed since matter.js 0.15.6 dispose MdnsService.
245
+ *
246
+ * @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
247
+ * @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
248
+ *
249
+ * @deprecated This method is deprecated and is ONLY used for jest tests.
250
+ */
167
251
  async destroyInstance(timeout = 1000, pause = 250) {
168
252
  this.log.info(`Destroy instance...`);
253
+ // Save server nodes to close
254
+ /*
255
+ const servers: ServerNode<ServerNode.RootEndpoint>[] = [];
256
+ if (this.bridgeMode === 'bridge') {
257
+ if (this.serverNode) servers.push(this.serverNode);
258
+ }
259
+ if (this.bridgeMode === 'childbridge' && this.plugins !== undefined) {
260
+ for (const plugin of this.plugins.array()) {
261
+ if (plugin.serverNode) servers.push(plugin.serverNode);
262
+ }
263
+ }
264
+ if (this.devices !== undefined) {
265
+ for (const device of this.devices.array()) {
266
+ if (device.mode === 'server' && device.serverNode) servers.push(device.serverNode);
267
+ }
268
+ }
269
+ */
270
+ // Let any already‐queued microtasks run first
271
+ // await Promise.resolve();
272
+ // Wait for the cleanup to finish
273
+ // await wait(pause, 'destroyInstance start', true);
274
+ // Cleanup
169
275
  await this.cleanup('destroying instance...', false, timeout);
276
+ // Close servers mdns service
277
+ /*
278
+ this.log.info(`Dispose ${servers.length} MdnsService...`);
279
+ for (const server of servers) {
280
+ // await server.env.get(MdnsService)[Symbol.asyncDispose]();
281
+ this.log.info(`Closed ${server.id} MdnsService`);
282
+ }
283
+ */
284
+ // Let any already‐queued microtasks run first
285
+ // await Promise.resolve();
286
+ // Wait for the cleanup to finish
170
287
  if (pause)
171
288
  await wait(pause, 'destroyInstance stop', true);
172
289
  }
290
+ /**
291
+ * Initializes the Matterbridge application.
292
+ *
293
+ * @remarks
294
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
295
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
296
+ * node version, registers signal handlers, initializes storage, and parses the command line.
297
+ *
298
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
299
+ */
173
300
  async initialize() {
301
+ // for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
302
+ // Emit the initialize_started event
174
303
  this.emit('initialize_started');
304
+ // Set the restart mode
175
305
  if (hasParameter('service'))
176
306
  this.restartMode = 'service';
177
307
  if (hasParameter('docker'))
178
308
  this.restartMode = 'docker';
309
+ // Set the matterbridge home directory
179
310
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
180
311
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
312
+ // Set the matterbridge directory
181
313
  this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
182
314
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
183
315
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
184
316
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
317
+ // Set the matterbridge plugin directory
185
318
  this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
186
319
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
320
+ // Set the matterbridge cert directory
187
321
  this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
188
322
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
323
+ // Set the matterbridge root directory
189
324
  const { fileURLToPath } = await import('node:url');
190
325
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
191
- this.rootDirectory = path.resolve(currentFileDirectory, '../');
326
+ this.rootDirectory = path.resolve(currentFileDirectory, '../'); // Adjust the path for dist directory
327
+ // Setup the matter environment with default values
192
328
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
193
329
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
194
330
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
195
331
  this.environment.vars.set('runtime.signals', false);
196
332
  this.environment.vars.set('runtime.exitcode', false);
333
+ // Register process handlers
197
334
  this.registerProcessHandlers();
335
+ // Initialize nodeStorage and nodeContext
198
336
  try {
199
337
  this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
200
338
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
201
339
  this.log.debug('Creating node storage context for matterbridge');
202
340
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
341
+ // TODO: Remove this code when node-persist-manager is updated
342
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
343
  const keys = (await this.nodeStorage?.storage.keys());
204
344
  for (const key of keys) {
205
345
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
346
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
206
347
  await this.nodeStorage?.storage.get(key);
207
348
  }
208
349
  const storages = await this.nodeStorage.getStorageNames();
209
350
  for (const storage of storages) {
210
351
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
211
352
  const nodeContext = await this.nodeStorage?.createStorage(storage);
353
+ // TODO: Remove this code when node-persist-manager is updated
354
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
355
  const keys = (await nodeContext?.storage.keys());
213
356
  keys.forEach(async (key) => {
214
357
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
215
358
  await nodeContext?.get(key);
216
359
  });
217
360
  }
361
+ // Creating a backup of the node storage since it is not corrupted
218
362
  this.log.debug('Creating node storage backup...');
219
363
  await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
220
364
  this.log.debug('Created node storage backup');
221
365
  }
222
366
  catch (error) {
367
+ // Restoring the backup of the node storage since it is corrupted
223
368
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
224
369
  if (hasParameter('norestore')) {
225
370
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -233,14 +378,19 @@ export class Matterbridge extends EventEmitter {
233
378
  if (!this.nodeStorage || !this.nodeContext) {
234
379
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
235
380
  }
381
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
236
382
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
383
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
237
384
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
385
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
238
386
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
387
+ // Certificate management
239
388
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
240
389
  try {
241
390
  await fs.access(pairingFilePath, fs.constants.R_OK);
242
391
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
243
392
  const pairingFileJson = JSON.parse(pairingFileContent);
393
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
244
394
  if (isValidNumber(pairingFileJson.vendorId)) {
245
395
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
246
396
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -269,11 +419,13 @@ export class Matterbridge extends EventEmitter {
269
419
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
270
420
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
271
421
  }
422
+ // Override the passcode and discriminator if they are present in the pairing file
272
423
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
273
424
  this.passcode = pairingFileJson.passcode;
274
425
  this.discriminator = pairingFileJson.discriminator;
275
426
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
276
427
  }
428
+ // Set the certification for matter.js if it is present in the pairing file
277
429
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
278
430
  const { hexToBuffer } = await import('./utils/hex.js');
279
431
  this.certification = {
@@ -288,40 +440,43 @@ export class Matterbridge extends EventEmitter {
288
440
  catch (error) {
289
441
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
290
442
  }
443
+ // Store the passcode, discriminator and port in the node context
291
444
  await this.nodeContext.set('matterport', this.port);
292
445
  await this.nodeContext.set('matterpasscode', this.passcode);
293
446
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
294
447
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
448
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
295
449
  if (hasParameter('logger')) {
296
450
  const level = getParameter('logger');
297
451
  if (level === 'debug') {
298
- this.log.logLevel = "debug";
452
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
299
453
  }
300
454
  else if (level === 'info') {
301
- this.log.logLevel = "info";
455
+ this.log.logLevel = "info" /* LogLevel.INFO */;
302
456
  }
303
457
  else if (level === 'notice') {
304
- this.log.logLevel = "notice";
458
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
305
459
  }
306
460
  else if (level === 'warn') {
307
- this.log.logLevel = "warn";
461
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
308
462
  }
309
463
  else if (level === 'error') {
310
- this.log.logLevel = "error";
464
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
311
465
  }
312
466
  else if (level === 'fatal') {
313
- this.log.logLevel = "fatal";
467
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
314
468
  }
315
469
  else {
316
470
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
317
- this.log.logLevel = "info";
471
+ this.log.logLevel = "info" /* LogLevel.INFO */;
318
472
  }
319
473
  }
320
474
  else {
321
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
475
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
322
476
  }
323
477
  this.frontend.logLevel = this.log.logLevel;
324
478
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
479
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
325
480
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
326
481
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
327
482
  this.fileLogger = true;
@@ -330,6 +485,7 @@ export class Matterbridge extends EventEmitter {
330
485
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
331
486
  if (this.profile !== undefined)
332
487
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
488
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
333
489
  if (hasParameter('matterlogger')) {
334
490
  const level = getParameter('matterlogger');
335
491
  if (level === 'debug') {
@@ -359,11 +515,13 @@ export class Matterbridge extends EventEmitter {
359
515
  Logger.level = (await this.nodeContext.get('matterLogLevel', this.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
360
516
  }
361
517
  Logger.format = MatterLogFormat.ANSI;
518
+ // Create the logger for matter.js with file logging (context: matterFileLog)
362
519
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
363
520
  this.matterFileLogger = true;
364
521
  }
365
522
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
366
523
  this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterFileLogger}.`);
524
+ // Log network interfaces
367
525
  const networkInterfaces = os.networkInterfaces();
368
526
  const availableAddresses = Object.entries(networkInterfaces);
369
527
  const availableInterfaceNames = Object.keys(networkInterfaces);
@@ -376,6 +534,7 @@ export class Matterbridge extends EventEmitter {
376
534
  });
377
535
  }
378
536
  }
537
+ // Set the interface to use for matter server node mdnsInterface
379
538
  if (hasParameter('mdnsinterface')) {
380
539
  this.mdnsInterface = getParameter('mdnsinterface');
381
540
  }
@@ -384,6 +543,7 @@ export class Matterbridge extends EventEmitter {
384
543
  if (this.mdnsInterface === '')
385
544
  this.mdnsInterface = undefined;
386
545
  }
546
+ // Validate mdnsInterface
387
547
  if (this.mdnsInterface) {
388
548
  if (!availableInterfaceNames.includes(this.mdnsInterface)) {
389
549
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
@@ -396,6 +556,7 @@ export class Matterbridge extends EventEmitter {
396
556
  }
397
557
  if (this.mdnsInterface)
398
558
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
559
+ // Set the listeningAddressIpv4 for the matter commissioning server
399
560
  if (hasParameter('ipv4address')) {
400
561
  this.ipv4Address = getParameter('ipv4address');
401
562
  }
@@ -404,6 +565,7 @@ export class Matterbridge extends EventEmitter {
404
565
  if (this.ipv4Address === '')
405
566
  this.ipv4Address = undefined;
406
567
  }
568
+ // Validate ipv4address
407
569
  if (this.ipv4Address) {
408
570
  let isValid = false;
409
571
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -419,6 +581,7 @@ export class Matterbridge extends EventEmitter {
419
581
  await this.nodeContext.remove('matteripv4address');
420
582
  }
421
583
  }
584
+ // Set the listeningAddressIpv6 for the matter commissioning server
422
585
  if (hasParameter('ipv6address')) {
423
586
  this.ipv6Address = getParameter('ipv6address');
424
587
  }
@@ -427,6 +590,7 @@ export class Matterbridge extends EventEmitter {
427
590
  if (this.ipv6Address === '')
428
591
  this.ipv6Address = undefined;
429
592
  }
593
+ // Validate ipv6address
430
594
  if (this.ipv6Address) {
431
595
  let isValid = false;
432
596
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -435,6 +599,7 @@ export class Matterbridge extends EventEmitter {
435
599
  isValid = true;
436
600
  break;
437
601
  }
602
+ /* istanbul ignore next */
438
603
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
439
604
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
440
605
  isValid = true;
@@ -447,6 +612,7 @@ export class Matterbridge extends EventEmitter {
447
612
  await this.nodeContext.remove('matteripv6address');
448
613
  }
449
614
  }
615
+ // Initialize the virtual mode
450
616
  if (hasParameter('novirtual')) {
451
617
  this.virtualMode = 'disabled';
452
618
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -455,12 +621,17 @@ export class Matterbridge extends EventEmitter {
455
621
  this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
456
622
  }
457
623
  this.log.debug(`Virtual mode ${this.virtualMode}.`);
624
+ // Initialize PluginManager
458
625
  this.plugins.logLevel = this.log.logLevel;
459
626
  await this.plugins.loadFromStorage();
627
+ // Initialize DeviceManager
460
628
  this.devices.logLevel = this.log.logLevel;
629
+ // Get the plugins from node storage and create the plugins node storage contexts
461
630
  for (const plugin of this.plugins) {
462
631
  const packageJson = await this.plugins.parse(plugin);
463
632
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
633
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
634
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
464
635
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
465
636
  try {
466
637
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -483,6 +654,7 @@ export class Matterbridge extends EventEmitter {
483
654
  await plugin.nodeContext.set('description', plugin.description);
484
655
  await plugin.nodeContext.set('author', plugin.author);
485
656
  }
657
+ // Log system info and create .matterbridge directory
486
658
  await this.logNodeAndSystemInfo();
487
659
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
488
660
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -490,6 +662,7 @@ export class Matterbridge extends EventEmitter {
490
662
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
491
663
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
492
664
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
665
+ // Check node version and throw error
493
666
  const minNodeVersion = 20;
494
667
  const nodeVersion = process.versions.node;
495
668
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -497,10 +670,18 @@ export class Matterbridge extends EventEmitter {
497
670
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
498
671
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
499
672
  }
673
+ // Parse command line
500
674
  await this.parseCommandLine();
675
+ // Emit the initialize_completed event
501
676
  this.emit('initialize_completed');
502
677
  this.initialized = true;
503
678
  }
679
+ /**
680
+ * Parses the command line arguments and performs the corresponding actions.
681
+ *
682
+ * @private
683
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
684
+ */
504
685
  async parseCommandLine() {
505
686
  if (hasParameter('list')) {
506
687
  this.log.info(`│ Registered plugins (${this.plugins.length})`);
@@ -516,6 +697,19 @@ export class Matterbridge extends EventEmitter {
516
697
  }
517
698
  index++;
518
699
  }
700
+ /*
701
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
702
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
703
+ serializedRegisteredDevices?.forEach((device, index) => {
704
+ if (index !== serializedRegisteredDevices.length - 1) {
705
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
706
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
707
+ } else {
708
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
709
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
710
+ }
711
+ });
712
+ */
519
713
  this.shutdown = true;
520
714
  return;
521
715
  }
@@ -565,8 +759,10 @@ export class Matterbridge extends EventEmitter {
565
759
  this.shutdown = true;
566
760
  return;
567
761
  }
762
+ // Initialize frontend
568
763
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
569
764
  await this.frontend.start(getIntParameter('frontend'));
765
+ // Start the matter storage and create the matterbridge context
570
766
  try {
571
767
  await this.startMatterStorage();
572
768
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -580,18 +776,21 @@ export class Matterbridge extends EventEmitter {
580
776
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
581
777
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
582
778
  }
779
+ // Clear the matterbridge context if the reset parameter is set (bridge mode)
583
780
  if (hasParameter('reset') && getParameter('reset') === undefined) {
584
781
  this.initialized = true;
585
782
  await this.shutdownProcessAndReset();
586
783
  this.shutdown = true;
587
784
  return;
588
785
  }
786
+ // Clear matterbridge plugin context if the reset parameter is set (childbridge mode)
589
787
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
590
788
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
591
789
  const plugin = this.plugins.get(getParameter('reset'));
592
790
  if (plugin) {
593
791
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
594
792
  if (!matterStorageManager) {
793
+ /* istanbul ignore next */
595
794
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
596
795
  }
597
796
  else {
@@ -610,35 +809,42 @@ export class Matterbridge extends EventEmitter {
610
809
  this.shutdown = true;
611
810
  return;
612
811
  }
812
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
613
813
  clearTimeout(this.checkUpdateTimeout);
614
814
  this.checkUpdateTimeout = setTimeout(async () => {
615
815
  const { checkUpdates } = await import('./update.js');
616
816
  checkUpdates(this);
617
817
  }, 30 * 1000).unref();
818
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
618
819
  clearInterval(this.checkUpdateInterval);
619
820
  this.checkUpdateInterval = setInterval(async () => {
620
821
  const { checkUpdates } = await import('./update.js');
621
822
  checkUpdates(this);
622
823
  }, 12 * 60 * 60 * 1000).unref();
824
+ // Start the matterbridge in mode test
623
825
  if (hasParameter('test')) {
624
826
  this.bridgeMode = 'bridge';
625
827
  return;
626
828
  }
829
+ // Start the matterbridge in mode controller
627
830
  if (hasParameter('controller')) {
628
831
  this.bridgeMode = 'controller';
629
832
  await this.startController();
630
833
  return;
631
834
  }
835
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
632
836
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
633
837
  this.log.info('Setting default matterbridge start mode to bridge');
634
838
  await this.nodeContext?.set('bridgeMode', 'bridge');
635
839
  }
840
+ // Start matterbridge in bridge mode
636
841
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
637
842
  this.bridgeMode = 'bridge';
638
843
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
639
844
  await this.startBridge();
640
845
  return;
641
846
  }
847
+ // Start matterbridge in childbridge mode
642
848
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
643
849
  this.bridgeMode = 'childbridge';
644
850
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -646,10 +852,22 @@ export class Matterbridge extends EventEmitter {
646
852
  return;
647
853
  }
648
854
  }
855
+ /**
856
+ * Asynchronously loads and starts the registered plugins.
857
+ *
858
+ * This method is responsible for initializing and starting all enabled plugins.
859
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
860
+ *
861
+ * @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving.
862
+ * @param {boolean} [start] - If true, the method will start the plugins after loading them.
863
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
864
+ */
649
865
  async startPlugins(wait = false, start = true) {
866
+ // Check, load and start the plugins
650
867
  for (const plugin of this.plugins) {
651
868
  plugin.configJson = await this.plugins.loadConfig(plugin);
652
869
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
870
+ // Check if the plugin is available
653
871
  if (!(await this.plugins.resolve(plugin.path))) {
654
872
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
655
873
  plugin.enabled = false;
@@ -669,10 +887,16 @@ export class Matterbridge extends EventEmitter {
669
887
  if (wait)
670
888
  await this.plugins.load(plugin, start, 'Matterbridge is starting');
671
889
  else
672
- this.plugins.load(plugin, start, 'Matterbridge is starting');
890
+ this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
673
891
  }
674
892
  this.frontend.wssSendRefreshRequired('plugins');
675
893
  }
894
+ /**
895
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
896
+ * - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
897
+ * - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
898
+ * - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
899
+ */
676
900
  registerProcessHandlers() {
677
901
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
678
902
  process.removeAllListeners('uncaughtException');
@@ -699,6 +923,9 @@ export class Matterbridge extends EventEmitter {
699
923
  };
700
924
  process.on('SIGTERM', this.sigtermHandler);
701
925
  }
926
+ /**
927
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
928
+ */
702
929
  deregisterProcessHandlers() {
703
930
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
704
931
  if (this.exceptionHandler)
@@ -715,13 +942,18 @@ export class Matterbridge extends EventEmitter {
715
942
  process.off('SIGTERM', this.sigtermHandler);
716
943
  this.sigtermHandler = undefined;
717
944
  }
945
+ /**
946
+ * Logs the node and system information.
947
+ */
718
948
  async logNodeAndSystemInfo() {
949
+ // IP address information
719
950
  const networkInterfaces = os.networkInterfaces();
720
951
  this.systemInformation.interfaceName = '';
721
952
  this.systemInformation.ipv4Address = '';
722
953
  this.systemInformation.ipv6Address = '';
723
954
  this.systemInformation.macAddress = '';
724
955
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
956
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
725
957
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
726
958
  continue;
727
959
  if (!interfaceDetails) {
@@ -747,16 +979,18 @@ export class Matterbridge extends EventEmitter {
747
979
  break;
748
980
  }
749
981
  }
982
+ // Node information
750
983
  this.systemInformation.nodeVersion = process.versions.node;
751
984
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
752
985
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
753
986
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
987
+ // Host system information
754
988
  this.systemInformation.hostname = os.hostname();
755
989
  this.systemInformation.user = os.userInfo().username;
756
- this.systemInformation.osType = os.type();
757
- this.systemInformation.osRelease = os.release();
758
- this.systemInformation.osPlatform = os.platform();
759
- this.systemInformation.osArch = os.arch();
990
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
991
+ this.systemInformation.osRelease = os.release(); // Kernel version
992
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
993
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
760
994
  this.systemInformation.totalMemory = formatBytes(os.totalmem());
761
995
  this.systemInformation.freeMemory = formatBytes(os.freemem());
762
996
  this.systemInformation.systemUptime = formatUptime(os.uptime());
@@ -766,6 +1000,7 @@ export class Matterbridge extends EventEmitter {
766
1000
  this.systemInformation.rss = formatBytes(process.memoryUsage().rss);
767
1001
  this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
768
1002
  this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
1003
+ // Log the system information
769
1004
  this.log.debug('Host System Information:');
770
1005
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
771
1006
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -785,14 +1020,17 @@ export class Matterbridge extends EventEmitter {
785
1020
  this.log.debug(`- RSS: ${this.systemInformation.rss}`);
786
1021
  this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
787
1022
  this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
1023
+ // Log directories
788
1024
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
789
1025
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
790
1026
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
791
1027
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
792
1028
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1029
+ // Global node_modules directory
793
1030
  if (this.nodeContext)
794
1031
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
795
1032
  if (this.globalModulesDirectory === '') {
1033
+ // First run of Matterbridge so the node storage is empty
796
1034
  this.log.debug(`Getting global node_modules directory...`);
797
1035
  try {
798
1036
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -805,6 +1043,7 @@ export class Matterbridge extends EventEmitter {
805
1043
  }
806
1044
  }
807
1045
  else {
1046
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
808
1047
  this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
809
1048
  try {
810
1049
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -816,25 +1055,37 @@ export class Matterbridge extends EventEmitter {
816
1055
  this.log.error(`Error checking global node_modules directory: ${error}`);
817
1056
  }
818
1057
  }
1058
+ // Matterbridge version
819
1059
  this.log.debug(`Reading matterbridge package.json...`);
820
1060
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
821
1061
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
822
1062
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1063
+ // Matterbridge latest version (will be set in the checkUpdate function)
823
1064
  if (this.nodeContext)
824
1065
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
825
1066
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1067
+ // Matterbridge dev version (will be set in the checkUpdate function)
826
1068
  if (this.nodeContext)
827
1069
  this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
828
1070
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1071
+ // Frontend version
829
1072
  this.log.debug(`Reading frontend package.json...`);
830
1073
  const frontendPackageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
831
1074
  this.frontendVersion = frontendPackageJson.version;
832
1075
  this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
1076
+ // Current working directory
833
1077
  const currentDir = process.cwd();
834
1078
  this.log.debug(`Current Working Directory: ${currentDir}`);
1079
+ // Command line arguments (excluding 'node' and the script name)
835
1080
  const cmdArgs = process.argv.slice(2).join(' ');
836
1081
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
837
1082
  }
1083
+ /**
1084
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
1085
+ *
1086
+ * @param {LogLevel} logLevel The logger logLevel to set.
1087
+ * @returns {Promise<LogLevel>} A promise that resolves when the logLevel has been set.
1088
+ */
838
1089
  async setLogLevel(logLevel) {
839
1090
  this.log.logLevel = logLevel;
840
1091
  this.frontend.logLevel = logLevel;
@@ -844,58 +1095,87 @@ export class Matterbridge extends EventEmitter {
844
1095
  for (const plugin of this.plugins) {
845
1096
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
846
1097
  continue;
847
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
848
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
849
- }
850
- let callbackLogLevel = "notice";
851
- if (logLevel === "info" || Logger.level === MatterLogLevel.INFO)
852
- callbackLogLevel = "info";
853
- if (logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG)
854
- callbackLogLevel = "debug";
1098
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel;
1099
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel);
1100
+ }
1101
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
1102
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
1103
+ if (logLevel === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
1104
+ callbackLogLevel = "info" /* LogLevel.INFO */;
1105
+ if (logLevel === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
1106
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
855
1107
  AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
856
1108
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
857
1109
  return logLevel;
858
1110
  }
1111
+ /**
1112
+ * Get the current logger logLevel.
1113
+ *
1114
+ * @returns {LogLevel} The current logger logLevel.
1115
+ */
859
1116
  getLogLevel() {
860
1117
  return this.log.logLevel;
861
1118
  }
1119
+ /**
1120
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1121
+ * It also logs to file (matter.log) if fileLogger is true.
1122
+ *
1123
+ * @param {boolean} fileLogger - Whether to log to file or not.
1124
+ * @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
1125
+ */
862
1126
  createDestinationMatterLogger(fileLogger) {
863
- this.matterLog.logNameColor = '\x1b[34m';
1127
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
864
1128
  if (fileLogger) {
865
1129
  this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
866
1130
  }
867
1131
  return (text, message) => {
1132
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
868
1133
  const logger = text.slice(44, 44 + 20).trim();
869
1134
  const msg = text.slice(65);
870
1135
  this.matterLog.logName = logger;
871
1136
  switch (message.level) {
872
1137
  case MatterLogLevel.DEBUG:
873
- this.matterLog.log("debug", msg);
1138
+ this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
874
1139
  break;
875
1140
  case MatterLogLevel.INFO:
876
- this.matterLog.log("info", msg);
1141
+ this.matterLog.log("info" /* LogLevel.INFO */, msg);
877
1142
  break;
878
1143
  case MatterLogLevel.NOTICE:
879
- this.matterLog.log("notice", msg);
1144
+ this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
880
1145
  break;
881
1146
  case MatterLogLevel.WARN:
882
- this.matterLog.log("warn", msg);
1147
+ this.matterLog.log("warn" /* LogLevel.WARN */, msg);
883
1148
  break;
884
1149
  case MatterLogLevel.ERROR:
885
- this.matterLog.log("error", msg);
1150
+ this.matterLog.log("error" /* LogLevel.ERROR */, msg);
886
1151
  break;
887
1152
  case MatterLogLevel.FATAL:
888
- this.matterLog.log("fatal", msg);
1153
+ this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
889
1154
  break;
890
1155
  }
891
1156
  };
892
1157
  }
1158
+ /**
1159
+ * Restarts the process by exiting the current instance and loading a new instance (/api/restart).
1160
+ *
1161
+ * @returns {Promise<void>} A promise that resolves when the restart is completed.
1162
+ */
893
1163
  async restartProcess() {
894
1164
  await this.cleanup('restarting...', true);
895
1165
  }
1166
+ /**
1167
+ * Shut down the process (/api/shutdown).
1168
+ *
1169
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1170
+ */
896
1171
  async shutdownProcess() {
897
1172
  await this.cleanup('shutting down...', false);
898
1173
  }
1174
+ /**
1175
+ * Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
1176
+ *
1177
+ * @returns {Promise<void>} A promise that resolves when the update is completed.
1178
+ */
899
1179
  async updateProcess() {
900
1180
  this.log.info('Updating matterbridge...');
901
1181
  try {
@@ -909,6 +1189,13 @@ export class Matterbridge extends EventEmitter {
909
1189
  this.frontend.wssSendRestartRequired();
910
1190
  await this.cleanup('updating...', false);
911
1191
  }
1192
+ /**
1193
+ * Unregister all devices and shut down the process (/api/unregister).
1194
+ *
1195
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1196
+ *
1197
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1198
+ */
912
1199
  async unregisterAndShutdownProcess(timeout = 1000) {
913
1200
  this.log.info('Unregistering all devices and shutting down...');
914
1201
  for (const plugin of this.plugins.array()) {
@@ -920,46 +1207,71 @@ export class Matterbridge extends EventEmitter {
920
1207
  await this.removeAllBridgedEndpoints(plugin.name, 100);
921
1208
  }
922
1209
  this.log.debug('Waiting for the MessageExchange to finish...');
923
- await wait(timeout);
1210
+ await wait(timeout); // Wait for MessageExchange to finish
924
1211
  this.log.debug('Cleaning up and shutting down...');
925
1212
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
926
1213
  }
1214
+ /**
1215
+ * Reset commissioning and shut down the process (/api/reset).
1216
+ *
1217
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1218
+ */
927
1219
  async shutdownProcessAndReset() {
928
1220
  await this.cleanup('shutting down with reset...', false);
929
1221
  }
1222
+ /**
1223
+ * Factory reset and shut down the process (/api/factory-reset).
1224
+ *
1225
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1226
+ */
930
1227
  async shutdownProcessAndFactoryReset() {
931
1228
  await this.cleanup('shutting down with factory reset...', false);
932
1229
  }
1230
+ /**
1231
+ * Cleans up the Matterbridge instance.
1232
+ *
1233
+ * @param {string} message - The cleanup message.
1234
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1235
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1236
+ *
1237
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1238
+ */
933
1239
  async cleanup(message, restart = false, timeout = 1000) {
934
1240
  if (this.initialized && !this.hasCleanupStarted) {
935
1241
  this.emit('cleanup_started');
936
1242
  this.hasCleanupStarted = true;
937
1243
  this.log.info(message);
1244
+ // Clear the start matter interval
938
1245
  if (this.startMatterInterval) {
939
1246
  clearInterval(this.startMatterInterval);
940
1247
  this.startMatterInterval = undefined;
941
1248
  this.log.debug('Start matter interval cleared');
942
1249
  }
1250
+ // Clear the check update timeout
943
1251
  if (this.checkUpdateTimeout) {
944
1252
  clearTimeout(this.checkUpdateTimeout);
945
1253
  this.checkUpdateTimeout = undefined;
946
1254
  this.log.debug('Check update timeout cleared');
947
1255
  }
1256
+ // Clear the check update interval
948
1257
  if (this.checkUpdateInterval) {
949
1258
  clearInterval(this.checkUpdateInterval);
950
1259
  this.checkUpdateInterval = undefined;
951
1260
  this.log.debug('Check update interval cleared');
952
1261
  }
1262
+ // Clear the configure timeout
953
1263
  if (this.configureTimeout) {
954
1264
  clearTimeout(this.configureTimeout);
955
1265
  this.configureTimeout = undefined;
956
1266
  this.log.debug('Matterbridge configure timeout cleared');
957
1267
  }
1268
+ // Clear the reachability timeout
958
1269
  if (this.reachabilityTimeout) {
959
1270
  clearTimeout(this.reachabilityTimeout);
960
1271
  this.reachabilityTimeout = undefined;
961
1272
  this.log.debug('Matterbridge reachability timeout cleared');
962
1273
  }
1274
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
963
1275
  for (const plugin of this.plugins) {
964
1276
  if (!plugin.enabled || plugin.error)
965
1277
  continue;
@@ -970,6 +1282,7 @@ export class Matterbridge extends EventEmitter {
970
1282
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
971
1283
  }
972
1284
  }
1285
+ // Stop matter server nodes
973
1286
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
974
1287
  if (timeout > 0) {
975
1288
  this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
@@ -996,6 +1309,7 @@ export class Matterbridge extends EventEmitter {
996
1309
  }
997
1310
  }
998
1311
  this.log.notice('Stopped matter server nodes');
1312
+ // Matter commisioning reset
999
1313
  if (message === 'shutting down with reset...') {
1000
1314
  this.log.info('Resetting Matterbridge commissioning information...');
1001
1315
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1005,6 +1319,7 @@ export class Matterbridge extends EventEmitter {
1005
1319
  await this.matterbridgeContext?.clearAll();
1006
1320
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1007
1321
  }
1322
+ // Unregister all devices
1008
1323
  if (message === 'unregistered all devices and shutting down...') {
1009
1324
  if (this.bridgeMode === 'bridge') {
1010
1325
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1022,16 +1337,35 @@ export class Matterbridge extends EventEmitter {
1022
1337
  }
1023
1338
  this.log.info('Matter storage reset done!');
1024
1339
  }
1340
+ // Stop matter storage
1025
1341
  await this.stopMatterStorage();
1342
+ // Stop the frontend
1026
1343
  await this.frontend.stop();
1027
1344
  this.frontend.destroy();
1345
+ // Close PluginManager and DeviceManager
1028
1346
  this.plugins.destroy();
1029
1347
  this.devices.destroy();
1348
+ // Stop thread messaging server
1030
1349
  this.server.close();
1350
+ // Close the matterbridge node storage and context
1031
1351
  if (this.nodeStorage && this.nodeContext) {
1352
+ /*
1353
+ TODO: Implement serialization of registered devices
1354
+ this.log.info('Saving registered devices...');
1355
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1356
+ this.devices.forEach(async (device) => {
1357
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1358
+ this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1359
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1360
+ });
1361
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1362
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1363
+ */
1364
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1032
1365
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1033
1366
  await this.nodeContext.close();
1034
1367
  this.nodeContext = undefined;
1368
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1035
1369
  for (const plugin of this.plugins) {
1036
1370
  if (plugin.nodeContext) {
1037
1371
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1048,8 +1382,10 @@ export class Matterbridge extends EventEmitter {
1048
1382
  }
1049
1383
  this.plugins.clear();
1050
1384
  this.devices.clear();
1385
+ // Factory reset
1051
1386
  if (message === 'shutting down with factory reset...') {
1052
1387
  try {
1388
+ // Delete matter storage directory with its subdirectories and backup
1053
1389
  const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
1054
1390
  this.log.info(`Removing matter storage directory: ${dir}`);
1055
1391
  await fs.rm(dir, { recursive: true });
@@ -1058,11 +1394,13 @@ export class Matterbridge extends EventEmitter {
1058
1394
  await fs.rm(backup, { recursive: true });
1059
1395
  }
1060
1396
  catch (error) {
1397
+ // istanbul ignore next if
1061
1398
  if (error instanceof Error && error.code !== 'ENOENT') {
1062
1399
  this.log.error(`Error removing matter storage directory: ${error}`);
1063
1400
  }
1064
1401
  }
1065
1402
  try {
1403
+ // Delete matterbridge storage directory with its subdirectories and backup
1066
1404
  const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
1067
1405
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1068
1406
  await fs.rm(dir, { recursive: true });
@@ -1071,18 +1409,20 @@ export class Matterbridge extends EventEmitter {
1071
1409
  await fs.rm(backup, { recursive: true });
1072
1410
  }
1073
1411
  catch (error) {
1412
+ // istanbul ignore next if
1074
1413
  if (error instanceof Error && error.code !== 'ENOENT') {
1075
1414
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1076
1415
  }
1077
1416
  }
1078
1417
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1079
1418
  }
1419
+ // Deregisters the process handlers
1080
1420
  this.deregisterProcessHandlers();
1081
1421
  if (restart) {
1082
1422
  if (message === 'updating...') {
1083
1423
  this.log.info('Cleanup completed. Updating...');
1084
1424
  Matterbridge.instance = undefined;
1085
- this.emit('update');
1425
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1086
1426
  }
1087
1427
  else if (message === 'restarting...') {
1088
1428
  this.log.info('Cleanup completed. Restarting...');
@@ -1111,7 +1451,14 @@ export class Matterbridge extends EventEmitter {
1111
1451
  this.log.debug('Cleanup already started...');
1112
1452
  }
1113
1453
  }
1454
+ /**
1455
+ * Starts the Matterbridge in bridge mode.
1456
+ *
1457
+ * @private
1458
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1459
+ */
1114
1460
  async startBridge() {
1461
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1115
1462
  if (!this.matterStorageManager)
1116
1463
  throw new Error('No storage manager initialized');
1117
1464
  if (!this.matterbridgeContext)
@@ -1150,13 +1497,16 @@ export class Matterbridge extends EventEmitter {
1150
1497
  clearInterval(this.startMatterInterval);
1151
1498
  this.startMatterInterval = undefined;
1152
1499
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1153
- this.startServerNode(this.serverNode);
1500
+ // Start the Matter server node
1501
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1502
+ // Start the Matter server node of single devices in mode 'server'
1154
1503
  for (const device of this.devices.array()) {
1155
1504
  if (device.mode === 'server' && device.serverNode) {
1156
1505
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1157
- this.startServerNode(device.serverNode);
1506
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1158
1507
  }
1159
1508
  }
1509
+ // Configure the plugins
1160
1510
  this.configureTimeout = setTimeout(async () => {
1161
1511
  for (const plugin of this.plugins.array()) {
1162
1512
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1174,28 +1524,40 @@ export class Matterbridge extends EventEmitter {
1174
1524
  }
1175
1525
  this.frontend.wssSendRefreshRequired('plugins');
1176
1526
  }, 30 * 1000).unref();
1527
+ // Setting reachability to true
1177
1528
  this.reachabilityTimeout = setTimeout(() => {
1178
1529
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1179
1530
  if (this.aggregatorNode)
1180
1531
  this.setAggregatorReachability(this.aggregatorNode, true);
1181
1532
  }, 60 * 1000).unref();
1533
+ // Logger.get('LogServerNode').info(this.serverNode);
1182
1534
  this.emit('bridge_started');
1183
1535
  this.log.notice('Matterbridge bridge started successfully');
1184
1536
  this.frontend.wssSendRefreshRequired('settings');
1185
1537
  this.frontend.wssSendRefreshRequired('plugins');
1186
1538
  }, this.startMatterIntervalMs);
1187
1539
  }
1540
+ /**
1541
+ * Starts the Matterbridge in childbridge mode.
1542
+ *
1543
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1544
+ *
1545
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1546
+ */
1188
1547
  async startChildbridge(delay = 1000) {
1189
1548
  if (!this.matterStorageManager)
1190
1549
  throw new Error('No storage manager initialized');
1550
+ // Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
1191
1551
  this.log.debug('Loading all plugins in childbridge mode...');
1192
1552
  await this.startPlugins(true, false);
1553
+ // Create server nodes for DynamicPlatform plugins and start all plugins in the background
1193
1554
  this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
1194
1555
  for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
1195
1556
  if (plugin.type === 'DynamicPlatform')
1196
1557
  await this.createDynamicPlugin(plugin);
1197
- this.plugins.start(plugin, 'Matterbridge is starting');
1558
+ this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
1198
1559
  }
1560
+ // Start the Matterbridge in childbridge mode when all plugins are loaded and started
1199
1561
  this.log.debug('Starting start matter interval in childbridge mode...');
1200
1562
  let failCount = 0;
1201
1563
  this.startMatterInterval = setInterval(async () => {
@@ -1229,8 +1591,9 @@ export class Matterbridge extends EventEmitter {
1229
1591
  clearInterval(this.startMatterInterval);
1230
1592
  this.startMatterInterval = undefined;
1231
1593
  if (delay > 0)
1232
- await wait(delay);
1594
+ await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1233
1595
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1596
+ // Configure the plugins
1234
1597
  this.configureTimeout = setTimeout(async () => {
1235
1598
  for (const plugin of this.plugins.array()) {
1236
1599
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1255,6 +1618,7 @@ export class Matterbridge extends EventEmitter {
1255
1618
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1256
1619
  continue;
1257
1620
  }
1621
+ // istanbul ignore next if cause is just a safety check
1258
1622
  if (!plugin.serverNode) {
1259
1623
  this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1260
1624
  continue;
@@ -1267,28 +1631,252 @@ export class Matterbridge extends EventEmitter {
1267
1631
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1268
1632
  continue;
1269
1633
  }
1270
- this.startServerNode(plugin.serverNode);
1634
+ // Start the Matter server node
1635
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1636
+ // Setting reachability to true
1271
1637
  plugin.reachabilityTimeout = setTimeout(() => {
1272
1638
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1273
1639
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1274
1640
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1275
1641
  }, 60 * 1000).unref();
1276
1642
  }
1643
+ // Start the Matter server node of single devices in mode 'server'
1277
1644
  for (const device of this.devices.array()) {
1278
1645
  if (device.mode === 'server' && device.serverNode) {
1279
1646
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1280
- this.startServerNode(device.serverNode);
1647
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1281
1648
  }
1282
1649
  }
1650
+ // Logger.get('LogServerNode').info(this.serverNode);
1283
1651
  this.emit('childbridge_started');
1284
1652
  this.log.notice('Matterbridge childbridge started successfully');
1285
1653
  this.frontend.wssSendRefreshRequired('settings');
1286
1654
  this.frontend.wssSendRefreshRequired('plugins');
1287
1655
  }, this.startMatterIntervalMs);
1288
1656
  }
1657
+ /**
1658
+ * Starts the Matterbridge controller.
1659
+ *
1660
+ * @private
1661
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1662
+ */
1289
1663
  async startController() {
1664
+ /*
1665
+ if (!this.matterStorageManager) {
1666
+ this.log.error('No storage manager initialized');
1667
+ await this.cleanup('No storage manager initialized');
1668
+ return;
1669
+ }
1670
+ this.log.info('Creating context: mattercontrollerContext');
1671
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1672
+ if (!this.controllerContext) {
1673
+ this.log.error('No storage context mattercontrollerContext initialized');
1674
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1675
+ return;
1676
+ }
1677
+
1678
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1679
+ this.matterServer = await this.createMatterServer(this.storageManager);
1680
+ this.log.info('Creating matter commissioning controller');
1681
+ this.commissioningController = new CommissioningController({
1682
+ autoConnect: false,
1683
+ });
1684
+ this.log.info('Adding matter commissioning controller to matter server');
1685
+ await this.matterServer.addCommissioningController(this.commissioningController);
1686
+
1687
+ this.log.info('Starting matter server');
1688
+ await this.matterServer.start();
1689
+ this.log.info('Matter server started');
1690
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1691
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1692
+ regulatoryCountryCode: 'XX',
1693
+ };
1694
+ const commissioningController = new CommissioningController({
1695
+ environment: {
1696
+ environment,
1697
+ id: uniqueId,
1698
+ },
1699
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1700
+ adminFabricLabel,
1701
+ });
1702
+
1703
+ if (hasParameter('pairingcode')) {
1704
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1705
+ const pairingCode = getParameter('pairingcode');
1706
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1707
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1708
+
1709
+ let longDiscriminator, setupPin, shortDiscriminator;
1710
+ if (pairingCode !== undefined) {
1711
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1712
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1713
+ longDiscriminator = undefined;
1714
+ setupPin = pairingCodeCodec.passcode;
1715
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1716
+ } else {
1717
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1718
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1719
+ setupPin = this.controllerContext.get('pin', 20202021);
1720
+ }
1721
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1722
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1723
+ }
1724
+
1725
+ const options = {
1726
+ commissioning: commissioningOptions,
1727
+ discovery: {
1728
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1729
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1730
+ },
1731
+ passcode: setupPin,
1732
+ } as NodeCommissioningOptions;
1733
+ this.log.info('Commissioning with options:', options);
1734
+ const nodeId = await this.commissioningController.commissionNode(options);
1735
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1736
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1737
+ } // (hasParameter('pairingcode'))
1738
+
1739
+ if (hasParameter('unpairall')) {
1740
+ this.log.info('***Commissioning controller unpairing all nodes...');
1741
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1742
+ for (const nodeId of nodeIds) {
1743
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1744
+ await this.commissioningController.removeNode(nodeId);
1745
+ }
1746
+ return;
1747
+ }
1748
+
1749
+ if (hasParameter('discover')) {
1750
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1751
+ // console.log(discover);
1752
+ }
1753
+
1754
+ if (!this.commissioningController.isCommissioned()) {
1755
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1756
+ return;
1757
+ }
1758
+
1759
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1760
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1761
+ for (const nodeId of nodeIds) {
1762
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1763
+
1764
+ const node = await this.commissioningController.connectNode(nodeId, {
1765
+ autoSubscribe: false,
1766
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1767
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1768
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1769
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1770
+ stateInformationCallback: (peerNodeId, info) => {
1771
+ switch (info) {
1772
+ case NodeStateInformation.Connected:
1773
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1774
+ break;
1775
+ case NodeStateInformation.Disconnected:
1776
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1777
+ break;
1778
+ case NodeStateInformation.Reconnecting:
1779
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1780
+ break;
1781
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1782
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1783
+ break;
1784
+ case NodeStateInformation.StructureChanged:
1785
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1786
+ break;
1787
+ case NodeStateInformation.Decommissioned:
1788
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1789
+ break;
1790
+ default:
1791
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1792
+ break;
1793
+ }
1794
+ },
1795
+ });
1796
+
1797
+ node.logStructure();
1798
+
1799
+ // Get the interaction client
1800
+ this.log.info('Getting the interaction client');
1801
+ const interactionClient = await node.getInteractionClient();
1802
+ let cluster;
1803
+ let attributes;
1804
+
1805
+ // Log BasicInformationCluster
1806
+ cluster = BasicInformationCluster;
1807
+ attributes = await interactionClient.getMultipleAttributes({
1808
+ attributes: [{ clusterId: cluster.id }],
1809
+ });
1810
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1811
+ attributes.forEach((attribute) => {
1812
+ this.log.info(
1813
+ `- 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}`,
1814
+ );
1815
+ });
1816
+
1817
+ // Log PowerSourceCluster
1818
+ cluster = PowerSourceCluster;
1819
+ attributes = await interactionClient.getMultipleAttributes({
1820
+ attributes: [{ clusterId: cluster.id }],
1821
+ });
1822
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1823
+ attributes.forEach((attribute) => {
1824
+ this.log.info(
1825
+ `- 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}`,
1826
+ );
1827
+ });
1828
+
1829
+ // Log ThreadNetworkDiagnostics
1830
+ cluster = ThreadNetworkDiagnosticsCluster;
1831
+ attributes = await interactionClient.getMultipleAttributes({
1832
+ attributes: [{ clusterId: cluster.id }],
1833
+ });
1834
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1835
+ attributes.forEach((attribute) => {
1836
+ this.log.info(
1837
+ `- 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}`,
1838
+ );
1839
+ });
1840
+
1841
+ // Log SwitchCluster
1842
+ cluster = SwitchCluster;
1843
+ attributes = await interactionClient.getMultipleAttributes({
1844
+ attributes: [{ clusterId: cluster.id }],
1845
+ });
1846
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1847
+ attributes.forEach((attribute) => {
1848
+ this.log.info(
1849
+ `- 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}`,
1850
+ );
1851
+ });
1852
+
1853
+ this.log.info('Subscribing to all attributes and events');
1854
+ await node.subscribeAllAttributesAndEvents({
1855
+ ignoreInitialTriggers: false,
1856
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1857
+ this.log.info(
1858
+ `***${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}`,
1859
+ ),
1860
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1861
+ this.log.info(
1862
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1863
+ );
1864
+ },
1865
+ });
1866
+ this.log.info('Subscribed to all attributes and events');
1867
+ }
1868
+ */
1290
1869
  }
1870
+ /** */
1871
+ /** Matter.js methods */
1872
+ /** */
1873
+ /**
1874
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1875
+ *
1876
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1877
+ */
1291
1878
  async startMatterStorage() {
1879
+ // Setup Matter storage
1292
1880
  this.log.info(`Starting matter node storage...`);
1293
1881
  this.matterStorageService = this.environment.get(StorageService);
1294
1882
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1296,8 +1884,17 @@ export class Matterbridge extends EventEmitter {
1296
1884
  this.log.info('Matter node storage manager "Matterbridge" created');
1297
1885
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1298
1886
  this.log.info('Matter node storage started');
1887
+ // Backup matter storage since it is created/opened correctly
1299
1888
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
1300
1889
  }
1890
+ /**
1891
+ * Makes a backup copy of the specified matter storage directory.
1892
+ *
1893
+ * @param {string} storageName - The name of the storage directory to be backed up.
1894
+ * @param {string} backupName - The name of the backup directory to be created.
1895
+ * @private
1896
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1897
+ */
1301
1898
  async backupMatterStorage(storageName, backupName) {
1302
1899
  this.log.info('Creating matter node storage backup...');
1303
1900
  try {
@@ -1308,6 +1905,11 @@ export class Matterbridge extends EventEmitter {
1308
1905
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1309
1906
  }
1310
1907
  }
1908
+ /**
1909
+ * Stops the matter storage.
1910
+ *
1911
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1912
+ */
1311
1913
  async stopMatterStorage() {
1312
1914
  this.log.info('Closing matter node storage...');
1313
1915
  await this.matterStorageManager?.close();
@@ -1316,6 +1918,20 @@ export class Matterbridge extends EventEmitter {
1316
1918
  this.matterbridgeContext = undefined;
1317
1919
  this.log.info('Matter node storage closed');
1318
1920
  }
1921
+ /**
1922
+ * Creates a server node storage context.
1923
+ *
1924
+ * @param {string} storeId - The storeId.
1925
+ * @param {string} deviceName - The name of the device.
1926
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1927
+ * @param {number} vendorId - The vendor ID.
1928
+ * @param {string} vendorName - The vendor name.
1929
+ * @param {number} productId - The product ID.
1930
+ * @param {string} productName - The product name.
1931
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1932
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
1933
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1934
+ */
1319
1935
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1320
1936
  const { randomBytes } = await import('node:crypto');
1321
1937
  if (!this.matterStorageService)
@@ -1355,6 +1971,15 @@ export class Matterbridge extends EventEmitter {
1355
1971
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1356
1972
  return storageContext;
1357
1973
  }
1974
+ /**
1975
+ * Creates a server node.
1976
+ *
1977
+ * @param {StorageContext} storageContext - The storage context for the server node.
1978
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
1979
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
1980
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
1981
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1982
+ */
1358
1983
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1359
1984
  const storeId = await storageContext.get('storeId');
1360
1985
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1364,24 +1989,37 @@ export class Matterbridge extends EventEmitter {
1364
1989
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1365
1990
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1366
1991
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1992
+ /**
1993
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1994
+ */
1367
1995
  const serverNode = await ServerNode.create({
1996
+ // Required: Give the Node a unique ID which is used to store the state of this node
1368
1997
  id: storeId,
1998
+ // Provide Network relevant configuration like the port
1999
+ // Optional when operating only one device on a host, Default port is 5540
1369
2000
  network: {
1370
2001
  listeningAddressIpv4: this.ipv4Address,
1371
2002
  listeningAddressIpv6: this.ipv6Address,
1372
2003
  port,
1373
2004
  },
2005
+ // Provide the certificate for the device
1374
2006
  operationalCredentials: {
1375
2007
  certification: this.certification,
1376
2008
  },
2009
+ // Provide Commissioning relevant settings
2010
+ // Optional for development/testing purposes
1377
2011
  commissioning: {
1378
2012
  passcode,
1379
2013
  discriminator,
1380
2014
  },
2015
+ // Provide Node announcement settings
2016
+ // Optional: If Ommitted some development defaults are used
1381
2017
  productDescription: {
1382
2018
  name: await storageContext.get('deviceName'),
1383
2019
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1384
2020
  },
2021
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
2022
+ // Optional: If Omitted some development defaults are used
1385
2023
  basicInformation: {
1386
2024
  vendorId: VendorId(await storageContext.get('vendorId')),
1387
2025
  vendorName: await storageContext.get('vendorName'),
@@ -1398,17 +2036,23 @@ export class Matterbridge extends EventEmitter {
1398
2036
  reachable: true,
1399
2037
  },
1400
2038
  });
2039
+ /**
2040
+ * This event is triggered when the device is initially commissioned successfully.
2041
+ * This means: It is added to the first fabric.
2042
+ */
1401
2043
  serverNode.lifecycle.commissioned.on(() => {
1402
2044
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1403
2045
  this.advertisingNodes.delete(storeId);
1404
2046
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1405
2047
  });
2048
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1406
2049
  serverNode.lifecycle.decommissioned.on(() => {
1407
2050
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1408
2051
  this.advertisingNodes.delete(storeId);
1409
2052
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1410
2053
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1411
2054
  });
2055
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1412
2056
  serverNode.lifecycle.online.on(async () => {
1413
2057
  this.log.notice(`Server node for ${storeId} is online`);
1414
2058
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1419,13 +2063,16 @@ export class Matterbridge extends EventEmitter {
1419
2063
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1420
2064
  }
1421
2065
  else {
2066
+ // istanbul ignore next
1422
2067
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2068
+ // istanbul ignore next
1423
2069
  this.advertisingNodes.delete(storeId);
1424
2070
  }
1425
2071
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1426
2072
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1427
2073
  this.emit('online', storeId);
1428
2074
  });
2075
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1429
2076
  serverNode.lifecycle.offline.on(() => {
1430
2077
  this.log.notice(`Server node for ${storeId} is offline`);
1431
2078
  this.advertisingNodes.delete(storeId);
@@ -1433,11 +2080,15 @@ export class Matterbridge extends EventEmitter {
1433
2080
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1434
2081
  this.emit('offline', storeId);
1435
2082
  });
2083
+ /**
2084
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2085
+ * information is needed.
2086
+ */
1436
2087
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1437
2088
  let action = '';
1438
2089
  switch (fabricAction) {
1439
2090
  case FabricAction.Added:
1440
- this.advertisingNodes.delete(storeId);
2091
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1441
2092
  action = 'added';
1442
2093
  break;
1443
2094
  case FabricAction.Removed:
@@ -1450,14 +2101,22 @@ export class Matterbridge extends EventEmitter {
1450
2101
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1451
2102
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1452
2103
  });
2104
+ /**
2105
+ * This event is triggered when an operative new session was opened by a Controller.
2106
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2107
+ */
1453
2108
  serverNode.events.sessions.opened.on((session) => {
1454
2109
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1455
2110
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1456
2111
  });
2112
+ /**
2113
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2114
+ */
1457
2115
  serverNode.events.sessions.closed.on((session) => {
1458
2116
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1459
2117
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1460
2118
  });
2119
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1461
2120
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1462
2121
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1463
2122
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
@@ -1465,6 +2124,12 @@ export class Matterbridge extends EventEmitter {
1465
2124
  this.log.info(`Created server node for ${storeId}`);
1466
2125
  return serverNode;
1467
2126
  }
2127
+ /**
2128
+ * Gets the matter sanitized data of the specified server node.
2129
+ *
2130
+ * @param {ServerNode} [serverNode] - The server node to start.
2131
+ * @returns {ApiMatter} The sanitized data of the server node.
2132
+ */
1468
2133
  getServerNodeData(serverNode) {
1469
2134
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1470
2135
  return {
@@ -1481,12 +2146,25 @@ export class Matterbridge extends EventEmitter {
1481
2146
  serialNumber: serverNode.state.basicInformation.serialNumber,
1482
2147
  };
1483
2148
  }
2149
+ /**
2150
+ * Starts the specified server node.
2151
+ *
2152
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2153
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2154
+ */
1484
2155
  async startServerNode(matterServerNode) {
1485
2156
  if (!matterServerNode)
1486
2157
  return;
1487
2158
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1488
2159
  await matterServerNode.start();
1489
2160
  }
2161
+ /**
2162
+ * Stops the specified server node.
2163
+ *
2164
+ * @param {ServerNode} matterServerNode - The server node to stop.
2165
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2166
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2167
+ */
1490
2168
  async stopServerNode(matterServerNode, timeout = 30000) {
1491
2169
  if (!matterServerNode)
1492
2170
  return;
@@ -1499,12 +2177,25 @@ export class Matterbridge extends EventEmitter {
1499
2177
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1500
2178
  }
1501
2179
  }
2180
+ /**
2181
+ * Creates an aggregator node with the specified storage context.
2182
+ *
2183
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2184
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2185
+ */
1502
2186
  async createAggregatorNode(storageContext) {
1503
2187
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1504
2188
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1505
2189
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1506
2190
  return aggregatorNode;
1507
2191
  }
2192
+ /**
2193
+ * Creates and configures the server node for an accessory plugin for a given device.
2194
+ *
2195
+ * @param {Plugin} plugin - The plugin to configure.
2196
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2197
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2198
+ */
1508
2199
  async createAccessoryPlugin(plugin, device) {
1509
2200
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1510
2201
  plugin.locked = true;
@@ -1516,6 +2207,12 @@ export class Matterbridge extends EventEmitter {
1516
2207
  await plugin.serverNode.add(device);
1517
2208
  }
1518
2209
  }
2210
+ /**
2211
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
2212
+ *
2213
+ * @param {Plugin} plugin - The plugin to configure.
2214
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
2215
+ */
1519
2216
  async createDynamicPlugin(plugin) {
1520
2217
  if (!plugin.locked) {
1521
2218
  plugin.locked = true;
@@ -1528,6 +2225,13 @@ export class Matterbridge extends EventEmitter {
1528
2225
  await plugin.serverNode.add(plugin.aggregatorNode);
1529
2226
  }
1530
2227
  }
2228
+ /**
2229
+ * Creates and configures the server node for a single not bridged device.
2230
+ *
2231
+ * @param {Plugin} plugin - The plugin to configure.
2232
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2233
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2234
+ */
1531
2235
  async createDeviceServerNode(plugin, device) {
1532
2236
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1533
2237
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1538,7 +2242,15 @@ export class Matterbridge extends EventEmitter {
1538
2242
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1539
2243
  }
1540
2244
  }
2245
+ /**
2246
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2247
+ *
2248
+ * @param {string} pluginName - The name of the plugin.
2249
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2250
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2251
+ */
1541
2252
  async addBridgedEndpoint(pluginName, device) {
2253
+ // Check if the plugin is registered
1542
2254
  const plugin = this.plugins.get(pluginName);
1543
2255
  if (!plugin) {
1544
2256
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1558,6 +2270,7 @@ export class Matterbridge extends EventEmitter {
1558
2270
  }
1559
2271
  else if (this.bridgeMode === 'bridge') {
1560
2272
  if (device.mode === 'matter') {
2273
+ // Register and add the device to the matterbridge server node
1561
2274
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1562
2275
  if (!this.serverNode) {
1563
2276
  this.log.error('Server node not found for Matterbridge');
@@ -1574,6 +2287,7 @@ export class Matterbridge extends EventEmitter {
1574
2287
  }
1575
2288
  }
1576
2289
  else {
2290
+ // Register and add the device to the matterbridge aggregator node
1577
2291
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1578
2292
  if (!this.aggregatorNode) {
1579
2293
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1591,6 +2305,7 @@ export class Matterbridge extends EventEmitter {
1591
2305
  }
1592
2306
  }
1593
2307
  else if (this.bridgeMode === 'childbridge') {
2308
+ // Register and add the device to the plugin server node
1594
2309
  if (plugin.type === 'AccessoryPlatform') {
1595
2310
  try {
1596
2311
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1614,10 +2329,12 @@ export class Matterbridge extends EventEmitter {
1614
2329
  return;
1615
2330
  }
1616
2331
  }
2332
+ // Register and add the device to the plugin aggregator node
1617
2333
  if (plugin.type === 'DynamicPlatform') {
1618
2334
  try {
1619
2335
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1620
2336
  await this.createDynamicPlugin(plugin);
2337
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1621
2338
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1622
2339
  if (!plugin.aggregatorNode) {
1623
2340
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1638,17 +2355,28 @@ export class Matterbridge extends EventEmitter {
1638
2355
  }
1639
2356
  if (plugin.registeredDevices !== undefined)
1640
2357
  plugin.registeredDevices++;
2358
+ // Add the device to the DeviceManager
1641
2359
  this.devices.set(device);
2360
+ // Subscribe to the attributes changed event
1642
2361
  await this.subscribeAttributeChanged(plugin, device);
1643
2362
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1644
2363
  }
2364
+ /**
2365
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2366
+ *
2367
+ * @param {string} pluginName - The name of the plugin.
2368
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2369
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2370
+ */
1645
2371
  async removeBridgedEndpoint(pluginName, device) {
1646
2372
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2373
+ // Check if the plugin is registered
1647
2374
  const plugin = this.plugins.get(pluginName);
1648
2375
  if (!plugin) {
1649
2376
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1650
2377
  return;
1651
2378
  }
2379
+ // Register and add the device to the matterbridge aggregator node
1652
2380
  if (this.bridgeMode === 'bridge') {
1653
2381
  if (!this.aggregatorNode) {
1654
2382
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1661,6 +2389,7 @@ export class Matterbridge extends EventEmitter {
1661
2389
  }
1662
2390
  else if (this.bridgeMode === 'childbridge') {
1663
2391
  if (plugin.type === 'AccessoryPlatform') {
2392
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1664
2393
  }
1665
2394
  else if (plugin.type === 'DynamicPlatform') {
1666
2395
  if (!plugin.aggregatorNode) {
@@ -1673,8 +2402,21 @@ export class Matterbridge extends EventEmitter {
1673
2402
  if (plugin.registeredDevices !== undefined)
1674
2403
  plugin.registeredDevices--;
1675
2404
  }
2405
+ // Remove the device from the DeviceManager
1676
2406
  this.devices.remove(device);
1677
2407
  }
2408
+ /**
2409
+ * Removes all bridged endpoints from the specified plugin.
2410
+ *
2411
+ * @param {string} pluginName - The name of the plugin.
2412
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2413
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2414
+ *
2415
+ * @remarks
2416
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2417
+ * It also applies a delay between each removal if specified.
2418
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2419
+ */
1678
2420
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1679
2421
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1680
2422
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1685,13 +2427,24 @@ export class Matterbridge extends EventEmitter {
1685
2427
  if (delay > 0)
1686
2428
  await wait(2000);
1687
2429
  }
2430
+ /**
2431
+ * Subscribes to the attribute change event for the given device and plugin.
2432
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2433
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2434
+ *
2435
+ * @param {Plugin} plugin - The plugin associated with the device.
2436
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2437
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2438
+ */
1688
2439
  async subscribeAttributeChanged(plugin, device) {
1689
2440
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
1690
2441
  return;
1691
2442
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
2443
+ // Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
1692
2444
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1693
2445
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1694
2446
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
2447
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1695
2448
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
1696
2449
  });
1697
2450
  }
@@ -1741,6 +2494,7 @@ export class Matterbridge extends EventEmitter {
1741
2494
  this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1742
2495
  await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1743
2496
  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}`);
2497
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1744
2498
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
1745
2499
  });
1746
2500
  }
@@ -1749,12 +2503,19 @@ export class Matterbridge extends EventEmitter {
1749
2503
  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...`);
1750
2504
  await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1751
2505
  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}`);
2506
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1752
2507
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
1753
2508
  });
1754
2509
  }
1755
2510
  }
1756
2511
  }
1757
2512
  }
2513
+ /**
2514
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2515
+ *
2516
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2517
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2518
+ */
1758
2519
  sanitizeFabricInformations(fabricInfo) {
1759
2520
  return fabricInfo.map((info) => {
1760
2521
  return {
@@ -1768,6 +2529,12 @@ export class Matterbridge extends EventEmitter {
1768
2529
  };
1769
2530
  });
1770
2531
  }
2532
+ /**
2533
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2534
+ *
2535
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2536
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2537
+ */
1771
2538
  sanitizeSessionInformation(sessions) {
1772
2539
  return sessions
1773
2540
  .filter((session) => session.isPeerActive)
@@ -1794,7 +2561,21 @@ export class Matterbridge extends EventEmitter {
1794
2561
  };
1795
2562
  });
1796
2563
  }
2564
+ /**
2565
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2566
+ *
2567
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2568
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2569
+ */
2570
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1797
2571
  async setAggregatorReachability(aggregatorNode, reachable) {
2572
+ /*
2573
+ for (const child of aggregatorNode.parts) {
2574
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2575
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2576
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2577
+ }
2578
+ */
1798
2579
  }
1799
2580
  getVendorIdName = (vendorId) => {
1800
2581
  if (!vendorId)
@@ -1834,10 +2615,11 @@ export class Matterbridge extends EventEmitter {
1834
2615
  case 0x1488:
1835
2616
  vendorName = '(ShortcutLabsFlic)';
1836
2617
  break;
1837
- case 65521:
2618
+ case 65521: // 0xFFF1
1838
2619
  vendorName = '(MatterTest)';
1839
2620
  break;
1840
2621
  }
1841
2622
  return vendorName;
1842
2623
  };
1843
2624
  }
2625
+ //# sourceMappingURL=matterbridge.js.map