matterbridge 3.4.3-dev-20251209-e6cb85f → 3.4.3

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 (324) hide show
  1. package/README.md +2 -3
  2. package/dist/broadcastServer.d.ts +144 -0
  3. package/dist/broadcastServer.d.ts.map +1 -0
  4. package/dist/broadcastServer.js +119 -0
  5. package/dist/broadcastServer.js.map +1 -0
  6. package/dist/broadcastServerTypes.d.ts +841 -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/deviceManager.d.ts +135 -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 +98 -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 +48 -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 +61 -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 +71 -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 +76 -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 +17 -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 +46 -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 +47 -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 +67 -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 +81 -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 +168 -0
  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 +105 -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 +118 -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 +112 -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 +40 -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 +87 -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 +166 -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 +111 -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 +205 -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 +141 -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 +24 -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 +24 -0
  115. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  116. package/dist/dgram/mb_mdns.js +80 -15
  117. package/dist/dgram/mb_mdns.js.map +1 -0
  118. package/dist/dgram/mdns.d.ts +290 -0
  119. package/dist/dgram/mdns.d.ts.map +1 -0
  120. package/dist/dgram/mdns.js +299 -137
  121. package/dist/dgram/mdns.js.map +1 -0
  122. package/dist/dgram/multicast.d.ts +67 -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 +56 -0
  127. package/dist/dgram/unicast.d.ts.map +1 -0
  128. package/dist/dgram/unicast.js +54 -0
  129. package/dist/dgram/unicast.js.map +1 -0
  130. package/dist/frontend.d.ts +238 -0
  131. package/dist/frontend.d.ts.map +1 -0
  132. package/dist/frontend.js +455 -35
  133. package/dist/frontend.js.map +1 -0
  134. package/dist/frontendTypes.d.ts +529 -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 +48 -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 +34 -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 +2 -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 +345 -0
  151. package/dist/jestutils/jestHelpers.d.ts.map +1 -0
  152. package/dist/jestutils/jestHelpers.js +371 -14
  153. package/dist/jestutils/jestHelpers.js.map +1 -0
  154. package/dist/logger/export.d.ts +2 -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 +2 -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 +2 -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 +2 -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 +2 -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 +5 -0
  175. package/dist/matter/export.d.ts.map +1 -0
  176. package/dist/matter/export.js +3 -0
  177. package/dist/matter/export.js.map +1 -0
  178. package/dist/matter/types.d.ts +3 -0
  179. package/dist/matter/types.d.ts.map +1 -0
  180. package/dist/matter/types.js +3 -0
  181. package/dist/matter/types.js.map +1 -0
  182. package/dist/matterNode.d.ts +342 -0
  183. package/dist/matterNode.d.ts.map +1 -0
  184. package/dist/matterNode.js +369 -8
  185. package/dist/matterNode.js.map +1 -0
  186. package/dist/matterbridge.d.ts +492 -0
  187. package/dist/matterbridge.d.ts.map +1 -0
  188. package/dist/matterbridge.js +811 -46
  189. package/dist/matterbridge.js.map +1 -0
  190. package/dist/matterbridgeAccessoryPlatform.d.ts +41 -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 +2404 -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 +698 -0
  199. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  200. package/dist/matterbridgeDeviceTypes.js +635 -14
  201. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  202. package/dist/matterbridgeDynamicPlatform.d.ts +41 -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 +1507 -0
  207. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  208. package/dist/matterbridgeEndpoint.js +1444 -53
  209. package/dist/matterbridgeEndpoint.js.map +1 -0
  210. package/dist/matterbridgeEndpointHelpers.d.ts +787 -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 +166 -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 +539 -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 +251 -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 +372 -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 +181 -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 +2 -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 +84 -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 +101 -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 +66 -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 +35 -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 +34 -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 +39 -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 +32 -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 +54 -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 +45 -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 +13 -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 +53 -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 +89 -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 +87 -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 +103 -0
  291. package/dist/utils/isvalid.d.ts.map +1 -0
  292. package/dist/utils/isvalid.js +101 -0
  293. package/dist/utils/isvalid.js.map +1 -0
  294. package/dist/utils/network.d.ts +111 -0
  295. package/dist/utils/network.d.ts.map +1 -0
  296. package/dist/utils/network.js +96 -5
  297. package/dist/utils/network.js.map +1 -0
  298. package/dist/utils/spawn.d.ts +33 -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 +108 -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 +54 -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 +25 -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 +52 -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 +69 -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/npm-shrinkwrap.json +2 -2
  323. package/package.json +2 -1
  324. package/scripts/data_model.mjs +2058 -0
