matterbridge 3.3.4-dev-20251022-681420c → 3.3.4

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