matterbridge 3.5.0-dev-20260119-f9ea00e → 3.5.0

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 (325) hide show
  1. package/CHANGELOG.md +119 -117
  2. package/dist/broadcastServer.d.ts +115 -0
  3. package/dist/broadcastServer.d.ts.map +1 -0
  4. package/dist/broadcastServer.js +117 -0
  5. package/dist/broadcastServer.js.map +1 -0
  6. package/dist/broadcastServerTypes.d.ts +43 -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 +24 -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 +36 -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 +42 -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 +1 -0
  23. package/dist/clusters/export.d.ts.map +1 -0
  24. package/dist/clusters/export.js +2 -0
  25. package/dist/clusters/export.js.map +1 -0
  26. package/dist/deviceManager.d.ts +108 -0
  27. package/dist/deviceManager.d.ts.map +1 -0
  28. package/dist/deviceManager.js +113 -1
  29. package/dist/deviceManager.js.map +1 -0
  30. package/dist/devices/airConditioner.d.ts +75 -0
  31. package/dist/devices/airConditioner.d.ts.map +1 -0
  32. package/dist/devices/airConditioner.js +57 -0
  33. package/dist/devices/airConditioner.js.map +1 -0
  34. package/dist/devices/batteryStorage.d.ts +43 -0
  35. package/dist/devices/batteryStorage.d.ts.map +1 -0
  36. package/dist/devices/batteryStorage.js +48 -1
  37. package/dist/devices/batteryStorage.js.map +1 -0
  38. package/dist/devices/cooktop.d.ts +55 -0
  39. package/dist/devices/cooktop.d.ts.map +1 -0
  40. package/dist/devices/cooktop.js +56 -0
  41. package/dist/devices/cooktop.js.map +1 -0
  42. package/dist/devices/dishwasher.d.ts +55 -0
  43. package/dist/devices/dishwasher.d.ts.map +1 -0
  44. package/dist/devices/dishwasher.js +57 -0
  45. package/dist/devices/dishwasher.js.map +1 -0
  46. package/dist/devices/evse.d.ts +57 -0
  47. package/dist/devices/evse.d.ts.map +1 -0
  48. package/dist/devices/evse.js +74 -10
  49. package/dist/devices/evse.js.map +1 -0
  50. package/dist/devices/export.d.ts +1 -0
  51. package/dist/devices/export.d.ts.map +1 -0
  52. package/dist/devices/export.js +5 -0
  53. package/dist/devices/export.js.map +1 -0
  54. package/dist/devices/extractorHood.d.ts +41 -0
  55. package/dist/devices/extractorHood.d.ts.map +1 -0
  56. package/dist/devices/extractorHood.js +43 -0
  57. package/dist/devices/extractorHood.js.map +1 -0
  58. package/dist/devices/heatPump.d.ts +43 -0
  59. package/dist/devices/heatPump.d.ts.map +1 -0
  60. package/dist/devices/heatPump.js +50 -2
  61. package/dist/devices/heatPump.js.map +1 -0
  62. package/dist/devices/laundryDryer.d.ts +58 -0
  63. package/dist/devices/laundryDryer.d.ts.map +1 -0
  64. package/dist/devices/laundryDryer.js +62 -3
  65. package/dist/devices/laundryDryer.js.map +1 -0
  66. package/dist/devices/laundryWasher.d.ts +64 -0
  67. package/dist/devices/laundryWasher.d.ts.map +1 -0
  68. package/dist/devices/laundryWasher.js +70 -4
  69. package/dist/devices/laundryWasher.js.map +1 -0
  70. package/dist/devices/microwaveOven.d.ts +77 -1
  71. package/dist/devices/microwaveOven.d.ts.map +1 -0
  72. package/dist/devices/microwaveOven.js +88 -5
  73. package/dist/devices/microwaveOven.js.map +1 -0
  74. package/dist/devices/oven.d.ts +82 -0
  75. package/dist/devices/oven.d.ts.map +1 -0
  76. package/dist/devices/oven.js +85 -0
  77. package/dist/devices/oven.js.map +1 -0
  78. package/dist/devices/refrigerator.d.ts +100 -0
  79. package/dist/devices/refrigerator.d.ts.map +1 -0
  80. package/dist/devices/refrigerator.js +102 -0
  81. package/dist/devices/refrigerator.js.map +1 -0
  82. package/dist/devices/roboticVacuumCleaner.d.ts +83 -0
  83. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  84. package/dist/devices/roboticVacuumCleaner.js +100 -9
  85. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  86. package/dist/devices/solarPower.d.ts +36 -0
  87. package/dist/devices/solarPower.d.ts.map +1 -0
  88. package/dist/devices/solarPower.js +38 -0
  89. package/dist/devices/solarPower.js.map +1 -0
  90. package/dist/devices/speaker.d.ts +79 -0
  91. package/dist/devices/speaker.d.ts.map +1 -0
  92. package/dist/devices/speaker.js +84 -0
  93. package/dist/devices/speaker.js.map +1 -0
  94. package/dist/devices/temperatureControl.d.ts +21 -0
  95. package/dist/devices/temperatureControl.d.ts.map +1 -0
  96. package/dist/devices/temperatureControl.js +24 -3
  97. package/dist/devices/temperatureControl.js.map +1 -0
  98. package/dist/devices/waterHeater.d.ts +74 -0
  99. package/dist/devices/waterHeater.d.ts.map +1 -0
  100. package/dist/devices/waterHeater.js +82 -2
  101. package/dist/devices/waterHeater.js.map +1 -0
  102. package/dist/dgram/coap.d.ts +171 -0
  103. package/dist/dgram/coap.d.ts.map +1 -0
  104. package/dist/dgram/coap.js +126 -13
  105. package/dist/dgram/coap.js.map +1 -0
  106. package/dist/dgram/dgram.d.ts +99 -0
  107. package/dist/dgram/dgram.d.ts.map +1 -0
  108. package/dist/dgram/dgram.js +114 -2
  109. package/dist/dgram/dgram.js.map +1 -0
  110. package/dist/dgram/mb_coap.d.ts +23 -0
  111. package/dist/dgram/mb_coap.d.ts.map +1 -0
  112. package/dist/dgram/mb_coap.js +41 -3
  113. package/dist/dgram/mb_coap.js.map +1 -0
  114. package/dist/dgram/mb_mdns.d.ts +23 -0
  115. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  116. package/dist/dgram/mb_mdns.js +80 -24
  117. package/dist/dgram/mb_mdns.js.map +1 -0
  118. package/dist/dgram/mdns.d.ts +187 -4
  119. package/dist/dgram/mdns.d.ts.map +1 -0
  120. package/dist/dgram/mdns.js +371 -139
  121. package/dist/dgram/mdns.js.map +1 -0
  122. package/dist/dgram/multicast.d.ts +49 -0
  123. package/dist/dgram/multicast.d.ts.map +1 -0
  124. package/dist/dgram/multicast.js +62 -1
  125. package/dist/dgram/multicast.js.map +1 -0
  126. package/dist/dgram/unicast.d.ts +53 -0
  127. package/dist/dgram/unicast.d.ts.map +1 -0
  128. package/dist/dgram/unicast.js +60 -0
  129. package/dist/dgram/unicast.js.map +1 -0
  130. package/dist/frontend.d.ts +187 -0
  131. package/dist/frontend.d.ts.map +1 -0
  132. package/dist/frontend.js +498 -37
  133. package/dist/frontend.js.map +1 -0
  134. package/dist/frontendTypes.d.ts +57 -0
  135. package/dist/frontendTypes.d.ts.map +1 -0
  136. package/dist/frontendTypes.js +45 -0
  137. package/dist/frontendTypes.js.map +1 -0
  138. package/dist/helpers.d.ts +43 -0
  139. package/dist/helpers.d.ts.map +1 -0
  140. package/dist/helpers.js +53 -0
  141. package/dist/helpers.js.map +1 -0
  142. package/dist/index.d.ts +23 -0
  143. package/dist/index.d.ts.map +1 -0
  144. package/dist/index.js +25 -0
  145. package/dist/index.js.map +1 -0
  146. package/dist/jestutils/export.d.ts +1 -0
  147. package/dist/jestutils/export.d.ts.map +1 -0
  148. package/dist/jestutils/export.js +1 -0
  149. package/dist/jestutils/export.js.map +1 -0
  150. package/dist/jestutils/jestHelpers.d.ts +255 -0
  151. package/dist/jestutils/jestHelpers.d.ts.map +1 -0
  152. package/dist/jestutils/jestHelpers.js +372 -14
  153. package/dist/jestutils/jestHelpers.js.map +1 -0
  154. package/dist/logger/export.d.ts +1 -0
  155. package/dist/logger/export.d.ts.map +1 -0
  156. package/dist/logger/export.js +1 -0
  157. package/dist/logger/export.js.map +1 -0
  158. package/dist/matter/behaviors.d.ts +1 -0
  159. package/dist/matter/behaviors.d.ts.map +1 -0
  160. package/dist/matter/behaviors.js +2 -0
  161. package/dist/matter/behaviors.js.map +1 -0
  162. package/dist/matter/clusters.d.ts +1 -0
  163. package/dist/matter/clusters.d.ts.map +1 -0
  164. package/dist/matter/clusters.js +2 -0
  165. package/dist/matter/clusters.js.map +1 -0
  166. package/dist/matter/devices.d.ts +1 -0
  167. package/dist/matter/devices.d.ts.map +1 -0
  168. package/dist/matter/devices.js +2 -0
  169. package/dist/matter/devices.js.map +1 -0
  170. package/dist/matter/endpoints.d.ts +1 -0
  171. package/dist/matter/endpoints.d.ts.map +1 -0
  172. package/dist/matter/endpoints.js +2 -0
  173. package/dist/matter/endpoints.js.map +1 -0
  174. package/dist/matter/export.d.ts +1 -0
  175. package/dist/matter/export.d.ts.map +1 -0
  176. package/dist/matter/export.js +2 -0
  177. package/dist/matter/export.js.map +1 -0
  178. package/dist/matter/types.d.ts +1 -0
  179. package/dist/matter/types.d.ts.map +1 -0
  180. package/dist/matter/types.js +2 -0
  181. package/dist/matter/types.js.map +1 -0
  182. package/dist/matterNode.d.ts +258 -0
  183. package/dist/matterNode.d.ts.map +1 -0
  184. package/dist/matterNode.js +359 -8
  185. package/dist/matterNode.js.map +1 -0
  186. package/dist/matterbridge.d.ts +362 -0
  187. package/dist/matterbridge.d.ts.map +1 -0
  188. package/dist/matterbridge.js +842 -46
  189. package/dist/matterbridge.js.map +1 -0
  190. package/dist/matterbridgeAccessoryPlatform.d.ts +36 -0
  191. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  192. package/dist/matterbridgeAccessoryPlatform.js +38 -0
  193. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  194. package/dist/matterbridgeBehaviors.d.ts +24 -0
  195. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  196. package/dist/matterbridgeBehaviors.js +68 -5
  197. package/dist/matterbridgeBehaviors.js.map +1 -0
  198. package/dist/matterbridgeDeviceTypes.d.ts +649 -0
  199. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  200. package/dist/matterbridgeDeviceTypes.js +673 -6
  201. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  202. package/dist/matterbridgeDynamicPlatform.d.ts +36 -0
  203. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  204. package/dist/matterbridgeDynamicPlatform.js +38 -0
  205. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  206. package/dist/matterbridgeEndpoint.d.ts +1332 -0
  207. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  208. package/dist/matterbridgeEndpoint.js +1457 -53
  209. package/dist/matterbridgeEndpoint.js.map +1 -0
  210. package/dist/matterbridgeEndpointHelpers.d.ts +425 -0
  211. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  212. package/dist/matterbridgeEndpointHelpers.js +483 -20
  213. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  214. package/dist/matterbridgeEndpointTypes.d.ts +70 -0
  215. package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
  216. package/dist/matterbridgeEndpointTypes.js +25 -0
  217. package/dist/matterbridgeEndpointTypes.js.map +1 -0
  218. package/dist/matterbridgePlatform.d.ts +425 -0
  219. package/dist/matterbridgePlatform.d.ts.map +1 -0
  220. package/dist/matterbridgePlatform.js +451 -1
  221. package/dist/matterbridgePlatform.js.map +1 -0
  222. package/dist/matterbridgeTypes.d.ts +46 -0
  223. package/dist/matterbridgeTypes.d.ts.map +1 -0
  224. package/dist/matterbridgeTypes.js +26 -0
  225. package/dist/matterbridgeTypes.js.map +1 -0
  226. package/dist/pluginManager.d.ts +305 -0
  227. package/dist/pluginManager.d.ts.map +1 -0
  228. package/dist/pluginManager.js +341 -5
  229. package/dist/pluginManager.js.map +1 -0
  230. package/dist/shelly.d.ts +157 -0
  231. package/dist/shelly.d.ts.map +1 -0
  232. package/dist/shelly.js +178 -7
  233. package/dist/shelly.js.map +1 -0
  234. package/dist/storage/export.d.ts +1 -0
  235. package/dist/storage/export.d.ts.map +1 -0
  236. package/dist/storage/export.js +1 -0
  237. package/dist/storage/export.js.map +1 -0
  238. package/dist/update.d.ts +75 -0
  239. package/dist/update.d.ts.map +1 -0
  240. package/dist/update.js +93 -1
  241. package/dist/update.js.map +1 -0
  242. package/dist/utils/colorUtils.d.ts +77 -0
  243. package/dist/utils/colorUtils.d.ts.map +1 -0
  244. package/dist/utils/colorUtils.js +97 -2
  245. package/dist/utils/colorUtils.js.map +1 -0
  246. package/dist/utils/commandLine.d.ts +60 -0
  247. package/dist/utils/commandLine.d.ts.map +1 -0
  248. package/dist/utils/commandLine.js +60 -0
  249. package/dist/utils/commandLine.js.map +1 -0
  250. package/dist/utils/copyDirectory.d.ts +33 -0
  251. package/dist/utils/copyDirectory.d.ts.map +1 -0
  252. package/dist/utils/copyDirectory.js +37 -0
  253. package/dist/utils/copyDirectory.js.map +1 -0
  254. package/dist/utils/createDirectory.d.ts +32 -0
  255. package/dist/utils/createDirectory.d.ts.map +1 -0
  256. package/dist/utils/createDirectory.js +33 -0
  257. package/dist/utils/createDirectory.js.map +1 -0
  258. package/dist/utils/createZip.d.ts +38 -0
  259. package/dist/utils/createZip.d.ts.map +1 -0
  260. package/dist/utils/createZip.js +47 -2
  261. package/dist/utils/createZip.js.map +1 -0
  262. package/dist/utils/deepCopy.d.ts +31 -0
  263. package/dist/utils/deepCopy.d.ts.map +1 -0
  264. package/dist/utils/deepCopy.js +39 -0
  265. package/dist/utils/deepCopy.js.map +1 -0
  266. package/dist/utils/deepEqual.d.ts +53 -0
  267. package/dist/utils/deepEqual.d.ts.map +1 -0
  268. package/dist/utils/deepEqual.js +72 -1
  269. package/dist/utils/deepEqual.js.map +1 -0
  270. package/dist/utils/error.d.ts +42 -0
  271. package/dist/utils/error.d.ts.map +1 -0
  272. package/dist/utils/error.js +42 -0
  273. package/dist/utils/error.js.map +1 -0
  274. package/dist/utils/export.d.ts +1 -0
  275. package/dist/utils/export.d.ts.map +1 -0
  276. package/dist/utils/export.js +1 -0
  277. package/dist/utils/export.js.map +1 -0
  278. package/dist/utils/format.d.ts +49 -0
  279. package/dist/utils/format.d.ts.map +1 -0
  280. package/dist/utils/format.js +49 -0
  281. package/dist/utils/format.js.map +1 -0
  282. package/dist/utils/hex.d.ts +85 -0
  283. package/dist/utils/hex.d.ts.map +1 -0
  284. package/dist/utils/hex.js +124 -0
  285. package/dist/utils/hex.js.map +1 -0
  286. package/dist/utils/inspector.d.ts +63 -0
  287. package/dist/utils/inspector.d.ts.map +1 -0
  288. package/dist/utils/inspector.js +69 -1
  289. package/dist/utils/inspector.js.map +1 -0
  290. package/dist/utils/isValid.d.ts +93 -0
  291. package/dist/utils/isValid.d.ts.map +1 -0
  292. package/dist/utils/isValid.js +93 -0
  293. package/dist/utils/isValid.js.map +1 -0
  294. package/dist/utils/network.d.ts +116 -0
  295. package/dist/utils/network.d.ts.map +1 -0
  296. package/dist/utils/network.js +126 -5
  297. package/dist/utils/network.js.map +1 -0
  298. package/dist/utils/spawn.d.ts +32 -0
  299. package/dist/utils/spawn.d.ts.map +1 -0
  300. package/dist/utils/spawn.js +71 -1
  301. package/dist/utils/spawn.js.map +1 -0
  302. package/dist/utils/tracker.d.ts +56 -0
  303. package/dist/utils/tracker.d.ts.map +1 -0
  304. package/dist/utils/tracker.js +64 -1
  305. package/dist/utils/tracker.js.map +1 -0
  306. package/dist/utils/wait.d.ts +51 -0
  307. package/dist/utils/wait.d.ts.map +1 -0
  308. package/dist/utils/wait.js +60 -8
  309. package/dist/utils/wait.js.map +1 -0
  310. package/dist/workerGlobalPrefix.d.ts +24 -0
  311. package/dist/workerGlobalPrefix.d.ts.map +1 -0
  312. package/dist/workerGlobalPrefix.js +37 -5
  313. package/dist/workerGlobalPrefix.js.map +1 -0
  314. package/dist/workerTypes.d.ts +25 -0
  315. package/dist/workerTypes.d.ts.map +1 -0
  316. package/dist/workerTypes.js +24 -0
  317. package/dist/workerTypes.js.map +1 -0
  318. package/dist/workers.d.ts +61 -0
  319. package/dist/workers.d.ts.map +1 -0
  320. package/dist/workers.js +68 -4
  321. package/dist/workers.js.map +1 -0
  322. package/frontend/build/assets/index.js +4 -4
  323. package/frontend/package.json +1 -1
  324. package/npm-shrinkwrap.json +5 -35
  325. package/package.json +7 -7