@@ -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 { FabricAction, 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;
@@ -68,12 +127,16 @@ export class MatterNode extends EventEmitter {
68
127
  this.server.on('broadcast_message', this.msgHandler.bind(this));
69
128
  if (this.verbose)
70
129
  this.log.debug(`BroadcastServer is ready`);
130
+ // Ensure the matterbridge directory exists
71
131
  fs.mkdirSync(matterbridge.matterbridgeDirectory, { recursive: true });
132
+ // Setup the plugin manager with thread server closed
72
133
  this.pluginManager = new PluginManager(this.matterbridge);
73
- this.pluginManager.logLevel = this.debug ? "debug" : "info";
134
+ this.pluginManager.logLevel = this.debug ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */;
135
+ // @ts-expect-error access private property
74
136
  this.pluginManager.server.close();
75
137
  if (this.verbose)
76
138
  this.log.debug(`PluginManager is ready`);
139
+ // Setup the matter environment
77
140
  this.environment.vars.set('log.level', MatterLogLevel.DEBUG);
78
141
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
79
142
  this.environment.vars.set('path.root', path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
@@ -81,9 +144,20 @@ export class MatterNode extends EventEmitter {
81
144
  this.environment.vars.set('runtime.exitcode', false);
82
145
  if (this.verbose)
83
146
  this.log.debug(`Matter Environment is ready`);
147
+ // Ensure MdnsService is registered in the default environment
148
+ // this.matterMdnsService = this.environment.get(MdnsService);
149
+ /*
150
+ this.matterMdnsService = new MdnsService(this.environment);
151
+ setImmediate(async () => {
152
+ await this.matterMdnsService?.construction.ready;
153
+ if (this.verbose) this.log.debug(`Matter MdnsService is ready`);
154
+ });
155
+ */
156
+ // Setup the matterbridge logger
84
157
  if (this.matterbridge.fileLogger) {
85
158
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.logLevel);
86
159
  }
160
+ // Setup the matter logger
87
161
  Logger.destinations.default.write = this.createDestinationMatterLogger();
88
162
  const levels = ['debug', 'info', 'notice', 'warn', 'error', 'fatal'];
89
163
  if (this.verbose)
@@ -91,6 +165,11 @@ export class MatterNode extends EventEmitter {
91
165
  if (this.debug)
92
166
  this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loaded`);
93
167
  }
168
+ /**
169
+ * Handles incoming messages from the broadcast server.
170
+ *
171
+ * @param {WorkerMessage} msg - The incoming message.
172
+ */
94
173
  async msgHandler(msg) {
95
174
  if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'matter')) {
96
175
  if (this.verbose)
@@ -121,32 +200,52 @@ export class MatterNode extends EventEmitter {
121
200
  }
122
201
  }
123
202
  }
203
+ /**
204
+ * Destroys the Matter instance.
205
+ * It closes the mDNS service and the broadcast server.
206
+ *
207
+ * @param {boolean} closeMdns - Whether to close the mDNS service. Default is true.
208
+ * @returns {Promise<void>} A promise that resolves when the instance is destroyed.
209
+ */
124
210
  async destroy(closeMdns = true) {
125
211
  if (this.verbose)
126
212
  this.log.debug(`Destroying MatterNode instance for ${this.storeId}...`);
213
+ // Close mDNS service
127
214
  if (closeMdns) {
128
215
  if (this.verbose)
129
216
  this.log.debug(`Closing Matter MdnsService for ${this.storeId}...`);
130
217
  this.matterMdnsService = this.environment.get(MdnsService);
218
+ // istanbul ignore next
219
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
220
  if (typeof this.matterMdnsService[Symbol.asyncDispose] === 'function')
132
221
  await this.matterMdnsService[Symbol.asyncDispose]();
222
+ // istanbul ignore next
223
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
133
224
  else
134
225
  await this.matterMdnsService.close();
135
226
  if (this.verbose)
136
227
  this.log.debug(`Closed Matter MdnsService for ${this.storeId}`);
137
228
  }
229
+ // Close the plugin manager
138
230
  this.pluginManager.destroy();
231
+ // Close the broadcast server
139
232
  this.server.close();
233
+ // Yield to the Node.js event loop to allow all resources to be released
140
234
  await this.yieldToNode();
141
235
  if (this.verbose)
142
236
  this.log.debug(`Destroyed MatterNode instance for ${this.storeId}`);
143
237
  }
144
238
  async create() {
145
239
  this.log.info('Creating Matter node...');
240
+ // Start matter storage
146
241
  await this.startMatterStorage();
242
+ // Load plugins from storage
243
+ // @ts-expect-error access private property
147
244
  this.pluginManager.matterbridge.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
245
+ // @ts-expect-error access private property
148
246
  this.pluginManager.matterbridge.nodeContext = await this.pluginManager.matterbridge.nodeStorage.createStorage('matterbridge');
149
247
  await this.pluginManager.loadFromStorage();
248
+ // Create Matter node for a server mode device
150
249
  if (this.pluginName && this.device && this.device.deviceName) {
151
250
  this.log.debug(`Creating MatterNode instance for server node device ${CYAN}${this.device.deviceName}${db}...`);
152
251
  await this.createDeviceServerNode(this.pluginName, this.device);
@@ -157,6 +256,7 @@ export class MatterNode extends EventEmitter {
157
256
  if (!this.pluginName) {
158
257
  this.log.debug('Creating MatterNode instance for all plugins...');
159
258
  await this.createMatterbridgeServerNode();
259
+ // Load all enabled plugins
160
260
  this.log.debug('Loading all plugins...');
161
261
  const loadPromises = [];
162
262
  for (const plugin of this.pluginManager.array().filter((p) => p.enabled)) {
@@ -169,6 +269,7 @@ export class MatterNode extends EventEmitter {
169
269
  }
170
270
  else {
171
271
  this.log.debug(`Creating MatterNode instance for plugin ${CYAN}${this.pluginName}${db}...`);
272
+ // Load only the specified plugin
172
273
  this.log.debug(`Loading plugin ${CYAN}${this.pluginName}${db}...`);
173
274
  await this.pluginManager.load(this.pluginName);
174
275
  this.log.debug(`Loaded plugin ${CYAN}${this.pluginName}${db}`);
@@ -182,13 +283,16 @@ export class MatterNode extends EventEmitter {
182
283
  if (!this.serverNode && !this.pluginName)
183
284
  throw new Error('Matter server node not created yet. Call create() first.');
184
285
  this.log.info('Starting MatterNode...');
286
+ // Start Matter node for a server mode device
185
287
  if (this.pluginName && this.device && this.device.deviceName) {
288
+ // Start the server node
186
289
  this.log.debug(`Starting MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
187
290
  await this.startServerNode();
188
291
  this.log.debug(`Started MatterNode for server device ${this.pluginName}:${this.device.deviceName}`);
189
292
  return;
190
293
  }
191
294
  if (!this.pluginName) {
295
+ // Start all loaded plugins
192
296
  this.log.debug('Starting all plugins...');
193
297
  const startPromises = [];
194
298
  for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.loaded)) {
@@ -196,9 +300,11 @@ export class MatterNode extends EventEmitter {
196
300
  }
197
301
  await Promise.all(startPromises);
198
302
  this.log.debug('Started all plugins');
303
+ // Start the server node
199
304
  this.log.debug('Starting MatterNode for all plugins...');
200
305
  await this.startServerNode();
201
306
  this.log.debug('Started MatterNode for all plugins');
307
+ // Configure all loaded plugins
202
308
  this.log.debug('Configuring all plugins...');
203
309
  const configurePromises = [];
204
310
  for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.started)) {
@@ -208,12 +314,16 @@ export class MatterNode extends EventEmitter {
208
314
  this.log.debug('Configured all plugins');
209
315
  }
210
316
  else {
317
+ // Start the loaded plugin
211
318
  await this.pluginManager.start(this.pluginName, 'Starting MatterNode');
319
+ // Start the server node
212
320
  this.log.debug(`Starting MatterNode for plugin ${this.pluginName}...`);
213
321
  await this.startServerNode();
214
322
  this.log.debug(`Started MatterNode for plugin ${this.pluginName}`);
323
+ // Configure the plugin
215
324
  await this.pluginManager.configure(this.pluginName);
216
325
  }
326
+ // Start the dependant MatterNodes
217
327
  this.log.debug(`Starting dependant MatterNodes...`);
218
328
  for (const dependantMatterNode of this.dependantMatterNodes.values()) {
219
329
  await dependantMatterNode.start();
@@ -226,13 +336,15 @@ export class MatterNode extends EventEmitter {
226
336
  if (!this.serverNode)
227
337
  throw new Error('Matter server node not created yet. Call create() first.');
228
338
  this.log.info('Stopping MatterNode...');
339
+ // Stop Matter node for a server mode device
229
340
  if (this.pluginName && this.device && this.device.deviceName) {
341
+ // Stop the server node
230
342
  this.log.debug(`Stopping MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
231
343
  await this.stopServerNode();
232
344
  this.serverNode = undefined;
233
345
  this.aggregatorNode = undefined;
234
346
  await this.stopMatterStorage();
235
- await this.destroy(false);
347
+ await this.destroy(false); // Do not close mDNS since it is shared
236
348
  this.log.debug(`Stopped MatterNode for server device ${this.pluginName}:${this.device.deviceName}`);
237
349
  this.log.info('Stopped MatterNode');
238
350
  await this.yieldToNode();
@@ -252,6 +364,7 @@ export class MatterNode extends EventEmitter {
252
364
  await this.pluginManager.shutdown(this.pluginName, 'Stopping MatterNode');
253
365
  this.log.debug(`Stopped plugin ${this.pluginName}`);
254
366
  }
367
+ // Stop the dependant MatterNodes
255
368
  this.log.debug(`Stopping dependant MatterNodes...`);
256
369
  for (const dependantMatterNode of this.dependantMatterNodes.values()) {
257
370
  await dependantMatterNode.stop();
@@ -264,24 +377,46 @@ export class MatterNode extends EventEmitter {
264
377
  this.log.info('Stopped MatterNode');
265
378
  await this.yieldToNode();
266
379
  }
380
+ /**
381
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (console and frontend).
382
+ * It also logs to file (matter.log) if fileLogger is true.
383
+ *
384
+ * @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
385
+ */
267
386
  createDestinationMatterLogger() {
268
- this.matterLog.logNameColor = '\x1b[34m';
387
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
269
388
  if (this.matterbridge.matterFileLogger) {
270
389
  this.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
271
390
  }
272
391
  return (text, message) => {
392
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
273
393
  const logger = text.slice(44, 44 + 20).trim();
274
394
  const msg = text.slice(65);
275
395
  this.matterLog.logName = logger;
276
396
  this.matterLog.log(MatterLogLevel.names[message.level], msg);
277
397
  };
278
398
  }
399
+ /**
400
+ * Starts the matter storage with name Matterbridge and performs a backup.
401
+ *
402
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
403
+ */
279
404
  async startMatterStorage() {
405
+ // Setup Matter storage
280
406
  this.log.info(`Starting matter node storage...`);
281
407
  this.matterStorageService = this.environment.get(StorageService);
282
408
  this.log.info(`Started matter node storage in ${CYAN}${this.matterStorageService.location}${nf}`);
409
+ // Backup matter storage since it is created/opened correctly
283
410
  await this.backupMatterStorage(path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
284
411
  }
412
+ /**
413
+ * Makes a backup copy of the specified matter storage directory.
414
+ *
415
+ * @param {string} storageName - The name of the storage directory to be backed up.
416
+ * @param {string} backupName - The name of the backup directory to be created.
417
+ * @private
418
+ * @returns {Promise<void>} A promise that resolves when the has been done.
419
+ */
285
420
  async backupMatterStorage(storageName, backupName) {
286
421
  this.log.info(`Creating matter node storage backup from ${CYAN}${storageName}${nf} to ${CYAN}${backupName}${nf}...`);
287
422
  try {
@@ -289,6 +424,7 @@ export class MatterNode extends EventEmitter {
289
424
  this.log.info('Created matter node storage backup');
290
425
  }
291
426
  catch (error) {
427
+ // istanbul ignore next if
292
428
  if (error instanceof Error && error?.code === 'ENOENT') {
293
429
  this.log.info(`No matter node storage found to backup from ${CYAN}${storageName}${nf} to ${CYAN}${backupName}${nf}`);
294
430
  }
@@ -297,6 +433,11 @@ export class MatterNode extends EventEmitter {
297
433
  }
298
434
  }
299
435
  }
436
+ /**
437
+ * Stops the matter storage.
438
+ *
439
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
440
+ */
300
441
  async stopMatterStorage() {
301
442
  this.log.info('Closing matter node storage...');
302
443
  await this.matterStorageManager?.close();
@@ -306,6 +447,21 @@ export class MatterNode extends EventEmitter {
306
447
  this.log.info('Closed matter node storage');
307
448
  this.emit('closed');
308
449
  }
450
+ /**
451
+ * Creates a server node storage context.
452
+ *
453
+ * @param {string} storeId - The storeId.
454
+ * @param {string} deviceName - The name of the device.
455
+ * @param {DeviceTypeId} deviceType - The device type of the device.
456
+ * @param {VendorId} vendorId - The vendor ID.
457
+ * @param {string} vendorName - The vendor name.
458
+ * @param {number} productId - The product ID.
459
+ * @param {string} productName - The product name.
460
+ * @param {string} [serialNumber] - The serial number of the device (optional).
461
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
462
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
463
+ * @throws {Error} If the storage service is not initialized.
464
+ */
309
465
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
310
466
  if (!this.matterStorageService) {
311
467
  throw new Error('No storage service initialized');
@@ -348,33 +504,52 @@ export class MatterNode extends EventEmitter {
348
504
  this.log.debug(`- hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
349
505
  return storageContext;
350
506
  }
507
+ /**
508
+ * Creates a server node.
509
+ *
510
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
511
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
512
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
513
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
514
+ * @throws {Error} If the matter storage context is not created yet.
515
+ */
351
516
  async createServerNode(port = 5540, passcode = 20252026, discriminator = 3850) {
352
517
  if (!this.matterStorageContext) {
353
518
  throw new Error('Matter server node context not created yet. Call createServerNodeContext() first.');
354
519
  }
355
520
  const storeId = await this.matterStorageContext.get('storeId');
356
521
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
522
+ /**
523
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
524
+ */
357
525
  const serverNode = await ServerNode.create({
526
+ // Required: Give the Node a unique ID which is used to store the state of this node
358
527
  id: storeId,
528
+ // Provide the environment to run this node in
359
529
  environment: this.environment,
530
+ // Provide Network relevant configuration like the port
360
531
  network: {
361
532
  listeningAddressIpv4: this.ipv4Address,
362
533
  listeningAddressIpv6: this.ipv6Address,
363
534
  port,
364
535
  },
536
+ // Provide the certificate for the device
365
537
  operationalCredentials: {
366
538
  certification: this.certification,
367
539
  },
540
+ // Provide Commissioning relevant settings
368
541
  commissioning: {
369
542
  passcode,
370
543
  discriminator,
371
544
  },
545
+ // Provide Node announcement settings
372
546
  productDescription: {
373
547
  name: await this.matterStorageContext.get('deviceName'),
374
548
  deviceType: DeviceTypeId(await this.matterStorageContext.get('deviceType')),
375
549
  vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
376
550
  productId: await this.matterStorageContext.get('productId'),
377
551
  },
552
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
378
553
  basicInformation: {
379
554
  vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
380
555
  vendorName: await this.matterStorageContext.get('vendorName'),
@@ -391,17 +566,23 @@ export class MatterNode extends EventEmitter {
391
566
  reachable: true,
392
567
  },
393
568
  });
569
+ /**
570
+ * This event is triggered when the device is initially commissioned successfully.
571
+ * This means: It is added to the first fabric.
572
+ */
394
573
  serverNode.lifecycle.commissioned.on(() => {
395
574
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
396
575
  this.advertisingNodes.delete(storeId);
397
576
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
398
577
  });
578
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
399
579
  serverNode.lifecycle.decommissioned.on(() => {
400
580
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
401
581
  this.advertisingNodes.delete(storeId);
402
582
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
403
583
  this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
404
584
  });
585
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
405
586
  serverNode.lifecycle.online.on(async () => {
406
587
  this.log.notice(`Server node for ${storeId} is online`);
407
588
  if (!serverNode.lifecycle.isCommissioned) {
@@ -419,6 +600,7 @@ export class MatterNode extends EventEmitter {
419
600
  this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is online`, timeout: 5, severity: 'success' } });
420
601
  this.emit('online', storeId);
421
602
  });
603
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
422
604
  serverNode.lifecycle.offline.on(() => {
423
605
  this.log.notice(`Server node for ${storeId} is offline`);
424
606
  this.advertisingNodes.delete(storeId);
@@ -426,11 +608,15 @@ export class MatterNode extends EventEmitter {
426
608
  this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
427
609
  this.emit('offline', storeId);
428
610
  });
611
+ /**
612
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
613
+ * information is needed.
614
+ */
429
615
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
430
616
  let action = '';
431
617
  switch (fabricAction) {
432
618
  case FabricAction.Added:
433
- this.advertisingNodes.delete(storeId);
619
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
434
620
  action = 'added';
435
621
  break;
436
622
  case FabricAction.Removed:
@@ -443,14 +629,22 @@ export class MatterNode extends EventEmitter {
443
629
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
444
630
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
445
631
  });
632
+ /**
633
+ * This event is triggered when an operative new session was opened by a Controller.
634
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
635
+ */
446
636
  serverNode.events.sessions.opened.on((session) => {
447
637
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
448
638
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
449
639
  });
640
+ /**
641
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
642
+ */
450
643
  serverNode.events.sessions.closed.on((session) => {
451
644
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
452
645
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
453
646
  });
647
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
454
648
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
455
649
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
456
650
  this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
@@ -459,6 +653,12 @@ export class MatterNode extends EventEmitter {
459
653
  this.log.info(`Created server node for ${this.storeId}`);
460
654
  return serverNode;
461
655
  }
656
+ /**
657
+ * Gets the matter serializable data of the specified server node.
658
+ *
659
+ * @param {ServerNode} [serverNode] - The server node to start.
660
+ * @returns {ApiMatter} The serializable data of the server node.
661
+ */
462
662
  getServerNodeData(serverNode) {
463
663
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
464
664
  return {
@@ -475,6 +675,13 @@ export class MatterNode extends EventEmitter {
475
675
  serialNumber: serverNode.state.basicInformation.serialNumber,
476
676
  };
477
677
  }
678
+ /**
679
+ * Starts the specified server node.
680
+ *
681
+ * @param {number} [timeout] - The timeout in milliseconds for starting the server node. Defaults to 30 seconds.
682
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
683
+ * @throws {Error} If the server node is not created yet.
684
+ */
478
685
  async startServerNode(timeout = 30000) {
479
686
  if (!this.serverNode) {
480
687
  throw new Error('Matter server node not created yet. Call create() first.');
@@ -485,9 +692,17 @@ export class MatterNode extends EventEmitter {
485
692
  this.log.notice(`Started ${this.serverNode.id} server node`);
486
693
  }
487
694
  catch (error) {
695
+ // istanbul ignore next
488
696
  this.log.error(`Failed to start ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
489
697
  }
490
698
  }
699
+ /**
700
+ * Stops the specified server node.
701
+ *
702
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
703
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
704
+ * @throws {Error} If the server node is not created yet.
705
+ */
491
706
  async stopServerNode(timeout = 30000) {
492
707
  if (!this.serverNode) {
493
708
  throw new Error('Matter server node not created yet. Call create() first.');
@@ -498,9 +713,16 @@ export class MatterNode extends EventEmitter {
498
713
  this.log.info(`Closed ${this.serverNode.id} server node`);
499
714
  }
500
715
  catch (error) {
716
+ // istanbul ignore next
501
717
  this.log.error(`Failed to close ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
502
718
  }
503
719
  }
720
+ /**
721
+ * Creates an aggregator node with the specified storage context.
722
+ *
723
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
724
+ * @throws {Error} If the matter storage context is not created yet.
725
+ */
504
726
  async createAggregatorNode() {
505
727
  if (!this.matterStorageContext) {
506
728
  throw new Error('Matter server node context not created yet. Call createServerNodeContext() first.');
@@ -510,9 +732,16 @@ export class MatterNode extends EventEmitter {
510
732
  this.log.info(`Created ${await this.matterStorageContext.get('storeId')} aggregator`);
511
733
  return aggregatorNode;
512
734
  }
735
+ /**
736
+ * Creates the matterbridge server node.
737
+ *
738
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created matterbridge server node.
739
+ */
513
740
  async createMatterbridgeServerNode() {
514
741
  this.log.debug(`Creating ${plg}Matterbridge${db} server node...`);
515
- this.matterStorageContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
742
+ this.matterStorageContext = await this.createServerNodeContext('Matterbridge', // storeId
743
+ 'Matterbridge', // deviceName
744
+ this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
516
745
  this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
517
746
  this.aggregatorNode = await this.createAggregatorNode();
518
747
  this.log.debug(`Adding ${plg}Matterbridge${db} aggregator node...`);
@@ -523,6 +752,13 @@ export class MatterNode extends EventEmitter {
523
752
  this.log.debug(`Created ${plg}Matterbridge${db} server node`);
524
753
  return this.serverNode;
525
754
  }
755
+ /**
756
+ * Creates and configures the server node for an accessory plugin for a given device.
757
+ *
758
+ * @param {Plugin | PluginName} plugin - The plugin to configure.
759
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
760
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the accessory plugin.
761
+ */
526
762
  async createAccessoryPlugin(plugin, device) {
527
763
  if (typeof plugin === 'string') {
528
764
  const _plugin = this.pluginManager.get(plugin);
@@ -544,6 +780,12 @@ export class MatterNode extends EventEmitter {
544
780
  }
545
781
  return this.serverNode;
546
782
  }
783
+ /**
784
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
785
+ *
786
+ * @param {Plugin | PluginName} plugin - The plugin to configure.
787
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the dynamic plugin.
788
+ */
547
789
  async createDynamicPlugin(plugin) {
548
790
  if (typeof plugin === 'string') {
549
791
  const _plugin = this.pluginManager.get(plugin);
@@ -567,6 +809,13 @@ export class MatterNode extends EventEmitter {
567
809
  }
568
810
  return this.serverNode;
569
811
  }
812
+ /**
813
+ * Creates and configures the server node for a single not bridged device.
814
+ *
815
+ * @param {Plugin | PluginName} plugin - The plugin to configure.
816
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
817
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the device with mode server.
818
+ */
570
819
  async createDeviceServerNode(plugin, device) {
571
820
  if (typeof plugin === 'string') {
572
821
  const _plugin = this.pluginManager.get(plugin);
@@ -587,13 +836,22 @@ export class MatterNode extends EventEmitter {
587
836
  }
588
837
  return this.serverNode;
589
838
  }
839
+ /**
840
+ * Adds a MatterbridgeEndpoint to the specified plugin.
841
+ *
842
+ * @param {string} pluginName - The name of the plugin.
843
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
844
+ * @returns {Promise<MatterbridgeEndpoint | undefined>} A promise that resolves to the added bridged endpoint, or undefined if there was an error.
845
+ */
590
846
  async addBridgedEndpoint(pluginName, device) {
847
+ // Check if the plugin is registered
591
848
  const plugin = this.pluginManager.get(pluginName);
592
849
  if (!plugin)
593
850
  throw new Error(`Error adding bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): plugin not found`);
594
851
  if (device.mode === 'server') {
595
852
  try {
596
853
  this.log.debug(`Creating MatterNode for device ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
854
+ // Create the MatterNode to manage the device
597
855
  const matterNode = new MatterNode(this.matterbridge, pluginName, device);
598
856
  this.dependantMatterNodes.set(device.id, matterNode);
599
857
  await matterNode.create();
@@ -606,6 +864,7 @@ export class MatterNode extends EventEmitter {
606
864
  }
607
865
  else if (this.matterbridge.bridgeMode === 'bridge') {
608
866
  if (device.mode === 'matter') {
867
+ // Register and add the device to the Matter server node
609
868
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
610
869
  if (!this.serverNode)
611
870
  throw new Error(`Server node not found for matter endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er})`);
@@ -618,6 +877,7 @@ export class MatterNode extends EventEmitter {
618
877
  }
619
878
  }
620
879
  else {
880
+ // Register and add the device to the Matter aggregator node
621
881
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
622
882
  if (!this.aggregatorNode)
623
883
  throw new Error(`Aggregator node not found for endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er})`);
@@ -631,6 +891,7 @@ export class MatterNode extends EventEmitter {
631
891
  }
632
892
  }
633
893
  else if (this.matterbridge.bridgeMode === 'childbridge') {
894
+ // Register and add the device to the plugin server node
634
895
  if (plugin.type === 'AccessoryPlatform') {
635
896
  try {
636
897
  this.log.debug(`Adding accessory endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
@@ -646,10 +907,12 @@ export class MatterNode extends EventEmitter {
646
907
  return;
647
908
  }
648
909
  }
910
+ // Register and add the device to the plugin aggregator node
649
911
  if (plugin.type === 'DynamicPlatform') {
650
912
  try {
651
913
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
652
914
  if (!this.serverNode) {
915
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
653
916
  await this.createDynamicPlugin(plugin);
654
917
  }
655
918
  if (device.mode === 'matter')
@@ -665,18 +928,30 @@ export class MatterNode extends EventEmitter {
665
928
  }
666
929
  if (plugin.registeredDevices !== undefined)
667
930
  plugin.registeredDevices++;
931
+ // Add the device to the DeviceManager
668
932
  await this.server.fetch({ type: 'devices_set', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
933
+ // Add the device to the DeviceManager
669
934
  await device.construction.ready;
935
+ // Subscribe to the attributes changed event
670
936
  await this.subscribeAttributeChanged(plugin, device);
671
937
  this.log.info(`Added endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${device.name}${nf})`);
672
938
  await this.yieldToNode(10);
673
939
  return device;
674
940
  }
941
+ /**
942
+ * Removes a MatterbridgeEndpoint from the specified plugin.
943
+ *
944
+ * @param {string} pluginName - The name of the plugin.
945
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
946
+ * @returns {Promise<MatterbridgeEndpoint | undefined>} A promise that resolves to the removed bridged endpoint, or undefined if there was an error.
947
+ */
675
948
  async removeBridgedEndpoint(pluginName, device) {
676
949
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
950
+ // Check if the plugin is registered
677
951
  const plugin = this.pluginManager.get(pluginName);
678
952
  if (!plugin)
679
953
  throw new Error(`Error removing bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): plugin not found`);
954
+ // Remove the device from the Matter aggregator node
680
955
  if (this.matterbridge.bridgeMode === 'bridge') {
681
956
  if (!this.aggregatorNode)
682
957
  throw new Error(`Error removing bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): aggregator node not found`);
@@ -697,11 +972,25 @@ export class MatterNode extends EventEmitter {
697
972
  this.log.info(`Removed bridged endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${device.name}${nf})`);
698
973
  if (plugin.registeredDevices !== undefined)
699
974
  plugin.registeredDevices--;
975
+ // Remove the device from the DeviceManager
700
976
  await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
701
977
  await this.yieldToNode(10);
702
978
  return device;
703
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
+ */
704
992
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
993
+ // Check if the plugin is registered
705
994
  const plugin = this.pluginManager.get(pluginName);
706
995
  if (!plugin)
707
996
  throw new Error(`Error removing all bridged endpoints for plugin ${plg}${pluginName}${er}: plugin not found`);
@@ -716,6 +1005,7 @@ export class MatterNode extends EventEmitter {
716
1005
  this.log.info(`Removed bridged endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${endpoint?.name}${nf})`);
717
1006
  if (plugin.registeredDevices !== undefined)
718
1007
  plugin.registeredDevices--;
1008
+ // Remove the device from the DeviceManager
719
1009
  await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
720
1010
  await this.yieldToNode(10);
721
1011
  if (delay > 0)
@@ -724,6 +1014,25 @@ export class MatterNode extends EventEmitter {
724
1014
  if (delay > 0)
725
1015
  await wait(Number(process.env['MATTERBRIDGE_REMOVE_ALL_ENDPOINT_TIMEOUT_MS']) || 2000);
726
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
+ */
727
1036
  async addVirtualEndpoint(pluginName, name, type, callback) {
728
1037
  this.log.debug(`Creating virtual device ${plg}${pluginName}${db}:${dev}${name}${db}...`);
729
1038
  const plugin = this.pluginManager.get(pluginName);
@@ -748,10 +1057,20 @@ export class MatterNode extends EventEmitter {
748
1057
  await this.yieldToNode(10);
749
1058
  return true;
750
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
+ */
751
1069
  async subscribeAttributeChanged(plugin, device) {
752
1070
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
753
1071
  return;
754
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
755
1074
  if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && this.serverNode) {
756
1075
  this.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
757
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'}`);
@@ -759,6 +1078,7 @@ export class MatterNode extends EventEmitter {
759
1078
  type: 'frontend_attributechanged',
760
1079
  src: 'matter',
761
1080
  dst: 'frontend',
1081
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
762
1082
  params: { plugin: device.plugin, serialNumber: device.serialNumber, uniqueId: device.uniqueId, number: device.number, id: device.id, cluster: 'BasicInformation', attribute: 'reachable', value: reachable },
763
1083
  });
764
1084
  });
@@ -811,6 +1131,7 @@ export class MatterNode extends EventEmitter {
811
1131
  type: 'frontend_attributechanged',
812
1132
  src: 'matter',
813
1133
  dst: 'frontend',
1134
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
814
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 },
815
1136
  });
816
1137
  });
@@ -824,6 +1145,7 @@ export class MatterNode extends EventEmitter {
824
1145
  type: 'frontend_attributechanged',
825
1146
  src: 'matter',
826
1147
  dst: 'frontend',
1148
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
827
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 },
828
1150
  });
829
1151
  });