@@ -1,8 +1,35 @@
1
+ /**
2
+ * This file contains the class MatterNode.
3
+ *
4
+ * @file matterNode.ts
5
+ * @author Luca Liguori
6
+ * @created 2025-10-01
7
+ * @version 1.0.0
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2025, 2026, 2027 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
+ // Node modules
1
25
  import path from 'node:path';
2
26
  import fs from 'node:fs';
3
27
  import EventEmitter from 'node:events';
28
+ // AnsiLogger module
4
29
  import { AnsiLogger, BLUE, CYAN, db, debugStringify, er, nf, or, zb } from 'node-ansi-logger';
30
+ // Node persist manager module
5
31
  import { NodeStorageManager } from 'node-persist-manager';
32
+ // @matter
6
33
  import '@matter/nodejs';
7
34
  import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, StorageService, UINT32_MAX, UINT16_MAX, Environment } from '@matter/general';
8
35
  import { MdnsService } from '@matter/protocol';
@@ -22,27 +49,48 @@ import { BroadcastServer } from './broadcastServer.js';
22
49
  import { toBaseDevice } from './deviceManager.js';
23
50
  import { PluginManager } from './pluginManager.js';
24
51
  import { addVirtualDevice } from './helpers.js';
52
+ /**
53
+ * Represents the Matter class.
54
+ */
25
55
  export class MatterNode extends EventEmitter {
26
56
  matterbridge;
27
57
  pluginName;
28
58
  device;
29
- log = new AnsiLogger({ logName: 'MatterNode', logTimestampFormat: 4, logLevel: "debug" });
30
- matterLog = new AnsiLogger({ logName: 'MatterNode', logTimestampFormat: 4, logLevel: "debug" });
59
+ /** Matterbridge logger */
60
+ log = new AnsiLogger({ logName: 'MatterNode', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
61
+ /** Matter logger */
62
+ matterLog = new AnsiLogger({ logName: 'MatterNode', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
63
+ /** Matter environment default */
31
64
  environment = Environment.default;
65
+ /** Matter storage id */
32
66
  storeId;
67
+ /** Matter mdns service from environment default */
33
68
  matterMdnsService;
69
+ /** Matter storage service from environment default */
34
70
  matterStorageService;
71
+ /** Matter storage manager created with name 'Matterbridge' */
35
72
  matterStorageManager;
73
+ /** Matter storage context created in the storage manager with name 'persist' */
36
74
  matterStorageContext;
75
+ /** Matter mdns interface name e.g. 'eth0' or 'wlan0' or 'Wi-Fi' */
37
76
  mdnsInterface;
77
+ /** Matter listeningAddressIpv4 address */
38
78
  ipv4Address;
79
+ /** Matter listeningAddressIpv6 address */
39
80
  ipv6Address;
81
+ /** Matter commissioning port It is incremented in childbridge mode. */
40
82
  port;
83
+ /** Matter commissioning passcode. It is incremented in childbridge mode. */
41
84
  passcode;
85
+ /** Matter commissioning discriminator. It is incremented in childbridge mode. */
42
86
  discriminator;
87
+ /** Matter device certification */
43
88
  certification;
89
+ /** Matter server node */
44
90
  serverNode;
91
+ /** Matter aggregator node */
45
92
  aggregatorNode;
93
+ // Default values for the aggregator node
46
94
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
47
95
  aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
48
96
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
@@ -50,12 +98,23 @@ export class MatterNode extends EventEmitter {
50
98
  aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
51
99
  aggregatorSerialNumber = getParameter('serialNumber');
52
100
  aggregatorUniqueId = getParameter('uniqueId');
101
+ /** Advertising nodes map: time advertising started keyed by storeId */
53
102
  advertisingNodes = new Map();
103
+ /** Plugins */
54
104
  pluginManager;
105
+ /** Dependant MatterNodes keyed by device id */
55
106
  dependantMatterNodes = new Map();
107
+ /** Broadcast server */
56
108
  server;
57
109
  debug = hasParameter('debug') || hasParameter('verbose');
58
110
  verbose = hasParameter('verbose');
111
+ /**
112
+ * Creates an instance of the Matter class.
113
+ *
114
+ * @param {SharedMatterbridge} matterbridge - The shared matterbridge instance.
115
+ * @param {PluginName} [pluginName] - The plugin name (optional). If not provided, it is assumed to be the main matter node instance and all plugins are included.
116
+ * @param {MatterbridgeEndpoint} [device] - The matterbridge endpoint device (optional). It is used to create a server mode device.
117
+ */
59
118
  constructor(matterbridge, pluginName, device) {
60
119
  super();
61
120
  this.matterbridge = matterbridge;
@@ -64,19 +123,25 @@ export class MatterNode extends EventEmitter {
64
123
  this.log.logNameColor = '\x1b[38;5;65m';
65
124
  if (this.debug)
66
125
  this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loading...`);
126
+ // Setup Matter parameters
67
127
  this.port = matterbridge.port;
68
128
  this.passcode = matterbridge.passcode;
69
129
  this.discriminator = matterbridge.discriminator;
130
+ // Setup the broadcast server
70
131
  this.server = new BroadcastServer('matter', this.log);
71
132
  this.server.on('broadcast_message', this.msgHandler.bind(this));
72
133
  if (this.verbose)
73
134
  this.log.debug(`BroadcastServer is ready`);
135
+ // Ensure the matterbridge directory exists
74
136
  fs.mkdirSync(matterbridge.matterbridgeDirectory, { recursive: true });
137
+ // Setup the plugin manager with thread server closed
75
138
  this.pluginManager = new PluginManager(this.matterbridge);
76
- this.pluginManager.logLevel = this.debug ? "debug" : "info";
139
+ this.pluginManager.logLevel = this.debug ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */;
140
+ // @ts-expect-error access private property
77
141
  this.pluginManager.server.close();
78
142
  if (this.verbose)
79
143
  this.log.debug(`PluginManager is ready`);
144
+ // Setup the matter environment
80
145
  this.environment.vars.set('log.level', MatterLogLevel.DEBUG);
81
146
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
82
147
  this.environment.vars.set('path.root', path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
@@ -84,15 +149,18 @@ export class MatterNode extends EventEmitter {
84
149
  this.environment.vars.set('runtime.exitcode', false);
85
150
  if (this.verbose)
86
151
  this.log.debug(`Matter Environment is ready`);
152
+ // Ensure MdnsService is registered in the default environment
87
153
  this.matterMdnsService = new MdnsService(this.environment);
88
154
  setImmediate(async () => {
89
155
  await this.matterMdnsService?.construction.ready;
90
156
  if (this.verbose)
91
157
  this.log.debug(`Matter MdnsService is ready`);
92
158
  });
159
+ // Setup the matterbridge logger
93
160
  if (this.matterbridge.fileLogger) {
94
161
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.logLevel);
95
162
  }
163
+ // Setup the matter logger
96
164
  Logger.destinations.default.write = this.createDestinationMatterLogger();
97
165
  const levels = ['debug', 'info', 'notice', 'warn', 'error', 'fatal'];
98
166
  if (this.verbose)
@@ -100,6 +168,11 @@ export class MatterNode extends EventEmitter {
100
168
  if (this.debug)
101
169
  this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loaded`);
102
170
  }
171
+ /**
172
+ * Handles incoming messages from the broadcast server.
173
+ *
174
+ * @param {WorkerMessage} msg - The incoming message.
175
+ */
103
176
  async msgHandler(msg) {
104
177
  if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'matter')) {
105
178
  if (this.verbose)
@@ -130,9 +203,17 @@ export class MatterNode extends EventEmitter {
130
203
  }
131
204
  }
132
205
  }
206
+ /**
207
+ * Destroys the Matter instance.
208
+ * It closes the mDNS service and the broadcast server.
209
+ *
210
+ * @param {boolean} closeMdns - Whether to close the mDNS service. Default is true.
211
+ * @returns {Promise<void>} A promise that resolves when the instance is destroyed.
212
+ */
133
213
  async destroy(closeMdns = true) {
134
214
  if (this.verbose)
135
215
  this.log.debug(`Destroying MatterNode instance for ${this.storeId}...`);
216
+ // Close mDNS service
136
217
  if (closeMdns) {
137
218
  if (this.verbose)
138
219
  this.log.debug(`Closing Matter MdnsService for ${this.storeId}...`);
@@ -140,18 +221,26 @@ export class MatterNode extends EventEmitter {
140
221
  if (this.verbose)
141
222
  this.log.debug(`Closed Matter MdnsService for ${this.storeId}`);
142
223
  }
224
+ // Close the plugin manager
143
225
  this.pluginManager.destroy();
226
+ // Close the broadcast server
144
227
  this.server.close();
228
+ // Yield to the Node.js event loop to allow all resources to be released
145
229
  await this.yieldToNode();
146
230
  if (this.verbose)
147
231
  this.log.debug(`Destroyed MatterNode instance for ${this.storeId}`);
148
232
  }
149
233
  async create() {
150
234
  this.log.info('Creating Matter node...');
235
+ // Start matter storage
151
236
  await this.startMatterStorage();
237
+ // Load plugins from storage
238
+ // @ts-expect-error access private property
152
239
  this.pluginManager.matterbridge.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
240
+ // @ts-expect-error access private property
153
241
  this.pluginManager.matterbridge.nodeContext = await this.pluginManager.matterbridge.nodeStorage.createStorage('matterbridge');
154
242
  await this.pluginManager.loadFromStorage();
243
+ // Create Matter node for a server mode device
155
244
  if (this.pluginName && this.device && this.device.deviceName) {
156
245
  this.log.debug(`Creating MatterNode instance for server node device ${CYAN}${this.device.deviceName}${db}...`);
157
246
  await this.createDeviceServerNode(this.pluginName, this.device);
@@ -162,6 +251,7 @@ export class MatterNode extends EventEmitter {
162
251
  if (!this.pluginName) {
163
252
  this.log.debug('Creating MatterNode instance for all plugins...');
164
253
  await this.createMatterbridgeServerNode();
254
+ // Load all enabled plugins
165
255
  this.log.debug('Loading all plugins...');
166
256
  const loadPromises = [];
167
257
  for (const plugin of this.pluginManager.array().filter((p) => p.enabled)) {
@@ -174,6 +264,7 @@ export class MatterNode extends EventEmitter {
174
264
  }
175
265
  else {
176
266
  this.log.debug(`Creating MatterNode instance for plugin ${CYAN}${this.pluginName}${db}...`);
267
+ // Load only the specified plugin
177
268
  this.log.debug(`Loading plugin ${CYAN}${this.pluginName}${db}...`);
178
269
  await this.pluginManager.load(this.pluginName);
179
270
  this.log.debug(`Loaded plugin ${CYAN}${this.pluginName}${db}`);
@@ -187,13 +278,16 @@ export class MatterNode extends EventEmitter {
187
278
  if (!this.serverNode && !this.pluginName)
188
279
  throw new Error('Matter server node not created yet. Call create() first.');
189
280
  this.log.info('Starting MatterNode...');
281
+ // Start Matter node for a server mode device
190
282
  if (this.pluginName && this.device && this.device.deviceName) {
283
+ // Start the server node
191
284
  this.log.debug(`Starting MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
192
285
  await this.startServerNode();
193
286
  this.log.debug(`Started MatterNode for server device ${this.pluginName}:${this.device.deviceName}`);
194
287
  return;
195
288
  }
196
289
  if (!this.pluginName) {
290
+ // Start all loaded plugins
197
291
  this.log.debug('Starting all plugins...');
198
292
  const startPromises = [];
199
293
  for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.loaded)) {
@@ -201,9 +295,11 @@ export class MatterNode extends EventEmitter {
201
295
  }
202
296
  await Promise.all(startPromises);
203
297
  this.log.debug('Started all plugins');
298
+ // Start the server node
204
299
  this.log.debug('Starting MatterNode for all plugins...');
205
300
  await this.startServerNode();
206
301
  this.log.debug('Started MatterNode for all plugins');
302
+ // Configure all loaded plugins
207
303
  this.log.debug('Configuring all plugins...');
208
304
  const configurePromises = [];
209
305
  for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.started)) {
@@ -213,12 +309,16 @@ export class MatterNode extends EventEmitter {
213
309
  this.log.debug('Configured all plugins');
214
310
  }
215
311
  else {
312
+ // Start the loaded plugin
216
313
  await this.pluginManager.start(this.pluginName, 'Starting MatterNode');
314
+ // Start the server node
217
315
  this.log.debug(`Starting MatterNode for plugin ${this.pluginName}...`);
218
316
  await this.startServerNode();
219
317
  this.log.debug(`Started MatterNode for plugin ${this.pluginName}`);
318
+ // Configure the plugin
220
319
  await this.pluginManager.configure(this.pluginName);
221
320
  }
321
+ // Start the dependant MatterNodes
222
322
  this.log.debug(`Starting dependant MatterNodes...`);
223
323
  for (const dependantMatterNode of this.dependantMatterNodes.values()) {
224
324
  await dependantMatterNode.start();
@@ -231,13 +331,15 @@ export class MatterNode extends EventEmitter {
231
331
  if (!this.serverNode)
232
332
  throw new Error('Matter server node not created yet. Call create() first.');
233
333
  this.log.info('Stopping MatterNode...');
334
+ // Stop Matter node for a server mode device
234
335
  if (this.pluginName && this.device && this.device.deviceName) {
336
+ // Stop the server node
235
337
  this.log.debug(`Stopping MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
236
338
  await this.stopServerNode();
237
339
  this.serverNode = undefined;
238
340
  this.aggregatorNode = undefined;
239
341
  await this.stopMatterStorage();
240
- await this.destroy(false);
342
+ await this.destroy(false); // Do not close mDNS since it is shared
241
343
  this.log.debug(`Stopped MatterNode for server device ${this.pluginName}:${this.device.deviceName}`);
242
344
  this.log.info('Stopped MatterNode');
243
345
  await this.yieldToNode();
@@ -257,6 +359,7 @@ export class MatterNode extends EventEmitter {
257
359
  await this.pluginManager.shutdown(this.pluginName, 'Stopping MatterNode');
258
360
  this.log.debug(`Stopped plugin ${this.pluginName}`);
259
361
  }
362
+ // Stop the dependant MatterNodes
260
363
  this.log.debug(`Stopping dependant MatterNodes...`);
261
364
  for (const dependantMatterNode of this.dependantMatterNodes.values()) {
262
365
  await dependantMatterNode.stop();
@@ -269,24 +372,46 @@ export class MatterNode extends EventEmitter {
269
372
  this.log.info('Stopped MatterNode');
270
373
  await this.yieldToNode();
271
374
  }
375
+ /**
376
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (console and frontend).
377
+ * It also logs to file (matter.log) if fileLogger is true.
378
+ *
379
+ * @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
380
+ */
272
381
  createDestinationMatterLogger() {
273
- this.matterLog.logNameColor = '\x1b[34m';
382
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
274
383
  if (this.matterbridge.matterFileLogger) {
275
384
  this.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
276
385
  }
277
386
  return (text, message) => {
387
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
278
388
  const logger = text.slice(44, 44 + 20).trim();
279
389
  const msg = text.slice(65);
280
390
  this.matterLog.logName = logger;
281
391
  this.matterLog.log(MatterLogLevel.names[message.level], msg);
282
392
  };
283
393
  }
394
+ /**
395
+ * Starts the matter storage with name Matterbridge and performs a backup.
396
+ *
397
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
398
+ */
284
399
  async startMatterStorage() {
400
+ // Setup Matter storage
285
401
  this.log.info(`Starting matter node storage...`);
286
402
  this.matterStorageService = this.environment.get(StorageService);
287
403
  this.log.info(`Started matter node storage in ${CYAN}${this.matterStorageService.location}${nf}`);
404
+ // Backup matter storage since it is created/opened correctly
288
405
  await this.backupMatterStorage(path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
289
406
  }
407
+ /**
408
+ * Makes a backup copy of the specified matter storage directory.
409
+ *
410
+ * @param {string} storageName - The name of the storage directory to be backed up.
411
+ * @param {string} backupName - The name of the backup directory to be created.
412
+ * @private
413
+ * @returns {Promise<void>} A promise that resolves when the has been done.
414
+ */
290
415
  async backupMatterStorage(storageName, backupName) {
291
416
  this.log.info(`Creating matter node storage backup from ${CYAN}${storageName}${nf} to ${CYAN}${backupName}${nf}...`);
292
417
  try {
@@ -294,6 +419,7 @@ export class MatterNode extends EventEmitter {
294
419
  this.log.info('Created matter node storage backup');
295
420
  }
296
421
  catch (error) {
422
+ // istanbul ignore next if
297
423
  if (error instanceof Error && error?.code === 'ENOENT') {
298
424
  this.log.info(`No matter node storage found to backup from ${CYAN}${storageName}${nf} to ${CYAN}${backupName}${nf}`);
299
425
  }
@@ -302,6 +428,11 @@ export class MatterNode extends EventEmitter {
302
428
  }
303
429
  }
304
430
  }
431
+ /**
432
+ * Stops the matter storage.
433
+ *
434
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
435
+ */
305
436
  async stopMatterStorage() {
306
437
  this.log.info('Closing matter node storage...');
307
438
  await this.matterStorageManager?.close();
@@ -311,6 +442,21 @@ export class MatterNode extends EventEmitter {
311
442
  this.log.info('Closed matter node storage');
312
443
  this.emit('closed');
313
444
  }
445
+ /**
446
+ * Creates a server node storage context.
447
+ *
448
+ * @param {string} storeId - The storeId.
449
+ * @param {string} deviceName - The name of the device.
450
+ * @param {DeviceTypeId} deviceType - The device type of the device.
451
+ * @param {VendorId} vendorId - The vendor ID.
452
+ * @param {string} vendorName - The vendor name.
453
+ * @param {number} productId - The product ID.
454
+ * @param {string} productName - The product name.
455
+ * @param {string} [serialNumber] - The serial number of the device (optional).
456
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
457
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
458
+ * @throws {Error} If the storage service is not initialized.
459
+ */
314
460
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
315
461
  if (!this.matterStorageService) {
316
462
  throw new Error('No storage service initialized');
@@ -353,33 +499,52 @@ export class MatterNode extends EventEmitter {
353
499
  this.log.debug(`- hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
354
500
  return storageContext;
355
501
  }
502
+ /**
503
+ * Creates a server node.
504
+ *
505
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
506
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
507
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
508
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
509
+ * @throws {Error} If the matter storage context is not created yet.
510
+ */
356
511
  async createServerNode(port = 5540, passcode = 20252026, discriminator = 3850) {
357
512
  if (!this.matterStorageContext) {
358
513
  throw new Error('Matter server node context not created yet. Call createServerNodeContext() first.');
359
514
  }
360
515
  const storeId = await this.matterStorageContext.get('storeId');
361
516
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
517
+ /**
518
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
519
+ */
362
520
  const serverNode = await ServerNode.create({
521
+ // Required: Give the Node a unique ID which is used to store the state of this node
363
522
  id: storeId,
523
+ // Provide the environment to run this node in
364
524
  environment: this.environment,
525
+ // Provide Network relevant configuration like the port
365
526
  network: {
366
527
  listeningAddressIpv4: this.ipv4Address,
367
528
  listeningAddressIpv6: this.ipv6Address,
368
529
  port,
369
530
  },
531
+ // Provide the certificate for the device
370
532
  operationalCredentials: {
371
533
  certification: this.certification,
372
534
  },
535
+ // Provide Commissioning relevant settings
373
536
  commissioning: {
374
537
  passcode,
375
538
  discriminator,
376
539
  },
540
+ // Provide Node announcement settings
377
541
  productDescription: {
378
542
  name: await this.matterStorageContext.get('deviceName'),
379
543
  deviceType: DeviceTypeId(await this.matterStorageContext.get('deviceType')),
380
544
  vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
381
545
  productId: await this.matterStorageContext.get('productId'),
382
546
  },
547
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
383
548
  basicInformation: {
384
549
  vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
385
550
  vendorName: await this.matterStorageContext.get('vendorName'),
@@ -396,17 +561,23 @@ export class MatterNode extends EventEmitter {
396
561
  reachable: true,
397
562
  },
398
563
  });
564
+ /**
565
+ * This event is triggered when the device is initially commissioned successfully.
566
+ * This means: It is added to the first fabric.
567
+ */
399
568
  serverNode.lifecycle.commissioned.on(() => {
400
569
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
401
570
  this.advertisingNodes.delete(storeId);
402
571
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
403
572
  });
573
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
404
574
  serverNode.lifecycle.decommissioned.on(() => {
405
575
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
406
576
  this.advertisingNodes.delete(storeId);
407
577
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
408
578
  this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
409
579
  });
580
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
410
581
  serverNode.lifecycle.online.on(async () => {
411
582
  this.log.notice(`Server node for ${storeId} is online`);
412
583
  if (!serverNode.lifecycle.isCommissioned) {
@@ -424,6 +595,7 @@ export class MatterNode extends EventEmitter {
424
595
  this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is online`, timeout: 5, severity: 'success' } });
425
596
  this.emit('online', storeId);
426
597
  });
598
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
427
599
  serverNode.lifecycle.offline.on(() => {
428
600
  this.log.notice(`Server node for ${storeId} is offline`);
429
601
  this.advertisingNodes.delete(storeId);
@@ -431,11 +603,15 @@ export class MatterNode extends EventEmitter {
431
603
  this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
432
604
  this.emit('offline', storeId);
433
605
  });
606
+ /**
607
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
608
+ * information is needed.
609
+ */
434
610
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
435
611
  let action = '';
436
612
  switch (fabricAction) {
437
613
  case 'added':
438
- this.advertisingNodes.delete(storeId);
614
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
439
615
  action = 'added';
440
616
  break;
441
617
  case 'deleted':
@@ -448,14 +624,22 @@ export class MatterNode extends EventEmitter {
448
624
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
449
625
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
450
626
  });
627
+ /**
628
+ * This event is triggered when an operative new session was opened by a Controller.
629
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
630
+ */
451
631
  serverNode.events.sessions.opened.on((session) => {
452
632
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
453
633
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
454
634
  });
635
+ /**
636
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
637
+ */
455
638
  serverNode.events.sessions.closed.on((session) => {
456
639
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
457
640
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
458
641
  });
642
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
459
643
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
460
644
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
461
645
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
@@ -464,6 +648,12 @@ export class MatterNode extends EventEmitter {
464
648
  this.log.info(`Created server node for ${this.storeId}`);
465
649
  return serverNode;
466
650
  }
651
+ /**
652
+ * Gets the matter serializable data of the specified server node.
653
+ *
654
+ * @param {ServerNode} [serverNode] - The server node to start.
655
+ * @returns {ApiMatter} The serializable data of the server node.
656
+ */
467
657
  getServerNodeData(serverNode) {
468
658
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
469
659
  return {
@@ -480,6 +670,13 @@ export class MatterNode extends EventEmitter {
480
670
  serialNumber: serverNode.state.basicInformation.serialNumber,
481
671
  };
482
672
  }
673
+ /**
674
+ * Starts the specified server node.
675
+ *
676
+ * @param {number} [timeout] - The timeout in milliseconds for starting the server node. Defaults to 30 seconds.
677
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
678
+ * @throws {Error} If the server node is not created yet.
679
+ */
483
680
  async startServerNode(timeout = 30000) {
484
681
  if (!this.serverNode) {
485
682
  throw new Error('Matter server node not created yet. Call create() first.');
@@ -490,9 +687,17 @@ export class MatterNode extends EventEmitter {
490
687
  this.log.notice(`Started ${this.serverNode.id} server node`);
491
688
  }
492
689
  catch (error) {
690
+ // istanbul ignore next
493
691
  this.log.error(`Failed to start ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
494
692
  }
495
693
  }
694
+ /**
695
+ * Stops the specified server node.
696
+ *
697
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
698
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
699
+ * @throws {Error} If the server node is not created yet.
700
+ */
496
701
  async stopServerNode(timeout = 30000) {
497
702
  if (!this.serverNode) {
498
703
  throw new Error('Matter server node not created yet. Call create() first.');
@@ -503,9 +708,16 @@ export class MatterNode extends EventEmitter {
503
708
  this.log.info(`Closed ${this.serverNode.id} server node`);
504
709
  }
505
710
  catch (error) {
711
+ // istanbul ignore next
506
712
  this.log.error(`Failed to close ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
507
713
  }
508
714
  }
715
+ /**
716
+ * Creates an aggregator node with the specified storage context.
717
+ *
718
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
719
+ * @throws {Error} If the matter storage context is not created yet.
720
+ */
509
721
  async createAggregatorNode() {
510
722
  if (!this.matterStorageContext) {
511
723
  throw new Error('Matter server node context not created yet. Call createServerNodeContext() first.');
@@ -515,9 +727,16 @@ export class MatterNode extends EventEmitter {
515
727
  this.log.info(`Created ${await this.matterStorageContext.get('storeId')} aggregator`);
516
728
  return aggregatorNode;
517
729
  }
730
+ /**
731
+ * Creates the matterbridge server node.
732
+ *
733
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created matterbridge server node.
734
+ */
518
735
  async createMatterbridgeServerNode() {
519
736
  this.log.debug(`Creating ${plg}Matterbridge${db} server node...`);
520
- this.matterStorageContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
737
+ this.matterStorageContext = await this.createServerNodeContext('Matterbridge', // storeId
738
+ 'Matterbridge', // deviceName
739
+ this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
521
740
  this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
522
741
  this.aggregatorNode = await this.createAggregatorNode();
523
742
  this.log.debug(`Adding ${plg}Matterbridge${db} aggregator node...`);
@@ -528,6 +747,13 @@ export class MatterNode extends EventEmitter {
528
747
  this.log.debug(`Created ${plg}Matterbridge${db} server node`);
529
748
  return this.serverNode;
530
749
  }
750
+ /**
751
+ * Creates and configures the server node for an accessory plugin for a given device.
752
+ *
753
+ * @param {Plugin | PluginName} plugin - The plugin to configure.
754
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
755
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the accessory plugin.
756
+ */
531
757
  async createAccessoryPlugin(plugin, device) {
532
758
  if (typeof plugin === 'string') {
533
759
  const _plugin = this.pluginManager.get(plugin);
@@ -549,6 +775,12 @@ export class MatterNode extends EventEmitter {
549
775
  }
550
776
  return this.serverNode;
551
777
  }
778
+ /**
779
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
780
+ *
781
+ * @param {Plugin | PluginName} plugin - The plugin to configure.
782
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the dynamic plugin.
783
+ */
552
784
  async createDynamicPlugin(plugin) {
553
785
  if (typeof plugin === 'string') {
554
786
  const _plugin = this.pluginManager.get(plugin);
@@ -572,6 +804,13 @@ export class MatterNode extends EventEmitter {
572
804
  }
573
805
  return this.serverNode;
574
806
  }
807
+ /**
808
+ * Creates and configures the server node for a single not bridged device.
809
+ *
810
+ * @param {Plugin | PluginName} plugin - The plugin to configure.
811
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
812
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the device with mode server.
813
+ */
575
814
  async createDeviceServerNode(plugin, device) {
576
815
  if (typeof plugin === 'string') {
577
816
  const _plugin = this.pluginManager.get(plugin);
@@ -592,13 +831,22 @@ export class MatterNode extends EventEmitter {
592
831
  }
593
832
  return this.serverNode;
594
833
  }
834
+ /**
835
+ * Adds a MatterbridgeEndpoint to the specified plugin.
836
+ *
837
+ * @param {string} pluginName - The name of the plugin.
838
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
839
+ * @returns {Promise<MatterbridgeEndpoint | undefined>} A promise that resolves to the added bridged endpoint, or undefined if there was an error.
840
+ */
595
841
  async addBridgedEndpoint(pluginName, device) {
842
+ // Check if the plugin is registered
596
843
  const plugin = this.pluginManager.get(pluginName);
597
844
  if (!plugin)
598
845
  throw new Error(`Error adding bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): plugin not found`);
599
846
  if (device.mode === 'server') {
600
847
  try {
601
848
  this.log.debug(`Creating MatterNode for device ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
849
+ // Create the MatterNode to manage the device
602
850
  const matterNode = new MatterNode(this.matterbridge, pluginName, device);
603
851
  matterNode.port = this.port ? this.port++ : undefined;
604
852
  matterNode.passcode = this.passcode ? this.passcode++ : undefined;
@@ -614,6 +862,7 @@ export class MatterNode extends EventEmitter {
614
862
  }
615
863
  else if (this.matterbridge.bridgeMode === 'bridge') {
616
864
  if (device.mode === 'matter') {
865
+ // Register and add the device to the Matter server node
617
866
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
618
867
  if (!this.serverNode)
619
868
  throw new Error(`Server node not found for matter endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er})`);
@@ -626,6 +875,7 @@ export class MatterNode extends EventEmitter {
626
875
  }
627
876
  }
628
877
  else {
878
+ // Register and add the device to the Matter aggregator node
629
879
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
630
880
  if (!this.aggregatorNode)
631
881
  throw new Error(`Aggregator node not found for endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er})`);
@@ -639,6 +889,7 @@ export class MatterNode extends EventEmitter {
639
889
  }
640
890
  }
641
891
  else if (this.matterbridge.bridgeMode === 'childbridge') {
892
+ // Register and add the device to the plugin server node
642
893
  if (plugin.type === 'AccessoryPlatform') {
643
894
  try {
644
895
  this.log.debug(`Adding accessory endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
@@ -654,10 +905,12 @@ export class MatterNode extends EventEmitter {
654
905
  return;
655
906
  }
656
907
  }
908
+ // Register and add the device to the plugin aggregator node
657
909
  if (plugin.type === 'DynamicPlatform') {
658
910
  try {
659
911
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
660
912
  if (!this.serverNode) {
913
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
661
914
  await this.createDynamicPlugin(plugin);
662
915
  }
663
916
  if (device.mode === 'matter')
@@ -673,19 +926,31 @@ export class MatterNode extends EventEmitter {
673
926
  }
674
927
  if (plugin.registeredDevices !== undefined)
675
928
  plugin.registeredDevices++;
929
+ // Add the device to the DeviceManager
676
930
  await this.server.fetch({ type: 'devices_set', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
931
+ // Add the device to the DeviceManager
677
932
  await device.construction.ready;
933
+ // Subscribe to the attributes changed event
678
934
  await this.subscribeAttributeChanged(plugin, device);
679
935
  this.log.info(`Added endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${device.name}${nf})`);
680
936
  await this.yieldToNode(10);
681
937
  return device;
682
938
  }
939
+ /**
940
+ * Removes a MatterbridgeEndpoint from the specified plugin.
941
+ *
942
+ * @param {string} pluginName - The name of the plugin.
943
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
944
+ * @returns {Promise<MatterbridgeEndpoint | undefined>} A promise that resolves to the removed bridged endpoint, or undefined if there was an error.
945
+ */
683
946
  async removeBridgedEndpoint(pluginName, device) {
684
947
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
948
+ // Check if the plugin is registered
685
949
  const plugin = this.pluginManager.get(pluginName);
686
950
  if (!plugin)
687
951
  throw new Error(`Error removing bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): plugin not found`);
688
952
  if (device.serverNode) {
953
+ // TODO: Close and remove the MatterNode managing the device
689
954
  }
690
955
  else if (this.matterbridge.bridgeMode === 'bridge') {
691
956
  if (!this.aggregatorNode)
@@ -707,11 +972,25 @@ export class MatterNode extends EventEmitter {
707
972
  this.log.info(`Removed bridged endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${device.name}${nf})`);
708
973
  if (plugin.registeredDevices !== undefined)
709
974
  plugin.registeredDevices--;
975
+ // Remove the device from the DeviceManager
710
976
  await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
711
977
  await this.yieldToNode(10);
712
978
  return device;
713
979
  }
980
+ /**
981
+ * Removes all bridged endpoints from the specified plugin.
982
+ *
983
+ * @param {string} pluginName - The name of the plugin.
984
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
985
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
986
+ *
987
+ * @remarks
988
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
989
+ * It also applies a delay between each removal if specified.
990
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
991
+ */
714
992
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
993
+ // Check if the plugin is registered
715
994
  const plugin = this.pluginManager.get(pluginName);
716
995
  if (!plugin)
717
996
  throw new Error(`Error removing all bridged endpoints for plugin ${plg}${pluginName}${er}: plugin not found`);
@@ -726,6 +1005,7 @@ export class MatterNode extends EventEmitter {
726
1005
  this.log.info(`Removed bridged endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${endpoint?.name}${nf})`);
727
1006
  if (plugin.registeredDevices !== undefined)
728
1007
  plugin.registeredDevices--;
1008
+ // Remove the device from the DeviceManager
729
1009
  await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
730
1010
  await this.yieldToNode(10);
731
1011
  if (delay > 0)
@@ -734,6 +1014,25 @@ export class MatterNode extends EventEmitter {
734
1014
  if (delay > 0)
735
1015
  await wait(Number(process.env['MATTERBRIDGE_REMOVE_ALL_ENDPOINT_TIMEOUT_MS']) || 2000);
736
1016
  }
1017
+ /**
1018
+ * Registers a virtual device with the Matterbridge platform.
1019
+ * Virtual devices are only supported in bridge mode and childbridge mode with a DynamicPlatform.
1020
+ *
1021
+ * The virtual device is created as an instance of `Endpoint` with the provided device type.
1022
+ * When the virtual device is turned on, the provided callback function is executed.
1023
+ * The onOff state of the virtual device always reverts to false when the device is turned on.
1024
+ *
1025
+ * @param {string} pluginName - The name of the plugin.
1026
+ * @param { string } name - The name of the virtual device.
1027
+ * @param { 'light' | 'outlet' | 'switch' | 'mounted_switch' } type - The type of the virtual device.
1028
+ * @param { () => Promise<void> } callback - The callback to call when the virtual device is turned on.
1029
+ *
1030
+ * @returns {Promise<boolean>} A promise that resolves to true if the virtual device was successfully registered, false otherwise.
1031
+ *
1032
+ * @remarks
1033
+ * The virtual devices don't show up in the device list of the frontend.
1034
+ * Type 'switch' is not supported by Alexa and 'mounted_switch' is not supported by Apple Home.
1035
+ */
737
1036
  async addVirtualEndpoint(pluginName, name, type, callback) {
738
1037
  this.log.debug(`Creating virtual device ${plg}${pluginName}${db}:${dev}${name}${db}...`);
739
1038
  const plugin = this.pluginManager.get(pluginName);
@@ -758,10 +1057,20 @@ export class MatterNode extends EventEmitter {
758
1057
  await this.yieldToNode(10);
759
1058
  return true;
760
1059
  }
1060
+ /**
1061
+ * Subscribes to the attribute change event for the given device and plugin.
1062
+ * Specifically, it listens for changes in the 'reachable' attribute of the
1063
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
1064
+ *
1065
+ * @param {Plugin} plugin - The plugin associated with the device.
1066
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
1067
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
1068
+ */
761
1069
  async subscribeAttributeChanged(plugin, device) {
762
1070
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
763
1071
  return;
764
1072
  this.log.debug(`Subscribing attributes for endpoint ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db}:${or}${device.id}${db}:${or}${device.number}${db} (${zb}${device.name}${db})`);
1073
+ // Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
765
1074
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && this.serverNode) {
766
1075
  this.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
767
1076
  this.log.debug(`Accessory endpoint ${plg}${plugin.name}${nf}:${dev}${device.deviceName}${nf}:${or}${device.id}${nf}:${or}${device.number}${nf} is ${reachable ? 'reachable' : 'unreachable'}`);
@@ -769,6 +1078,7 @@ export class MatterNode extends EventEmitter {
769
1078
  type: 'frontend_attributechanged',
770
1079
  src: 'matter',
771
1080
  dst: 'frontend',
1081
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
772
1082
  params: { plugin: device.plugin, serialNumber: device.serialNumber, uniqueId: device.uniqueId, number: device.number, id: device.id, cluster: 'BasicInformation', attribute: 'reachable', value: reachable },
773
1083
  });
774
1084
  });
@@ -821,6 +1131,7 @@ export class MatterNode extends EventEmitter {
821
1131
  type: 'frontend_attributechanged',
822
1132
  src: 'matter',
823
1133
  dst: 'frontend',
1134
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
824
1135
  params: { plugin: device.plugin, serialNumber: device.serialNumber, uniqueId: device.uniqueId, number: device.number, id: device.id, cluster: sub.cluster, attribute: sub.attribute, value: value },
825
1136
  });
826
1137
  });
@@ -834,6 +1145,7 @@ export class MatterNode extends EventEmitter {
834
1145
  type: 'frontend_attributechanged',
835
1146
  src: 'matter',
836
1147
  dst: 'frontend',
1148
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
837
1149
  params: { plugin: device.plugin, serialNumber: device.serialNumber, uniqueId: device.uniqueId, number: child.number, id: child.id, cluster: sub.cluster, attribute: sub.attribute, value: value },
838
1150
  });
839
1151
  });
@@ -841,6 +1153,12 @@ export class MatterNode extends EventEmitter {
841
1153
  }
842
1154
  }
843
1155
  }
1156
+ /**
1157
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
1158
+ *
1159
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
1160
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
1161
+ */
844
1162
  sanitizeFabricInformations(fabricInfo) {
845
1163
  return fabricInfo.map((info) => {
846
1164
  return {
@@ -854,6 +1172,12 @@ export class MatterNode extends EventEmitter {
854
1172
  };
855
1173
  });
856
1174
  }
1175
+ /**
1176
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
1177
+ *
1178
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
1179
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
1180
+ */
857
1181
  sanitizeSessionInformation(sessions) {
858
1182
  return sessions
859
1183
  .filter((session) => session.isPeerActive)
@@ -880,10 +1204,21 @@ export class MatterNode extends EventEmitter {
880
1204
  };
881
1205
  });
882
1206
  }
1207
+ /**
1208
+ * Sets the reachability of the specified server node and trigger the corresponding event.
1209
+ *
1210
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
1211
+ */
883
1212
  async setServerReachability(reachable) {
884
1213
  await this.serverNode?.setStateOf(BasicInformationServer, { reachable });
885
1214
  this.serverNode?.act((agent) => this.serverNode?.eventsOf(BasicInformationServer).reachableChanged?.emit({ reachableNewValue: reachable }, agent.context));
886
1215
  }
1216
+ /**
1217
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
1218
+ *
1219
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
1220
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
1221
+ */
887
1222
  async setAggregatorReachability(aggregatorNode, reachable) {
888
1223
  for (const child of aggregatorNode.parts) {
889
1224
  this.log.debug(`Setting reachability of ${child?.deviceName} to ${reachable}`);
@@ -929,19 +1264,35 @@ export class MatterNode extends EventEmitter {
929
1264
  case 0x1488:
930
1265
  vendorName = '(ShortcutLabsFlic)';
931
1266
  break;
932
- case 65521:
1267
+ case 65521: // 0xFFF1
933
1268
  vendorName = '(MatterTest)';
934
1269
  break;
935
1270
  }
936
1271
  return vendorName;
937
1272
  };
1273
+ /**
1274
+ * Yield to the Node.js event loop:
1275
+ * 1. Flushes the current microtask queue (Promise/async continuations queued so far).
1276
+ * 2. Yields one macrotask turn (setImmediate) and then its microtasks.
1277
+ * 3. Waits a bit (setTimeout) to allow other macrotasks to run.
1278
+ *
1279
+ * This does **not** guarantee that every promise in the process is settled,
1280
+ * but it gives all already-scheduled work a very good chance to run before continuing.
1281
+ *
1282
+ * @param {number} [timeout] - Optional timeout in milliseconds to wait after yielding. Default is 100 ms (minimum 10 ms).
1283
+ * @returns {Promise<void>}
1284
+ */
938
1285
  async yieldToNode(timeout = 100) {
1286
+ // 1. Let all currently queued microtasks run
939
1287
  await Promise.resolve();
1288
+ // 2. Yield to the next event-loop turn (macrotask + its microtasks)
940
1289
  await new Promise((resolve) => {
941
1290
  setImmediate(resolve);
942
1291
  });
1292
+ // 3. Pause a bit to allow other macrotasks to run
943
1293
  await new Promise((resolve) => {
944
1294
  setTimeout(resolve, Math.min(timeout, 10));
945
1295
  });
946
1296
  }
947
1297
  }
1298
+ //# sourceMappingURL=matterNode.js.map