@@ -831,6 +1153,12 @@ export class MatterNode extends EventEmitter {
831
1153
  }
832
1154
  }
833
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
+ */
834
1162
  sanitizeFabricInformations(fabricInfo) {
835
1163
  return fabricInfo.map((info) => {
836
1164
  return {
@@ -844,6 +1172,12 @@ export class MatterNode extends EventEmitter {
844
1172
  };
845
1173
  });
846
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
+ */
847
1181
  sanitizeSessionInformation(sessions) {
848
1182
  return sessions
849
1183
  .filter((session) => session.isPeerActive)
@@ -870,10 +1204,21 @@ export class MatterNode extends EventEmitter {
870
1204
  };
871
1205
  });
872
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
+ */
873
1212
  async setServerReachability(reachable) {
874
1213
  await this.serverNode?.setStateOf(BasicInformationServer, { reachable });
875
1214
  this.serverNode?.act((agent) => this.serverNode?.eventsOf(BasicInformationServer).reachableChanged?.emit({ reachableNewValue: reachable }, agent.context));
876
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
+ */
877
1222
  async setAggregatorReachability(aggregatorNode, reachable) {
878
1223
  for (const child of aggregatorNode.parts) {
879
1224
  this.log.debug(`Setting reachability of ${child?.deviceName} to ${reachable}`);
@@ -919,19 +1264,35 @@ export class MatterNode extends EventEmitter {
919
1264
  case 0x1488:
920
1265
  vendorName = '(ShortcutLabsFlic)';
921
1266
  break;
922
- case 65521:
1267
+ case 65521: // 0xFFF1
923
1268
  vendorName = '(MatterTest)';
924
1269
  break;
925
1270
  }
926
1271
  return vendorName;
927
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
+ */
928
1285
  async yieldToNode(timeout = 100) {
1286
+ // 1. Let all currently queued microtasks run
929
1287
  await Promise.resolve();
1288
+ // 2. Yield to the next event-loop turn (macrotask + its microtasks)
930
1289
  await new Promise((resolve) => {
931
1290
  setImmediate(resolve);
932
1291
  });
1292
+ // 3. Pause a bit to allow other macrotasks to run
933
1293
  await new Promise((resolve) => {
934
1294
  setTimeout(resolve, Math.min(timeout, 10));
935
1295
  });
936
1296
  }
937
1297
  }
1298
+ //# sourceMappingURL=matterNode.js.map