matterbridge 3.2.8-dev-20250920-1a6178d → 3.2.8

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 (284) hide show
  1. package/dist/cli.d.ts +26 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +91 -2
  4. package/dist/cli.js.map +1 -0
  5. package/dist/cliEmitter.d.ts +34 -0
  6. package/dist/cliEmitter.d.ts.map +1 -0
  7. package/dist/cliEmitter.js +30 -0
  8. package/dist/cliEmitter.js.map +1 -0
  9. package/dist/clusters/export.d.ts +2 -0
  10. package/dist/clusters/export.d.ts.map +1 -0
  11. package/dist/clusters/export.js +2 -0
  12. package/dist/clusters/export.js.map +1 -0
  13. package/dist/defaultConfigSchema.d.ts +28 -0
  14. package/dist/defaultConfigSchema.d.ts.map +1 -0
  15. package/dist/defaultConfigSchema.js +24 -0
  16. package/dist/defaultConfigSchema.js.map +1 -0
  17. package/dist/deviceManager.d.ts +112 -0
  18. package/dist/deviceManager.d.ts.map +1 -0
  19. package/dist/deviceManager.js +94 -1
  20. package/dist/deviceManager.js.map +1 -0
  21. package/dist/devices/airConditioner.d.ts +98 -0
  22. package/dist/devices/airConditioner.d.ts.map +1 -0
  23. package/dist/devices/airConditioner.js +57 -0
  24. package/dist/devices/airConditioner.js.map +1 -0
  25. package/dist/devices/batteryStorage.d.ts +48 -0
  26. package/dist/devices/batteryStorage.d.ts.map +1 -0
  27. package/dist/devices/batteryStorage.js +48 -1
  28. package/dist/devices/batteryStorage.js.map +1 -0
  29. package/dist/devices/cooktop.d.ts +60 -0
  30. package/dist/devices/cooktop.d.ts.map +1 -0
  31. package/dist/devices/cooktop.js +55 -0
  32. package/dist/devices/cooktop.js.map +1 -0
  33. package/dist/devices/dishwasher.d.ts +71 -0
  34. package/dist/devices/dishwasher.d.ts.map +1 -0
  35. package/dist/devices/dishwasher.js +57 -0
  36. package/dist/devices/dishwasher.js.map +1 -0
  37. package/dist/devices/evse.d.ts +75 -0
  38. package/dist/devices/evse.d.ts.map +1 -0
  39. package/dist/devices/evse.js +74 -10
  40. package/dist/devices/evse.js.map +1 -0
  41. package/dist/devices/export.d.ts +17 -0
  42. package/dist/devices/export.d.ts.map +1 -0
  43. package/dist/devices/export.js +5 -0
  44. package/dist/devices/export.js.map +1 -0
  45. package/dist/devices/extractorHood.d.ts +46 -0
  46. package/dist/devices/extractorHood.d.ts.map +1 -0
  47. package/dist/devices/extractorHood.js +42 -0
  48. package/dist/devices/extractorHood.js.map +1 -0
  49. package/dist/devices/heatPump.d.ts +47 -0
  50. package/dist/devices/heatPump.d.ts.map +1 -0
  51. package/dist/devices/heatPump.js +50 -2
  52. package/dist/devices/heatPump.js.map +1 -0
  53. package/dist/devices/laundryDryer.d.ts +67 -0
  54. package/dist/devices/laundryDryer.d.ts.map +1 -0
  55. package/dist/devices/laundryDryer.js +62 -3
  56. package/dist/devices/laundryDryer.js.map +1 -0
  57. package/dist/devices/laundryWasher.d.ts +81 -0
  58. package/dist/devices/laundryWasher.d.ts.map +1 -0
  59. package/dist/devices/laundryWasher.js +70 -4
  60. package/dist/devices/laundryWasher.js.map +1 -0
  61. package/dist/devices/microwaveOven.d.ts +168 -0
  62. package/dist/devices/microwaveOven.d.ts.map +1 -0
  63. package/dist/devices/microwaveOven.js +88 -5
  64. package/dist/devices/microwaveOven.js.map +1 -0
  65. package/dist/devices/oven.d.ts +105 -0
  66. package/dist/devices/oven.d.ts.map +1 -0
  67. package/dist/devices/oven.js +85 -0
  68. package/dist/devices/oven.js.map +1 -0
  69. package/dist/devices/refrigerator.d.ts +118 -0
  70. package/dist/devices/refrigerator.d.ts.map +1 -0
  71. package/dist/devices/refrigerator.js +102 -0
  72. package/dist/devices/refrigerator.js.map +1 -0
  73. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  74. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  75. package/dist/devices/roboticVacuumCleaner.js +100 -9
  76. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  77. package/dist/devices/solarPower.d.ts +40 -0
  78. package/dist/devices/solarPower.d.ts.map +1 -0
  79. package/dist/devices/solarPower.js +38 -0
  80. package/dist/devices/solarPower.js.map +1 -0
  81. package/dist/devices/speaker.d.ts +87 -0
  82. package/dist/devices/speaker.d.ts.map +1 -0
  83. package/dist/devices/speaker.js +84 -0
  84. package/dist/devices/speaker.js.map +1 -0
  85. package/dist/devices/temperatureControl.d.ts +166 -0
  86. package/dist/devices/temperatureControl.d.ts.map +1 -0
  87. package/dist/devices/temperatureControl.js +25 -3
  88. package/dist/devices/temperatureControl.js.map +1 -0
  89. package/dist/devices/waterHeater.d.ts +111 -0
  90. package/dist/devices/waterHeater.d.ts.map +1 -0
  91. package/dist/devices/waterHeater.js +82 -2
  92. package/dist/devices/waterHeater.js.map +1 -0
  93. package/dist/dgram/coap.d.ts +205 -0
  94. package/dist/dgram/coap.d.ts.map +1 -0
  95. package/dist/dgram/coap.js +126 -13
  96. package/dist/dgram/coap.js.map +1 -0
  97. package/dist/dgram/dgram.d.ts +141 -0
  98. package/dist/dgram/dgram.d.ts.map +1 -0
  99. package/dist/dgram/dgram.js +114 -2
  100. package/dist/dgram/dgram.js.map +1 -0
  101. package/dist/dgram/mb_coap.d.ts +24 -0
  102. package/dist/dgram/mb_coap.d.ts.map +1 -0
  103. package/dist/dgram/mb_coap.js +41 -3
  104. package/dist/dgram/mb_coap.js.map +1 -0
  105. package/dist/dgram/mb_mdns.d.ts +24 -0
  106. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  107. package/dist/dgram/mb_mdns.js +80 -15
  108. package/dist/dgram/mb_mdns.js.map +1 -0
  109. package/dist/dgram/mdns.d.ts +290 -0
  110. package/dist/dgram/mdns.d.ts.map +1 -0
  111. package/dist/dgram/mdns.js +299 -137
  112. package/dist/dgram/mdns.js.map +1 -0
  113. package/dist/dgram/multicast.d.ts +67 -0
  114. package/dist/dgram/multicast.d.ts.map +1 -0
  115. package/dist/dgram/multicast.js +62 -1
  116. package/dist/dgram/multicast.js.map +1 -0
  117. package/dist/dgram/unicast.d.ts +56 -0
  118. package/dist/dgram/unicast.d.ts.map +1 -0
  119. package/dist/dgram/unicast.js +54 -0
  120. package/dist/dgram/unicast.js.map +1 -0
  121. package/dist/frontend.d.ts +228 -0
  122. package/dist/frontend.d.ts.map +1 -0
  123. package/dist/frontend.js +429 -38
  124. package/dist/frontend.js.map +1 -0
  125. package/dist/frontendTypes.d.ts +572 -0
  126. package/dist/frontendTypes.d.ts.map +1 -0
  127. package/dist/frontendTypes.js +51 -3
  128. package/dist/frontendTypes.js.map +1 -0
  129. package/dist/globalMatterbridge.d.ts +59 -0
  130. package/dist/globalMatterbridge.d.ts.map +1 -0
  131. package/dist/globalMatterbridge.js +47 -0
  132. package/dist/globalMatterbridge.js.map +1 -0
  133. package/dist/helpers.d.ts +48 -0
  134. package/dist/helpers.d.ts.map +1 -0
  135. package/dist/helpers.js +53 -0
  136. package/dist/helpers.js.map +1 -0
  137. package/dist/index.d.ts +33 -0
  138. package/dist/index.d.ts.map +1 -0
  139. package/dist/index.js +30 -1
  140. package/dist/index.js.map +1 -0
  141. package/dist/logger/export.d.ts +2 -0
  142. package/dist/logger/export.d.ts.map +1 -0
  143. package/dist/logger/export.js +1 -0
  144. package/dist/logger/export.js.map +1 -0
  145. package/dist/matter/behaviors.d.ts +2 -0
  146. package/dist/matter/behaviors.d.ts.map +1 -0
  147. package/dist/matter/behaviors.js +2 -0
  148. package/dist/matter/behaviors.js.map +1 -0
  149. package/dist/matter/clusters.d.ts +2 -0
  150. package/dist/matter/clusters.d.ts.map +1 -0
  151. package/dist/matter/clusters.js +2 -0
  152. package/dist/matter/clusters.js.map +1 -0
  153. package/dist/matter/devices.d.ts +2 -0
  154. package/dist/matter/devices.d.ts.map +1 -0
  155. package/dist/matter/devices.js +2 -0
  156. package/dist/matter/devices.js.map +1 -0
  157. package/dist/matter/endpoints.d.ts +2 -0
  158. package/dist/matter/endpoints.d.ts.map +1 -0
  159. package/dist/matter/endpoints.js +2 -0
  160. package/dist/matter/endpoints.js.map +1 -0
  161. package/dist/matter/export.d.ts +5 -0
  162. package/dist/matter/export.d.ts.map +1 -0
  163. package/dist/matter/export.js +3 -0
  164. package/dist/matter/export.js.map +1 -0
  165. package/dist/matter/types.d.ts +3 -0
  166. package/dist/matter/types.d.ts.map +1 -0
  167. package/dist/matter/types.js +3 -0
  168. package/dist/matter/types.js.map +1 -0
  169. package/dist/matterbridge.d.ts +442 -0
  170. package/dist/matterbridge.d.ts.map +1 -0
  171. package/dist/matterbridge.js +775 -50
  172. package/dist/matterbridge.js.map +1 -0
  173. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  174. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  175. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  176. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  177. package/dist/matterbridgeBehaviors.d.ts +1747 -0
  178. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  179. package/dist/matterbridgeBehaviors.js +65 -5
  180. package/dist/matterbridgeBehaviors.js.map +1 -0
  181. package/dist/matterbridgeDeviceTypes.d.ts +761 -0
  182. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  183. package/dist/matterbridgeDeviceTypes.js +630 -17
  184. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  185. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  186. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  187. package/dist/matterbridgeDynamicPlatform.js +36 -0
  188. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  189. package/dist/matterbridgeEndpoint.d.ts +1515 -0
  190. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  191. package/dist/matterbridgeEndpoint.js +1373 -55
  192. package/dist/matterbridgeEndpoint.js.map +1 -0
  193. package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
  194. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  195. package/dist/matterbridgeEndpointHelpers.js +345 -12
  196. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  197. package/dist/matterbridgePlatform.d.ts +380 -0
  198. package/dist/matterbridgePlatform.d.ts.map +1 -0
  199. package/dist/matterbridgePlatform.js +304 -0
  200. package/dist/matterbridgePlatform.js.map +1 -0
  201. package/dist/matterbridgeTypes.d.ts +190 -0
  202. package/dist/matterbridgeTypes.d.ts.map +1 -0
  203. package/dist/matterbridgeTypes.js +25 -0
  204. package/dist/matterbridgeTypes.js.map +1 -0
  205. package/dist/pluginManager.d.ts +270 -0
  206. package/dist/pluginManager.d.ts.map +1 -0
  207. package/dist/pluginManager.js +249 -3
  208. package/dist/pluginManager.js.map +1 -0
  209. package/dist/shelly.d.ts +174 -0
  210. package/dist/shelly.d.ts.map +1 -0
  211. package/dist/shelly.js +172 -11
  212. package/dist/shelly.js.map +1 -0
  213. package/dist/storage/export.d.ts +2 -0
  214. package/dist/storage/export.d.ts.map +1 -0
  215. package/dist/storage/export.js +1 -0
  216. package/dist/storage/export.js.map +1 -0
  217. package/dist/update.d.ts +75 -0
  218. package/dist/update.d.ts.map +1 -0
  219. package/dist/update.js +69 -0
  220. package/dist/update.js.map +1 -0
  221. package/dist/utils/colorUtils.d.ts +99 -0
  222. package/dist/utils/colorUtils.d.ts.map +1 -0
  223. package/dist/utils/colorUtils.js +97 -2
  224. package/dist/utils/colorUtils.js.map +1 -0
  225. package/dist/utils/commandLine.d.ts +59 -0
  226. package/dist/utils/commandLine.d.ts.map +1 -0
  227. package/dist/utils/commandLine.js +54 -0
  228. package/dist/utils/commandLine.js.map +1 -0
  229. package/dist/utils/copyDirectory.d.ts +33 -0
  230. package/dist/utils/copyDirectory.d.ts.map +1 -0
  231. package/dist/utils/copyDirectory.js +38 -1
  232. package/dist/utils/copyDirectory.js.map +1 -0
  233. package/dist/utils/createDirectory.d.ts +34 -0
  234. package/dist/utils/createDirectory.d.ts.map +1 -0
  235. package/dist/utils/createDirectory.js +33 -0
  236. package/dist/utils/createDirectory.js.map +1 -0
  237. package/dist/utils/createZip.d.ts +39 -0
  238. package/dist/utils/createZip.d.ts.map +1 -0
  239. package/dist/utils/createZip.js +47 -2
  240. package/dist/utils/createZip.js.map +1 -0
  241. package/dist/utils/deepCopy.d.ts +32 -0
  242. package/dist/utils/deepCopy.d.ts.map +1 -0
  243. package/dist/utils/deepCopy.js +39 -0
  244. package/dist/utils/deepCopy.js.map +1 -0
  245. package/dist/utils/deepEqual.d.ts +54 -0
  246. package/dist/utils/deepEqual.d.ts.map +1 -0
  247. package/dist/utils/deepEqual.js +72 -1
  248. package/dist/utils/deepEqual.js.map +1 -0
  249. package/dist/utils/error.d.ts +44 -0
  250. package/dist/utils/error.d.ts.map +1 -0
  251. package/dist/utils/error.js +41 -0
  252. package/dist/utils/error.js.map +1 -0
  253. package/dist/utils/export.d.ts +13 -0
  254. package/dist/utils/export.d.ts.map +1 -0
  255. package/dist/utils/export.js +1 -0
  256. package/dist/utils/export.js.map +1 -0
  257. package/dist/utils/hex.d.ts +89 -0
  258. package/dist/utils/hex.d.ts.map +1 -0
  259. package/dist/utils/hex.js +124 -0
  260. package/dist/utils/hex.js.map +1 -0
  261. package/dist/utils/isvalid.d.ts +103 -0
  262. package/dist/utils/isvalid.d.ts.map +1 -0
  263. package/dist/utils/isvalid.js +101 -0
  264. package/dist/utils/isvalid.js.map +1 -0
  265. package/dist/utils/jestHelpers.d.ts +137 -0
  266. package/dist/utils/jestHelpers.d.ts.map +1 -0
  267. package/dist/utils/jestHelpers.js +153 -3
  268. package/dist/utils/jestHelpers.js.map +1 -0
  269. package/dist/utils/network.d.ts +84 -0
  270. package/dist/utils/network.d.ts.map +1 -0
  271. package/dist/utils/network.js +91 -5
  272. package/dist/utils/network.js.map +1 -0
  273. package/dist/utils/spawn.d.ts +33 -0
  274. package/dist/utils/spawn.d.ts.map +1 -0
  275. package/dist/utils/spawn.js +68 -0
  276. package/dist/utils/spawn.js.map +1 -0
  277. package/dist/utils/wait.d.ts +54 -0
  278. package/dist/utils/wait.d.ts.map +1 -0
  279. package/dist/utils/wait.js +60 -8
  280. package/dist/utils/wait.js.map +1 -0
  281. package/frontend/build/assets/index.js +1 -1
  282. package/frontend/build/index.html +12 -12
  283. package/npm-shrinkwrap.json +2 -2
  284. package/package.json +2 -1
@@ -1,15 +1,43 @@
1
+ /**
2
+ * This file contains the class Matterbridge.
3
+ *
4
+ * @file matterbridge.ts
5
+ * @author Luca Liguori
6
+ * @created 2023-12-29
7
+ * @version 1.6.0
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2023, 2024, 2025 Luca Liguori.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ // Node.js modules
1
25
  import os from 'node:os';
2
26
  import path from 'node:path';
3
27
  import { promises as fs } from 'node:fs';
4
28
  import EventEmitter from 'node:events';
5
29
  import { inspect } from 'node:util';
30
+ // AnsiLogger module
6
31
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE } from 'node-ansi-logger';
32
+ // NodeStorage module
7
33
  import { NodeStorageManager } from 'node-persist-manager';
34
+ // @matter
8
35
  import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
9
36
  import { FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
10
37
  import { AggregatorEndpoint } from '@matter/main/endpoints';
11
38
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
12
39
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
40
+ // Matterbridge
13
41
  import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
14
42
  import { withTimeout, waiter, wait } from './utils/wait.js';
15
43
  import { dev, plg, typ } from './matterbridgeTypes.js';
@@ -19,6 +47,9 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
19
47
  import { bridge } from './matterbridgeDeviceTypes.js';
20
48
  import { Frontend } from './frontend.js';
21
49
  import { addVirtualDevices } from './helpers.js';
50
+ /**
51
+ * Represents the Matterbridge application.
52
+ */
22
53
  export class Matterbridge extends EventEmitter {
23
54
  systemInformation = {
24
55
  interfaceName: '',
@@ -59,7 +90,7 @@ export class Matterbridge extends EventEmitter {
59
90
  shellySysUpdate: false,
60
91
  shellyMainUpdate: false,
61
92
  profile: getParameter('profile'),
62
- loggerLevel: "info",
93
+ loggerLevel: "info" /* LogLevel.INFO */,
63
94
  fileLogger: false,
64
95
  matterLoggerLevel: MatterLogLevel.INFO,
65
96
  matterFileLogger: false,
@@ -87,16 +118,20 @@ export class Matterbridge extends EventEmitter {
87
118
  profile = getParameter('profile');
88
119
  shutdown = false;
89
120
  failCountLimit = hasParameter('shelly') ? 600 : 120;
90
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
121
+ // Matterbridge logger
122
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
91
123
  matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
92
- matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
124
+ // Matter logger
125
+ matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
93
126
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
94
127
  plugins = new PluginManager(this);
95
128
  devices = new DeviceManager(this);
96
129
  frontend = new Frontend(this);
130
+ // Matterbridge storage
97
131
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
98
132
  nodeStorage;
99
133
  nodeContext;
134
+ // Cleanup
100
135
  hasCleanupStarted = false;
101
136
  initialized = false;
102
137
  startMatterInterval;
@@ -109,19 +144,23 @@ export class Matterbridge extends EventEmitter {
109
144
  sigtermHandler;
110
145
  exceptionHandler;
111
146
  rejectionHandler;
147
+ // Matter environment
112
148
  environment = Environment.default;
149
+ // Matter storage
113
150
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
114
151
  matterStorageService;
115
152
  matterStorageManager;
116
153
  matterbridgeContext;
117
154
  controllerContext;
118
- mdnsInterface;
119
- ipv4address;
120
- ipv6address;
121
- port;
122
- passcode;
123
- discriminator;
124
- certification;
155
+ // Matter parameters
156
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
157
+ ipv4address; // matter server node listeningAddressIpv4
158
+ ipv6address; // matter server node listeningAddressIpv6
159
+ port; // first server node port
160
+ passcode; // first server node passcode
161
+ discriminator; // first server node discriminator
162
+ certification; // device certification
163
+ // Matter nodes
125
164
  serverNode;
126
165
  aggregatorNode;
127
166
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
@@ -131,18 +170,35 @@ export class Matterbridge extends EventEmitter {
131
170
  aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
132
171
  aggregatorSerialNumber = getParameter('serialNumber');
133
172
  aggregatorUniqueId = getParameter('uniqueId');
173
+ /** Advertising nodes map: time advertising started keyed by storeId */
134
174
  advertisingNodes = new Map();
135
175
  static instance;
176
+ // We load asyncronously so is private
136
177
  constructor() {
137
178
  super();
138
179
  this.log.logNameColor = '\x1b[38;5;115m';
139
180
  }
181
+ /**
182
+ * Retrieves the list of Matterbridge devices.
183
+ *
184
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
185
+ */
140
186
  getDevices() {
141
187
  return this.devices.array();
142
188
  }
189
+ /**
190
+ * Retrieves the list of registered plugins.
191
+ *
192
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
193
+ */
143
194
  getPlugins() {
144
195
  return this.plugins.array();
145
196
  }
197
+ /**
198
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
199
+ *
200
+ * @param {LogLevel} logLevel The logger logLevel to set.
201
+ */
146
202
  async setLogLevel(logLevel) {
147
203
  if (this.log)
148
204
  this.log.logLevel = logLevel;
@@ -156,19 +212,31 @@ export class Matterbridge extends EventEmitter {
156
212
  for (const plugin of this.plugins) {
157
213
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
158
214
  continue;
159
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
160
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
161
- }
162
- let callbackLogLevel = "notice";
163
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
164
- callbackLogLevel = "info";
165
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
166
- callbackLogLevel = "debug";
215
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
216
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
217
+ }
218
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
219
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
220
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
221
+ callbackLogLevel = "info" /* LogLevel.INFO */;
222
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
223
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
167
224
  AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
168
225
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
169
226
  }
227
+ //* ************************************************************************************************************************************ */
228
+ // loadInstance() and cleanup() methods */
229
+ //* ************************************************************************************************************************************ */
230
+ /**
231
+ * Loads an instance of the Matterbridge class.
232
+ * If an instance already exists, return that instance.
233
+ *
234
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
235
+ * @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
236
+ */
170
237
  static async loadInstance(initialize = false) {
171
238
  if (!Matterbridge.instance) {
239
+ // eslint-disable-next-line no-console
172
240
  if (hasParameter('debug'))
173
241
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
174
242
  Matterbridge.instance = new Matterbridge();
@@ -177,8 +245,17 @@ export class Matterbridge extends EventEmitter {
177
245
  }
178
246
  return Matterbridge.instance;
179
247
  }
248
+ /**
249
+ * Call cleanup() and dispose MdnsService.
250
+ *
251
+ * @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
252
+ * @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
253
+ *
254
+ * @deprecated This method is deprecated and is ONLY used for jest tests.
255
+ */
180
256
  async destroyInstance(timeout = 1000, pause = 250) {
181
257
  this.log.info(`Destroy instance...`);
258
+ // Save server nodes to close
182
259
  const servers = [];
183
260
  if (this.bridgeMode === 'bridge') {
184
261
  if (this.serverNode)
@@ -196,72 +273,106 @@ export class Matterbridge extends EventEmitter {
196
273
  servers.push(device.serverNode);
197
274
  }
198
275
  }
276
+ // Let any already‐queued microtasks run first
199
277
  await Promise.resolve();
278
+ // Wait for the cleanup to finish
200
279
  await wait(pause, 'destroyInstance start', true);
280
+ // Cleanup
201
281
  await this.cleanup('destroying instance...', false, timeout);
282
+ // Close servers mdns service
202
283
  this.log.info(`Dispose ${servers.length} MdnsService...`);
203
284
  for (const server of servers) {
204
285
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
205
286
  this.log.info(`Closed ${server.id} MdnsService`);
206
287
  }
288
+ // Let any already‐queued microtasks run first
207
289
  await Promise.resolve();
290
+ // Wait for the cleanup to finish
208
291
  await wait(pause, 'destroyInstance stop', true);
209
292
  }
293
+ /**
294
+ * Initializes the Matterbridge application.
295
+ *
296
+ * @remarks
297
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
298
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
299
+ * node version, registers signal handlers, initializes storage, and parses the command line.
300
+ *
301
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
302
+ */
210
303
  async initialize() {
304
+ // for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
305
+ // Emit the initialize_started event
211
306
  this.emit('initialize_started');
307
+ // Set the restart mode
212
308
  if (hasParameter('service'))
213
309
  this.restartMode = 'service';
214
310
  if (hasParameter('docker'))
215
311
  this.restartMode = 'docker';
312
+ // Set the matterbridge home directory
216
313
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
217
314
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
218
315
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
316
+ // Set the matterbridge directory
219
317
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
220
318
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
221
319
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
222
320
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
223
321
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
322
+ // Set the matterbridge plugin directory
224
323
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
225
324
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
226
325
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
326
+ // Set the matterbridge cert directory
227
327
  this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
228
328
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
229
329
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
330
+ // Set the matterbridge root directory
230
331
  const { fileURLToPath } = await import('node:url');
231
332
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
232
333
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
233
334
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
335
+ // Setup the matter environment
234
336
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
235
337
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
236
338
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
237
339
  this.environment.vars.set('runtime.signals', false);
238
340
  this.environment.vars.set('runtime.exitcode', false);
341
+ // Register process handlers
239
342
  this.registerProcessHandlers();
343
+ // Initialize nodeStorage and nodeContext
240
344
  try {
241
345
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
242
346
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
243
347
  this.log.debug('Creating node storage context for matterbridge');
244
348
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
349
+ // TODO: Remove this code when node-persist-manager is updated
350
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
351
  const keys = (await this.nodeStorage?.storage.keys());
246
352
  for (const key of keys) {
247
353
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
354
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
248
355
  await this.nodeStorage?.storage.get(key);
249
356
  }
250
357
  const storages = await this.nodeStorage.getStorageNames();
251
358
  for (const storage of storages) {
252
359
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
253
360
  const nodeContext = await this.nodeStorage?.createStorage(storage);
361
+ // TODO: Remove this code when node-persist-manager is updated
362
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
363
  const keys = (await nodeContext?.storage.keys());
255
364
  keys.forEach(async (key) => {
256
365
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
257
366
  await nodeContext?.get(key);
258
367
  });
259
368
  }
369
+ // Creating a backup of the node storage since it is not corrupted
260
370
  this.log.debug('Creating node storage backup...');
261
371
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
262
372
  this.log.debug('Created node storage backup');
263
373
  }
264
374
  catch (error) {
375
+ // Restoring the backup of the node storage since it is corrupted
265
376
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
266
377
  if (hasParameter('norestore')) {
267
378
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -275,14 +386,19 @@ export class Matterbridge extends EventEmitter {
275
386
  if (!this.nodeStorage || !this.nodeContext) {
276
387
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
277
388
  }
389
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
278
390
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
391
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
279
392
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
393
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
280
394
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
395
+ // Certificate management
281
396
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
282
397
  try {
283
398
  await fs.access(pairingFilePath, fs.constants.R_OK);
284
399
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
285
400
  const pairingFileJson = JSON.parse(pairingFileContent);
401
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
286
402
  if (isValidNumber(pairingFileJson.vendorId)) {
287
403
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
288
404
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -311,11 +427,13 @@ export class Matterbridge extends EventEmitter {
311
427
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
312
428
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
313
429
  }
430
+ // Override the passcode and discriminator if they are present in the pairing file
314
431
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
315
432
  this.passcode = pairingFileJson.passcode;
316
433
  this.discriminator = pairingFileJson.discriminator;
317
434
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
318
435
  }
436
+ // Set the certification for matter.js if it is present in the pairing file
319
437
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
320
438
  const { hexToBuffer } = await import('./utils/hex.js');
321
439
  this.certification = {
@@ -330,41 +448,44 @@ export class Matterbridge extends EventEmitter {
330
448
  catch (error) {
331
449
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
332
450
  }
451
+ // Store the passcode, discriminator and port in the node context
333
452
  await this.nodeContext.set('matterport', this.port);
334
453
  await this.nodeContext.set('matterpasscode', this.passcode);
335
454
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
336
455
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
456
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
337
457
  if (hasParameter('logger')) {
338
458
  const level = getParameter('logger');
339
459
  if (level === 'debug') {
340
- this.log.logLevel = "debug";
460
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
341
461
  }
342
462
  else if (level === 'info') {
343
- this.log.logLevel = "info";
463
+ this.log.logLevel = "info" /* LogLevel.INFO */;
344
464
  }
345
465
  else if (level === 'notice') {
346
- this.log.logLevel = "notice";
466
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
347
467
  }
348
468
  else if (level === 'warn') {
349
- this.log.logLevel = "warn";
469
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
350
470
  }
351
471
  else if (level === 'error') {
352
- this.log.logLevel = "error";
472
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
353
473
  }
354
474
  else if (level === 'fatal') {
355
- this.log.logLevel = "fatal";
475
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
356
476
  }
357
477
  else {
358
478
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
359
- this.log.logLevel = "info";
479
+ this.log.logLevel = "info" /* LogLevel.INFO */;
360
480
  }
361
481
  }
362
482
  else {
363
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
483
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
364
484
  }
365
485
  this.frontend.logLevel = this.log.logLevel;
366
486
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
367
487
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
488
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
368
489
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
369
490
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
370
491
  this.matterbridgeInformation.fileLogger = true;
@@ -373,6 +494,7 @@ export class Matterbridge extends EventEmitter {
373
494
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
374
495
  if (this.profile !== undefined)
375
496
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
497
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
376
498
  if (hasParameter('matterlogger')) {
377
499
  const level = getParameter('matterlogger');
378
500
  if (level === 'debug') {
@@ -402,12 +524,14 @@ export class Matterbridge extends EventEmitter {
402
524
  Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
403
525
  }
404
526
  Logger.format = MatterLogFormat.ANSI;
527
+ // Create the logger for matter.js with file logging (context: matterFileLog)
405
528
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
406
529
  this.matterbridgeInformation.matterFileLogger = true;
407
530
  }
408
531
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterbridgeInformation.matterFileLogger);
409
532
  this.matterbridgeInformation.matterLoggerLevel = Logger.level;
410
533
  this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
534
+ // Log network interfaces
411
535
  const networkInterfaces = os.networkInterfaces();
412
536
  const availableAddresses = Object.entries(networkInterfaces);
413
537
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -420,6 +544,7 @@ export class Matterbridge extends EventEmitter {
420
544
  });
421
545
  }
422
546
  }
547
+ // Set the interface to use for matter server node mdnsInterface
423
548
  if (hasParameter('mdnsinterface')) {
424
549
  this.mdnsInterface = getParameter('mdnsinterface');
425
550
  }
@@ -428,6 +553,7 @@ export class Matterbridge extends EventEmitter {
428
553
  if (this.mdnsInterface === '')
429
554
  this.mdnsInterface = undefined;
430
555
  }
556
+ // Validate mdnsInterface
431
557
  if (this.mdnsInterface) {
432
558
  if (!availableInterfaces.includes(this.mdnsInterface)) {
433
559
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -440,6 +566,7 @@ export class Matterbridge extends EventEmitter {
440
566
  }
441
567
  if (this.mdnsInterface)
442
568
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
569
+ // Set the listeningAddressIpv4 for the matter commissioning server
443
570
  if (hasParameter('ipv4address')) {
444
571
  this.ipv4address = getParameter('ipv4address');
445
572
  }
@@ -448,6 +575,7 @@ export class Matterbridge extends EventEmitter {
448
575
  if (this.ipv4address === '')
449
576
  this.ipv4address = undefined;
450
577
  }
578
+ // Validate ipv4address
451
579
  if (this.ipv4address) {
452
580
  let isValid = false;
453
581
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -463,6 +591,7 @@ export class Matterbridge extends EventEmitter {
463
591
  await this.nodeContext.remove('matteripv4address');
464
592
  }
465
593
  }
594
+ // Set the listeningAddressIpv6 for the matter commissioning server
466
595
  if (hasParameter('ipv6address')) {
467
596
  this.ipv6address = getParameter('ipv6address');
468
597
  }
@@ -471,6 +600,7 @@ export class Matterbridge extends EventEmitter {
471
600
  if (this.ipv6address === '')
472
601
  this.ipv6address = undefined;
473
602
  }
603
+ // Validate ipv6address
474
604
  if (this.ipv6address) {
475
605
  let isValid = false;
476
606
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -479,6 +609,7 @@ export class Matterbridge extends EventEmitter {
479
609
  isValid = true;
480
610
  break;
481
611
  }
612
+ /* istanbul ignore next */
482
613
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
483
614
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
484
615
  isValid = true;
@@ -491,6 +622,7 @@ export class Matterbridge extends EventEmitter {
491
622
  await this.nodeContext.remove('matteripv6address');
492
623
  }
493
624
  }
625
+ // Initialize the virtual mode
494
626
  if (hasParameter('novirtual')) {
495
627
  this.matterbridgeInformation.virtualMode = 'disabled';
496
628
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -499,12 +631,17 @@ export class Matterbridge extends EventEmitter {
499
631
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
500
632
  }
501
633
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
634
+ // Initialize PluginManager
502
635
  this.plugins.logLevel = this.log.logLevel;
503
636
  await this.plugins.loadFromStorage();
637
+ // Initialize DeviceManager
504
638
  this.devices.logLevel = this.log.logLevel;
639
+ // Get the plugins from node storage and create the plugins node storage contexts
505
640
  for (const plugin of this.plugins) {
506
641
  const packageJson = await this.plugins.parse(plugin);
507
642
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
643
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
644
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
508
645
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
509
646
  try {
510
647
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -527,6 +664,7 @@ export class Matterbridge extends EventEmitter {
527
664
  await plugin.nodeContext.set('description', plugin.description);
528
665
  await plugin.nodeContext.set('author', plugin.author);
529
666
  }
667
+ // Log system info and create .matterbridge directory
530
668
  await this.logNodeAndSystemInfo();
531
669
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
532
670
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -534,6 +672,7 @@ export class Matterbridge extends EventEmitter {
534
672
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
535
673
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
536
674
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
675
+ // Check node version and throw error
537
676
  const minNodeVersion = 18;
538
677
  const nodeVersion = process.versions.node;
539
678
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -541,10 +680,18 @@ export class Matterbridge extends EventEmitter {
541
680
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
542
681
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
543
682
  }
683
+ // Parse command line
544
684
  await this.parseCommandLine();
685
+ // Emit the initialize_completed event
545
686
  this.emit('initialize_completed');
546
687
  this.initialized = true;
547
688
  }
689
+ /**
690
+ * Parses the command line arguments and performs the corresponding actions.
691
+ *
692
+ * @private
693
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
694
+ */
548
695
  async parseCommandLine() {
549
696
  if (hasParameter('help')) {
550
697
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -606,6 +753,19 @@ export class Matterbridge extends EventEmitter {
606
753
  }
607
754
  index++;
608
755
  }
756
+ /*
757
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
758
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
759
+ serializedRegisteredDevices?.forEach((device, index) => {
760
+ if (index !== serializedRegisteredDevices.length - 1) {
761
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
762
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
763
+ } else {
764
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
765
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
766
+ }
767
+ });
768
+ */
609
769
  this.shutdown = true;
610
770
  return;
611
771
  }
@@ -655,6 +815,7 @@ export class Matterbridge extends EventEmitter {
655
815
  this.shutdown = true;
656
816
  return;
657
817
  }
818
+ // Start the matter storage and create the matterbridge context
658
819
  try {
659
820
  await this.startMatterStorage();
660
821
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -670,18 +831,21 @@ export class Matterbridge extends EventEmitter {
670
831
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
671
832
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
672
833
  }
834
+ // Clear the matterbridge context if the reset parameter is set
673
835
  if (hasParameter('reset') && getParameter('reset') === undefined) {
674
836
  this.initialized = true;
675
837
  await this.shutdownProcessAndReset();
676
838
  this.shutdown = true;
677
839
  return;
678
840
  }
841
+ // Clear matterbridge plugin context if the reset parameter is set
679
842
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
680
843
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
681
844
  const plugin = this.plugins.get(getParameter('reset'));
682
845
  if (plugin) {
683
846
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
684
847
  if (!matterStorageManager) {
848
+ /* istanbul ignore next */
685
849
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
686
850
  }
687
851
  else {
@@ -700,37 +864,45 @@ export class Matterbridge extends EventEmitter {
700
864
  this.shutdown = true;
701
865
  return;
702
866
  }
867
+ // Initialize frontend
703
868
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
704
869
  await this.frontend.start(getIntParameter('frontend'));
870
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
705
871
  clearTimeout(this.checkUpdateTimeout);
706
872
  this.checkUpdateTimeout = setTimeout(async () => {
707
873
  const { checkUpdates } = await import('./update.js');
708
874
  checkUpdates(this);
709
875
  }, 30 * 1000).unref();
876
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
710
877
  clearInterval(this.checkUpdateInterval);
711
878
  this.checkUpdateInterval = setInterval(async () => {
712
879
  const { checkUpdates } = await import('./update.js');
713
880
  checkUpdates(this);
714
881
  }, 12 * 60 * 60 * 1000).unref();
882
+ // Start the matterbridge in mode test
715
883
  if (hasParameter('test')) {
716
884
  this.bridgeMode = 'bridge';
717
885
  return;
718
886
  }
887
+ // Start the matterbridge in mode controller
719
888
  if (hasParameter('controller')) {
720
889
  this.bridgeMode = 'controller';
721
890
  await this.startController();
722
891
  return;
723
892
  }
893
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
724
894
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
725
895
  this.log.info('Setting default matterbridge start mode to bridge');
726
896
  await this.nodeContext?.set('bridgeMode', 'bridge');
727
897
  }
898
+ // Start matterbridge in bridge mode
728
899
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
729
900
  this.bridgeMode = 'bridge';
730
901
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
731
902
  await this.startBridge();
732
903
  return;
733
904
  }
905
+ // Start matterbridge in childbridge mode
734
906
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
735
907
  this.bridgeMode = 'childbridge';
736
908
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -738,10 +910,20 @@ export class Matterbridge extends EventEmitter {
738
910
  return;
739
911
  }
740
912
  }
913
+ /**
914
+ * Asynchronously loads and starts the registered plugins.
915
+ *
916
+ * This method is responsible for initializing and starting all enabled plugins.
917
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
918
+ *
919
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
920
+ */
741
921
  async startPlugins() {
922
+ // Check, load and start the plugins
742
923
  for (const plugin of this.plugins) {
743
924
  plugin.configJson = await this.plugins.loadConfig(plugin);
744
925
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
926
+ // Check if the plugin is available
745
927
  if (!(await this.plugins.resolve(plugin.path))) {
746
928
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
747
929
  plugin.enabled = false;
@@ -758,10 +940,14 @@ export class Matterbridge extends EventEmitter {
758
940
  plugin.started = false;
759
941
  plugin.configured = false;
760
942
  plugin.registeredDevices = undefined;
761
- this.plugins.load(plugin, true, 'Matterbridge is starting');
943
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
762
944
  }
763
945
  this.frontend.wssSendRefreshRequired('plugins');
764
946
  }
947
+ /**
948
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
949
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
950
+ */
765
951
  registerProcessHandlers() {
766
952
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
767
953
  process.removeAllListeners('uncaughtException');
@@ -788,6 +974,9 @@ export class Matterbridge extends EventEmitter {
788
974
  };
789
975
  process.on('SIGTERM', this.sigtermHandler);
790
976
  }
977
+ /**
978
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
979
+ */
791
980
  deregisterProcessHandlers() {
792
981
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
793
982
  if (this.exceptionHandler)
@@ -804,12 +993,17 @@ export class Matterbridge extends EventEmitter {
804
993
  process.off('SIGTERM', this.sigtermHandler);
805
994
  this.sigtermHandler = undefined;
806
995
  }
996
+ /**
997
+ * Logs the node and system information.
998
+ */
807
999
  async logNodeAndSystemInfo() {
1000
+ // IP address information
808
1001
  const networkInterfaces = os.networkInterfaces();
809
1002
  this.systemInformation.interfaceName = '';
810
1003
  this.systemInformation.ipv4Address = '';
811
1004
  this.systemInformation.ipv6Address = '';
812
1005
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
1006
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
813
1007
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
814
1008
  continue;
815
1009
  if (!interfaceDetails) {
@@ -835,19 +1029,22 @@ export class Matterbridge extends EventEmitter {
835
1029
  break;
836
1030
  }
837
1031
  }
1032
+ // Node information
838
1033
  this.systemInformation.nodeVersion = process.versions.node;
839
1034
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
840
1035
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
841
1036
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1037
+ // Host system information
842
1038
  this.systemInformation.hostname = os.hostname();
843
1039
  this.systemInformation.user = os.userInfo().username;
844
- this.systemInformation.osType = os.type();
845
- this.systemInformation.osRelease = os.release();
846
- this.systemInformation.osPlatform = os.platform();
847
- this.systemInformation.osArch = os.arch();
848
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
849
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
850
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
1040
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1041
+ this.systemInformation.osRelease = os.release(); // Kernel version
1042
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1043
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
1044
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1045
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1046
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
1047
+ // Log the system information
851
1048
  this.log.debug('Host System Information:');
852
1049
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
853
1050
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -863,14 +1060,17 @@ export class Matterbridge extends EventEmitter {
863
1060
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
864
1061
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
865
1062
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1063
+ // Log directories
866
1064
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
867
1065
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
868
1066
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
869
1067
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
870
1068
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1069
+ // Global node_modules directory
871
1070
  if (this.nodeContext)
872
1071
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
873
1072
  if (this.globalModulesDirectory === '') {
1073
+ // First run of Matterbridge so the node storage is empty
874
1074
  this.log.debug(`Getting global node_modules directory...`);
875
1075
  try {
876
1076
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -883,6 +1083,7 @@ export class Matterbridge extends EventEmitter {
883
1083
  }
884
1084
  }
885
1085
  else {
1086
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
886
1087
  this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
887
1088
  try {
888
1089
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -894,58 +1095,86 @@ export class Matterbridge extends EventEmitter {
894
1095
  this.log.error(`Error checking global node_modules directory: ${error}`);
895
1096
  }
896
1097
  }
1098
+ // Matterbridge version
897
1099
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
898
1100
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
899
1101
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
900
1102
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1103
+ // Matterbridge latest version (will be set in the checkUpdate function)
901
1104
  if (this.nodeContext)
902
1105
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
903
1106
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1107
+ // Matterbridge dev version (will be set in the checkUpdate function)
904
1108
  if (this.nodeContext)
905
1109
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
906
1110
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1111
+ // Current working directory
907
1112
  const currentDir = process.cwd();
908
1113
  this.log.debug(`Current Working Directory: ${currentDir}`);
1114
+ // Command line arguments (excluding 'node' and the script name)
909
1115
  const cmdArgs = process.argv.slice(2).join(' ');
910
1116
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
911
1117
  }
1118
+ /**
1119
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1120
+ * It also logs to file (matter.log) if fileLogger is true.
1121
+ *
1122
+ * @param {boolean} fileLogger - Whether to log to file or not.
1123
+ * @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
1124
+ */
912
1125
  createDestinationMatterLogger(fileLogger) {
913
- this.matterLog.logNameColor = '\x1b[34m';
1126
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
914
1127
  if (fileLogger) {
915
1128
  this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, this.matterLoggerFile);
916
1129
  }
917
1130
  return (text, message) => {
1131
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
918
1132
  const logger = text.slice(44, 44 + 20).trim();
919
1133
  const msg = text.slice(65);
920
1134
  this.matterLog.logName = logger;
921
1135
  switch (message.level) {
922
1136
  case MatterLogLevel.DEBUG:
923
- this.matterLog.log("debug", msg);
1137
+ this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
924
1138
  break;
925
1139
  case MatterLogLevel.INFO:
926
- this.matterLog.log("info", msg);
1140
+ this.matterLog.log("info" /* LogLevel.INFO */, msg);
927
1141
  break;
928
1142
  case MatterLogLevel.NOTICE:
929
- this.matterLog.log("notice", msg);
1143
+ this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
930
1144
  break;
931
1145
  case MatterLogLevel.WARN:
932
- this.matterLog.log("warn", msg);
1146
+ this.matterLog.log("warn" /* LogLevel.WARN */, msg);
933
1147
  break;
934
1148
  case MatterLogLevel.ERROR:
935
- this.matterLog.log("error", msg);
1149
+ this.matterLog.log("error" /* LogLevel.ERROR */, msg);
936
1150
  break;
937
1151
  case MatterLogLevel.FATAL:
938
- this.matterLog.log("fatal", msg);
1152
+ this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
939
1153
  break;
940
1154
  }
941
1155
  };
942
1156
  }
1157
+ /**
1158
+ * Restarts the process by exiting the current instance and loading a new instance (/api/restart).
1159
+ *
1160
+ * @returns {Promise<void>} A promise that resolves when the restart is completed.
1161
+ */
943
1162
  async restartProcess() {
944
1163
  await this.cleanup('restarting...', true);
945
1164
  }
1165
+ /**
1166
+ * Shut down the process (/api/shutdown).
1167
+ *
1168
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1169
+ */
946
1170
  async shutdownProcess() {
947
1171
  await this.cleanup('shutting down...', false);
948
1172
  }
1173
+ /**
1174
+ * Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
1175
+ *
1176
+ * @returns {Promise<void>} A promise that resolves when the update is completed.
1177
+ */
949
1178
  async updateProcess() {
950
1179
  this.log.info('Updating matterbridge...');
951
1180
  try {
@@ -959,6 +1188,13 @@ export class Matterbridge extends EventEmitter {
959
1188
  this.frontend.wssSendRestartRequired();
960
1189
  await this.cleanup('updating...', false);
961
1190
  }
1191
+ /**
1192
+ * Unregister all devices and shut down the process (/api/unregister).
1193
+ *
1194
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1195
+ *
1196
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1197
+ */
962
1198
  async unregisterAndShutdownProcess(timeout = 1000) {
963
1199
  this.log.info('Unregistering all devices and shutting down...');
964
1200
  for (const plugin of this.plugins.array()) {
@@ -970,46 +1206,71 @@ export class Matterbridge extends EventEmitter {
970
1206
  await this.removeAllBridgedEndpoints(plugin.name, 100);
971
1207
  }
972
1208
  this.log.debug('Waiting for the MessageExchange to finish...');
973
- await wait(timeout);
1209
+ await wait(timeout); // Wait for MessageExchange to finish
974
1210
  this.log.debug('Cleaning up and shutting down...');
975
1211
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
976
1212
  }
1213
+ /**
1214
+ * Reset commissioning and shut down the process (/api/reset).
1215
+ *
1216
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1217
+ */
977
1218
  async shutdownProcessAndReset() {
978
1219
  await this.cleanup('shutting down with reset...', false);
979
1220
  }
1221
+ /**
1222
+ * Factory reset and shut down the process (/api/factory-reset).
1223
+ *
1224
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1225
+ */
980
1226
  async shutdownProcessAndFactoryReset() {
981
1227
  await this.cleanup('shutting down with factory reset...', false);
982
1228
  }
1229
+ /**
1230
+ * Cleans up the Matterbridge instance.
1231
+ *
1232
+ * @param {string} message - The cleanup message.
1233
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1234
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1235
+ *
1236
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1237
+ */
983
1238
  async cleanup(message, restart = false, timeout = 1000) {
984
1239
  if (this.initialized && !this.hasCleanupStarted) {
985
1240
  this.emit('cleanup_started');
986
1241
  this.hasCleanupStarted = true;
987
1242
  this.log.info(message);
1243
+ // Clear the start matter interval
988
1244
  if (this.startMatterInterval) {
989
1245
  clearInterval(this.startMatterInterval);
990
1246
  this.startMatterInterval = undefined;
991
1247
  this.log.debug('Start matter interval cleared');
992
1248
  }
1249
+ // Clear the check update timeout
993
1250
  if (this.checkUpdateTimeout) {
994
1251
  clearTimeout(this.checkUpdateTimeout);
995
1252
  this.checkUpdateTimeout = undefined;
996
1253
  this.log.debug('Check update timeout cleared');
997
1254
  }
1255
+ // Clear the check update interval
998
1256
  if (this.checkUpdateInterval) {
999
1257
  clearInterval(this.checkUpdateInterval);
1000
1258
  this.checkUpdateInterval = undefined;
1001
1259
  this.log.debug('Check update interval cleared');
1002
1260
  }
1261
+ // Clear the configure timeout
1003
1262
  if (this.configureTimeout) {
1004
1263
  clearTimeout(this.configureTimeout);
1005
1264
  this.configureTimeout = undefined;
1006
1265
  this.log.debug('Matterbridge configure timeout cleared');
1007
1266
  }
1267
+ // Clear the reachability timeout
1008
1268
  if (this.reachabilityTimeout) {
1009
1269
  clearTimeout(this.reachabilityTimeout);
1010
1270
  this.reachabilityTimeout = undefined;
1011
1271
  this.log.debug('Matterbridge reachability timeout cleared');
1012
1272
  }
1273
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
1013
1274
  for (const plugin of this.plugins) {
1014
1275
  if (!plugin.enabled || plugin.error)
1015
1276
  continue;
@@ -1020,6 +1281,7 @@ export class Matterbridge extends EventEmitter {
1020
1281
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1021
1282
  }
1022
1283
  }
1284
+ // Stop matter server nodes
1023
1285
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1024
1286
  this.log.debug('Waiting for the MessageExchange to finish...');
1025
1287
  await wait(timeout, 'Waiting for the MessageExchange to finish...', true);
@@ -1044,6 +1306,7 @@ export class Matterbridge extends EventEmitter {
1044
1306
  }
1045
1307
  }
1046
1308
  this.log.notice('Stopped matter server nodes');
1309
+ // Matter commisioning reset
1047
1310
  if (message === 'shutting down with reset...') {
1048
1311
  this.log.info('Resetting Matterbridge commissioning information...');
1049
1312
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1053,6 +1316,7 @@ export class Matterbridge extends EventEmitter {
1053
1316
  await this.matterbridgeContext?.clearAll();
1054
1317
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1055
1318
  }
1319
+ // Unregister all devices
1056
1320
  if (message === 'unregistered all devices and shutting down...') {
1057
1321
  if (this.bridgeMode === 'bridge') {
1058
1322
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1070,12 +1334,29 @@ export class Matterbridge extends EventEmitter {
1070
1334
  }
1071
1335
  this.log.info('Matter storage reset done!');
1072
1336
  }
1337
+ // Stop matter storage
1073
1338
  await this.stopMatterStorage();
1339
+ // Stop the frontend
1074
1340
  await this.frontend.stop();
1341
+ // Close the matterbridge node storage and context
1075
1342
  if (this.nodeStorage && this.nodeContext) {
1343
+ /*
1344
+ TODO: Implement serialization of registered devices in edge mode
1345
+ this.log.info('Saving registered devices...');
1346
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1347
+ this.devices.forEach(async (device) => {
1348
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1349
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1350
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1351
+ });
1352
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1353
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1354
+ */
1355
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1076
1356
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1077
1357
  await this.nodeContext.close();
1078
1358
  this.nodeContext = undefined;
1359
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1079
1360
  for (const plugin of this.plugins) {
1080
1361
  if (plugin.nodeContext) {
1081
1362
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1092,8 +1373,10 @@ export class Matterbridge extends EventEmitter {
1092
1373
  }
1093
1374
  this.plugins.clear();
1094
1375
  this.devices.clear();
1376
+ // Factory reset
1095
1377
  if (message === 'shutting down with factory reset...') {
1096
1378
  try {
1379
+ // Delete matter storage directory with its subdirectories and backup
1097
1380
  const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
1098
1381
  this.log.info(`Removing matter storage directory: ${dir}`);
1099
1382
  await fs.rm(dir, { recursive: true });
@@ -1102,11 +1385,13 @@ export class Matterbridge extends EventEmitter {
1102
1385
  await fs.rm(backup, { recursive: true });
1103
1386
  }
1104
1387
  catch (error) {
1388
+ // istanbul ignore next if
1105
1389
  if (error instanceof Error && error.code !== 'ENOENT') {
1106
1390
  this.log.error(`Error removing matter storage directory: ${error}`);
1107
1391
  }
1108
1392
  }
1109
1393
  try {
1394
+ // Delete matterbridge storage directory with its subdirectories and backup
1110
1395
  const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
1111
1396
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1112
1397
  await fs.rm(dir, { recursive: true });
@@ -1115,18 +1400,20 @@ export class Matterbridge extends EventEmitter {
1115
1400
  await fs.rm(backup, { recursive: true });
1116
1401
  }
1117
1402
  catch (error) {
1403
+ // istanbul ignore next if
1118
1404
  if (error instanceof Error && error.code !== 'ENOENT') {
1119
1405
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1120
1406
  }
1121
1407
  }
1122
1408
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1123
1409
  }
1410
+ // Deregisters the process handlers
1124
1411
  this.deregisterProcessHandlers();
1125
1412
  if (restart) {
1126
1413
  if (message === 'updating...') {
1127
1414
  this.log.info('Cleanup completed. Updating...');
1128
1415
  Matterbridge.instance = undefined;
1129
- this.emit('update');
1416
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1130
1417
  }
1131
1418
  else if (message === 'restarting...') {
1132
1419
  this.log.info('Cleanup completed. Restarting...');
@@ -1147,6 +1434,13 @@ export class Matterbridge extends EventEmitter {
1147
1434
  this.log.debug('Cleanup already started...');
1148
1435
  }
1149
1436
  }
1437
+ /**
1438
+ * Creates and configures the server node for a single not bridged device.
1439
+ *
1440
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1441
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1442
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1443
+ */
1150
1444
  async createDeviceServerNode(plugin, device) {
1151
1445
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1152
1446
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1157,6 +1451,13 @@ export class Matterbridge extends EventEmitter {
1157
1451
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1158
1452
  }
1159
1453
  }
1454
+ /**
1455
+ * Creates and configures the server node for an accessory plugin for a given device.
1456
+ *
1457
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1458
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1459
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1460
+ */
1160
1461
  async createAccessoryPlugin(plugin, device) {
1161
1462
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1162
1463
  plugin.locked = true;
@@ -1167,6 +1468,12 @@ export class Matterbridge extends EventEmitter {
1167
1468
  await plugin.serverNode.add(device);
1168
1469
  }
1169
1470
  }
1471
+ /**
1472
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
1473
+ *
1474
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1475
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
1476
+ */
1170
1477
  async createDynamicPlugin(plugin) {
1171
1478
  if (!plugin.locked) {
1172
1479
  plugin.locked = true;
@@ -1176,7 +1483,14 @@ export class Matterbridge extends EventEmitter {
1176
1483
  await plugin.serverNode.add(plugin.aggregatorNode);
1177
1484
  }
1178
1485
  }
1486
+ /**
1487
+ * Starts the Matterbridge in bridge mode.
1488
+ *
1489
+ * @private
1490
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1491
+ */
1179
1492
  async startBridge() {
1493
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1180
1494
  if (!this.matterStorageManager)
1181
1495
  throw new Error('No storage manager initialized');
1182
1496
  if (!this.matterbridgeContext)
@@ -1215,13 +1529,16 @@ export class Matterbridge extends EventEmitter {
1215
1529
  clearInterval(this.startMatterInterval);
1216
1530
  this.startMatterInterval = undefined;
1217
1531
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1218
- this.startServerNode(this.serverNode);
1532
+ // Start the Matter server node
1533
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1534
+ // Start the Matter server node of single devices in mode 'server'
1219
1535
  for (const device of this.devices.array()) {
1220
1536
  if (device.mode === 'server' && device.serverNode) {
1221
1537
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1222
- this.startServerNode(device.serverNode);
1538
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1223
1539
  }
1224
1540
  }
1541
+ // Configure the plugins
1225
1542
  this.configureTimeout = setTimeout(async () => {
1226
1543
  for (const plugin of this.plugins.array()) {
1227
1544
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1239,16 +1556,26 @@ export class Matterbridge extends EventEmitter {
1239
1556
  }
1240
1557
  this.frontend.wssSendRefreshRequired('plugins');
1241
1558
  }, 30 * 1000).unref();
1559
+ // Setting reachability to true
1242
1560
  this.reachabilityTimeout = setTimeout(() => {
1243
1561
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1244
1562
  if (this.aggregatorNode)
1245
1563
  this.setAggregatorReachability(this.aggregatorNode, true);
1246
1564
  }, 60 * 1000).unref();
1565
+ // Logger.get('LogServerNode').info(this.serverNode);
1247
1566
  this.emit('bridge_started');
1248
1567
  this.log.notice('Matterbridge bridge started successfully');
1568
+ this.frontend.wssSendRefreshRequired('settings');
1249
1569
  this.frontend.wssSendRefreshRequired('plugins');
1250
1570
  }, this.startMatterIntervalMs);
1251
1571
  }
1572
+ /**
1573
+ * Starts the Matterbridge in childbridge mode.
1574
+ *
1575
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1576
+ *
1577
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1578
+ */
1252
1579
  async startChildbridge(delay = 1000) {
1253
1580
  if (!this.matterStorageManager)
1254
1581
  throw new Error('No storage manager initialized');
@@ -1289,8 +1616,9 @@ export class Matterbridge extends EventEmitter {
1289
1616
  clearInterval(this.startMatterInterval);
1290
1617
  this.startMatterInterval = undefined;
1291
1618
  if (delay > 0)
1292
- await wait(delay);
1619
+ await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1293
1620
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1621
+ // Configure the plugins
1294
1622
  this.configureTimeout = setTimeout(async () => {
1295
1623
  for (const plugin of this.plugins.array()) {
1296
1624
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1327,27 +1655,252 @@ export class Matterbridge extends EventEmitter {
1327
1655
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1328
1656
  continue;
1329
1657
  }
1330
- this.startServerNode(plugin.serverNode);
1658
+ // Start the Matter server node
1659
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1660
+ // Setting reachability to true
1331
1661
  plugin.reachabilityTimeout = setTimeout(() => {
1332
1662
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggregator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
1333
1663
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1334
1664
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1335
1665
  }, 60 * 1000).unref();
1336
1666
  }
1667
+ // Start the Matter server node of single devices in mode 'server'
1337
1668
  for (const device of this.devices.array()) {
1338
1669
  if (device.mode === 'server' && device.serverNode) {
1339
1670
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1340
- this.startServerNode(device.serverNode);
1671
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1341
1672
  }
1342
1673
  }
1674
+ // Logger.get('LogServerNode').info(this.serverNode);
1343
1675
  this.emit('childbridge_started');
1344
1676
  this.log.notice('Matterbridge childbridge started successfully');
1677
+ this.frontend.wssSendRefreshRequired('settings');
1345
1678
  this.frontend.wssSendRefreshRequired('plugins');
1346
1679
  }, this.startMatterIntervalMs);
1347
1680
  }
1681
+ /**
1682
+ * Starts the Matterbridge controller.
1683
+ *
1684
+ * @private
1685
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1686
+ */
1348
1687
  async startController() {
1688
+ /*
1689
+ if (!this.matterStorageManager) {
1690
+ this.log.error('No storage manager initialized');
1691
+ await this.cleanup('No storage manager initialized');
1692
+ return;
1693
+ }
1694
+ this.log.info('Creating context: mattercontrollerContext');
1695
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1696
+ if (!this.controllerContext) {
1697
+ this.log.error('No storage context mattercontrollerContext initialized');
1698
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1699
+ return;
1700
+ }
1701
+
1702
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1703
+ this.matterServer = await this.createMatterServer(this.storageManager);
1704
+ this.log.info('Creating matter commissioning controller');
1705
+ this.commissioningController = new CommissioningController({
1706
+ autoConnect: false,
1707
+ });
1708
+ this.log.info('Adding matter commissioning controller to matter server');
1709
+ await this.matterServer.addCommissioningController(this.commissioningController);
1710
+
1711
+ this.log.info('Starting matter server');
1712
+ await this.matterServer.start();
1713
+ this.log.info('Matter server started');
1714
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1715
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1716
+ regulatoryCountryCode: 'XX',
1717
+ };
1718
+ const commissioningController = new CommissioningController({
1719
+ environment: {
1720
+ environment,
1721
+ id: uniqueId,
1722
+ },
1723
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1724
+ adminFabricLabel,
1725
+ });
1726
+
1727
+ if (hasParameter('pairingcode')) {
1728
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1729
+ const pairingCode = getParameter('pairingcode');
1730
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1731
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1732
+
1733
+ let longDiscriminator, setupPin, shortDiscriminator;
1734
+ if (pairingCode !== undefined) {
1735
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1736
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1737
+ longDiscriminator = undefined;
1738
+ setupPin = pairingCodeCodec.passcode;
1739
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1740
+ } else {
1741
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1742
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1743
+ setupPin = this.controllerContext.get('pin', 20202021);
1744
+ }
1745
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1746
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1747
+ }
1748
+
1749
+ const options = {
1750
+ commissioning: commissioningOptions,
1751
+ discovery: {
1752
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1753
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1754
+ },
1755
+ passcode: setupPin,
1756
+ } as NodeCommissioningOptions;
1757
+ this.log.info('Commissioning with options:', options);
1758
+ const nodeId = await this.commissioningController.commissionNode(options);
1759
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1760
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1761
+ } // (hasParameter('pairingcode'))
1762
+
1763
+ if (hasParameter('unpairall')) {
1764
+ this.log.info('***Commissioning controller unpairing all nodes...');
1765
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1766
+ for (const nodeId of nodeIds) {
1767
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1768
+ await this.commissioningController.removeNode(nodeId);
1769
+ }
1770
+ return;
1771
+ }
1772
+
1773
+ if (hasParameter('discover')) {
1774
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1775
+ // console.log(discover);
1776
+ }
1777
+
1778
+ if (!this.commissioningController.isCommissioned()) {
1779
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1780
+ return;
1781
+ }
1782
+
1783
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1784
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1785
+ for (const nodeId of nodeIds) {
1786
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1787
+
1788
+ const node = await this.commissioningController.connectNode(nodeId, {
1789
+ autoSubscribe: false,
1790
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1791
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1792
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1793
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1794
+ stateInformationCallback: (peerNodeId, info) => {
1795
+ switch (info) {
1796
+ case NodeStateInformation.Connected:
1797
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1798
+ break;
1799
+ case NodeStateInformation.Disconnected:
1800
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1801
+ break;
1802
+ case NodeStateInformation.Reconnecting:
1803
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1804
+ break;
1805
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1806
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1807
+ break;
1808
+ case NodeStateInformation.StructureChanged:
1809
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1810
+ break;
1811
+ case NodeStateInformation.Decommissioned:
1812
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1813
+ break;
1814
+ default:
1815
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1816
+ break;
1817
+ }
1818
+ },
1819
+ });
1820
+
1821
+ node.logStructure();
1822
+
1823
+ // Get the interaction client
1824
+ this.log.info('Getting the interaction client');
1825
+ const interactionClient = await node.getInteractionClient();
1826
+ let cluster;
1827
+ let attributes;
1828
+
1829
+ // Log BasicInformationCluster
1830
+ cluster = BasicInformationCluster;
1831
+ attributes = await interactionClient.getMultipleAttributes({
1832
+ attributes: [{ clusterId: cluster.id }],
1833
+ });
1834
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1835
+ attributes.forEach((attribute) => {
1836
+ this.log.info(
1837
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1838
+ );
1839
+ });
1840
+
1841
+ // Log PowerSourceCluster
1842
+ cluster = PowerSourceCluster;
1843
+ attributes = await interactionClient.getMultipleAttributes({
1844
+ attributes: [{ clusterId: cluster.id }],
1845
+ });
1846
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1847
+ attributes.forEach((attribute) => {
1848
+ this.log.info(
1849
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1850
+ );
1851
+ });
1852
+
1853
+ // Log ThreadNetworkDiagnostics
1854
+ cluster = ThreadNetworkDiagnosticsCluster;
1855
+ attributes = await interactionClient.getMultipleAttributes({
1856
+ attributes: [{ clusterId: cluster.id }],
1857
+ });
1858
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1859
+ attributes.forEach((attribute) => {
1860
+ this.log.info(
1861
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1862
+ );
1863
+ });
1864
+
1865
+ // Log SwitchCluster
1866
+ cluster = SwitchCluster;
1867
+ attributes = await interactionClient.getMultipleAttributes({
1868
+ attributes: [{ clusterId: cluster.id }],
1869
+ });
1870
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1871
+ attributes.forEach((attribute) => {
1872
+ this.log.info(
1873
+ `- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
1874
+ );
1875
+ });
1876
+
1877
+ this.log.info('Subscribing to all attributes and events');
1878
+ await node.subscribeAllAttributesAndEvents({
1879
+ ignoreInitialTriggers: false,
1880
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1881
+ this.log.info(
1882
+ `***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`,
1883
+ ),
1884
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1885
+ this.log.info(
1886
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1887
+ );
1888
+ },
1889
+ });
1890
+ this.log.info('Subscribed to all attributes and events');
1891
+ }
1892
+ */
1349
1893
  }
1894
+ /** */
1895
+ /** Matter.js methods */
1896
+ /** */
1897
+ /**
1898
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1899
+ *
1900
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1901
+ */
1350
1902
  async startMatterStorage() {
1903
+ // Setup Matter storage
1351
1904
  this.log.info(`Starting matter node storage...`);
1352
1905
  this.matterStorageService = this.environment.get(StorageService);
1353
1906
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1355,8 +1908,17 @@ export class Matterbridge extends EventEmitter {
1355
1908
  this.log.info('Matter node storage manager "Matterbridge" created');
1356
1909
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1357
1910
  this.log.info('Matter node storage started');
1911
+ // Backup matter storage since it is created/opened correctly
1358
1912
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1359
1913
  }
1914
+ /**
1915
+ * Makes a backup copy of the specified matter storage directory.
1916
+ *
1917
+ * @param {string} storageName - The name of the storage directory to be backed up.
1918
+ * @param {string} backupName - The name of the backup directory to be created.
1919
+ * @private
1920
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1921
+ */
1360
1922
  async backupMatterStorage(storageName, backupName) {
1361
1923
  this.log.info('Creating matter node storage backup...');
1362
1924
  try {
@@ -1367,6 +1929,11 @@ export class Matterbridge extends EventEmitter {
1367
1929
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1368
1930
  }
1369
1931
  }
1932
+ /**
1933
+ * Stops the matter storage.
1934
+ *
1935
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1936
+ */
1370
1937
  async stopMatterStorage() {
1371
1938
  this.log.info('Closing matter node storage...');
1372
1939
  await this.matterStorageManager?.close();
@@ -1375,6 +1942,20 @@ export class Matterbridge extends EventEmitter {
1375
1942
  this.matterbridgeContext = undefined;
1376
1943
  this.log.info('Matter node storage closed');
1377
1944
  }
1945
+ /**
1946
+ * Creates a server node storage context.
1947
+ *
1948
+ * @param {string} storeId - The storeId.
1949
+ * @param {string} deviceName - The name of the device.
1950
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1951
+ * @param {number} vendorId - The vendor ID.
1952
+ * @param {string} vendorName - The vendor name.
1953
+ * @param {number} productId - The product ID.
1954
+ * @param {string} productName - The product name.
1955
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1956
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
1957
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1958
+ */
1378
1959
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1379
1960
  const { randomBytes } = await import('node:crypto');
1380
1961
  if (!this.matterStorageService)
@@ -1414,6 +1995,15 @@ export class Matterbridge extends EventEmitter {
1414
1995
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1415
1996
  return storageContext;
1416
1997
  }
1998
+ /**
1999
+ * Creates a server node.
2000
+ *
2001
+ * @param {StorageContext} storageContext - The storage context for the server node.
2002
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
2003
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
2004
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
2005
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
2006
+ */
1417
2007
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1418
2008
  const storeId = await storageContext.get('storeId');
1419
2009
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1423,24 +2013,37 @@ export class Matterbridge extends EventEmitter {
1423
2013
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1424
2014
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1425
2015
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2016
+ /**
2017
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
2018
+ */
1426
2019
  const serverNode = await ServerNode.create({
2020
+ // Required: Give the Node a unique ID which is used to store the state of this node
1427
2021
  id: storeId,
2022
+ // Provide Network relevant configuration like the port
2023
+ // Optional when operating only one device on a host, Default port is 5540
1428
2024
  network: {
1429
2025
  listeningAddressIpv4: this.ipv4address,
1430
2026
  listeningAddressIpv6: this.ipv6address,
1431
2027
  port,
1432
2028
  },
2029
+ // Provide the certificate for the device
1433
2030
  operationalCredentials: {
1434
2031
  certification: this.certification,
1435
2032
  },
2033
+ // Provide Commissioning relevant settings
2034
+ // Optional for development/testing purposes
1436
2035
  commissioning: {
1437
2036
  passcode,
1438
2037
  discriminator,
1439
2038
  },
2039
+ // Provide Node announcement settings
2040
+ // Optional: If Ommitted some development defaults are used
1440
2041
  productDescription: {
1441
2042
  name: await storageContext.get('deviceName'),
1442
2043
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1443
2044
  },
2045
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
2046
+ // Optional: If Omitted some development defaults are used
1444
2047
  basicInformation: {
1445
2048
  vendorId: VendorId(await storageContext.get('vendorId')),
1446
2049
  vendorName: await storageContext.get('vendorName'),
@@ -1457,17 +2060,23 @@ export class Matterbridge extends EventEmitter {
1457
2060
  reachable: true,
1458
2061
  },
1459
2062
  });
2063
+ /**
2064
+ * This event is triggered when the device is initially commissioned successfully.
2065
+ * This means: It is added to the first fabric.
2066
+ */
1460
2067
  serverNode.lifecycle.commissioned.on(() => {
1461
2068
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1462
2069
  this.advertisingNodes.delete(storeId);
1463
2070
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1464
2071
  });
2072
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1465
2073
  serverNode.lifecycle.decommissioned.on(() => {
1466
2074
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1467
2075
  this.advertisingNodes.delete(storeId);
1468
2076
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1469
2077
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1470
2078
  });
2079
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1471
2080
  serverNode.lifecycle.online.on(async () => {
1472
2081
  this.log.notice(`Server node for ${storeId} is online`);
1473
2082
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1478,13 +2087,16 @@ export class Matterbridge extends EventEmitter {
1478
2087
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1479
2088
  }
1480
2089
  else {
2090
+ // istanbul ignore next
1481
2091
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2092
+ // istanbul ignore next
1482
2093
  this.advertisingNodes.delete(storeId);
1483
2094
  }
1484
2095
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1485
2096
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1486
2097
  this.emit('online', storeId);
1487
2098
  });
2099
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1488
2100
  serverNode.lifecycle.offline.on(() => {
1489
2101
  this.log.notice(`Server node for ${storeId} is offline`);
1490
2102
  this.advertisingNodes.delete(storeId);
@@ -1492,11 +2104,15 @@ export class Matterbridge extends EventEmitter {
1492
2104
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1493
2105
  this.emit('offline', storeId);
1494
2106
  });
2107
+ /**
2108
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2109
+ * information is needed.
2110
+ */
1495
2111
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1496
2112
  let action = '';
1497
2113
  switch (fabricAction) {
1498
2114
  case FabricAction.Added:
1499
- this.advertisingNodes.delete(storeId);
2115
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1500
2116
  action = 'added';
1501
2117
  break;
1502
2118
  case FabricAction.Removed:
@@ -1509,14 +2125,22 @@ export class Matterbridge extends EventEmitter {
1509
2125
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1510
2126
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1511
2127
  });
2128
+ /**
2129
+ * This event is triggered when an operative new session was opened by a Controller.
2130
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2131
+ */
1512
2132
  serverNode.events.sessions.opened.on((session) => {
1513
2133
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1514
2134
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1515
2135
  });
2136
+ /**
2137
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2138
+ */
1516
2139
  serverNode.events.sessions.closed.on((session) => {
1517
2140
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1518
2141
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1519
2142
  });
2143
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1520
2144
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1521
2145
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1522
2146
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
@@ -1524,6 +2148,12 @@ export class Matterbridge extends EventEmitter {
1524
2148
  this.log.info(`Created server node for ${storeId}`);
1525
2149
  return serverNode;
1526
2150
  }
2151
+ /**
2152
+ * Gets the matter sanitized data of the specified server node.
2153
+ *
2154
+ * @param {ServerNode} [serverNode] - The server node to start.
2155
+ * @returns {ApiMatter} The sanitized data of the server node.
2156
+ */
1527
2157
  getServerNodeData(serverNode) {
1528
2158
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1529
2159
  return {
@@ -1540,12 +2170,25 @@ export class Matterbridge extends EventEmitter {
1540
2170
  serialNumber: serverNode.state.basicInformation.serialNumber,
1541
2171
  };
1542
2172
  }
2173
+ /**
2174
+ * Starts the specified server node.
2175
+ *
2176
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2177
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2178
+ */
1543
2179
  async startServerNode(matterServerNode) {
1544
2180
  if (!matterServerNode)
1545
2181
  return;
1546
2182
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1547
2183
  await matterServerNode.start();
1548
2184
  }
2185
+ /**
2186
+ * Stops the specified server node.
2187
+ *
2188
+ * @param {ServerNode} matterServerNode - The server node to stop.
2189
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2190
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2191
+ */
1549
2192
  async stopServerNode(matterServerNode, timeout = 30000) {
1550
2193
  if (!matterServerNode)
1551
2194
  return;
@@ -1558,13 +2201,27 @@ export class Matterbridge extends EventEmitter {
1558
2201
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1559
2202
  }
1560
2203
  }
2204
+ /**
2205
+ * Creates an aggregator node with the specified storage context.
2206
+ *
2207
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2208
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2209
+ */
1561
2210
  async createAggregatorNode(storageContext) {
1562
2211
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1563
2212
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1564
2213
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1565
2214
  return aggregatorNode;
1566
2215
  }
2216
+ /**
2217
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2218
+ *
2219
+ * @param {string} pluginName - The name of the plugin.
2220
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2221
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2222
+ */
1567
2223
  async addBridgedEndpoint(pluginName, device) {
2224
+ // Check if the plugin is registered
1568
2225
  const plugin = this.plugins.get(pluginName);
1569
2226
  if (!plugin) {
1570
2227
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1584,6 +2241,7 @@ export class Matterbridge extends EventEmitter {
1584
2241
  }
1585
2242
  else if (this.bridgeMode === 'bridge') {
1586
2243
  if (device.mode === 'matter') {
2244
+ // Register and add the device to the matterbridge server node
1587
2245
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1588
2246
  if (!this.serverNode) {
1589
2247
  this.log.error('Server node not found for Matterbridge');
@@ -1600,6 +2258,7 @@ export class Matterbridge extends EventEmitter {
1600
2258
  }
1601
2259
  }
1602
2260
  else {
2261
+ // Register and add the device to the matterbridge aggregator node
1603
2262
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1604
2263
  if (!this.aggregatorNode) {
1605
2264
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1617,6 +2276,7 @@ export class Matterbridge extends EventEmitter {
1617
2276
  }
1618
2277
  }
1619
2278
  else if (this.bridgeMode === 'childbridge') {
2279
+ // Register and add the device to the plugin server node
1620
2280
  if (plugin.type === 'AccessoryPlatform') {
1621
2281
  try {
1622
2282
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1640,10 +2300,12 @@ export class Matterbridge extends EventEmitter {
1640
2300
  return;
1641
2301
  }
1642
2302
  }
2303
+ // Register and add the device to the plugin aggregator node
1643
2304
  if (plugin.type === 'DynamicPlatform') {
1644
2305
  try {
1645
2306
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1646
2307
  await this.createDynamicPlugin(plugin);
2308
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1647
2309
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1648
2310
  if (!plugin.aggregatorNode) {
1649
2311
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1664,17 +2326,28 @@ export class Matterbridge extends EventEmitter {
1664
2326
  }
1665
2327
  if (plugin.registeredDevices !== undefined)
1666
2328
  plugin.registeredDevices++;
2329
+ // Add the device to the DeviceManager
1667
2330
  this.devices.set(device);
2331
+ // Subscribe to the reachable$Changed event
1668
2332
  await this.subscribeAttributeChanged(plugin, device);
1669
2333
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1670
2334
  }
2335
+ /**
2336
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2337
+ *
2338
+ * @param {string} pluginName - The name of the plugin.
2339
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2340
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2341
+ */
1671
2342
  async removeBridgedEndpoint(pluginName, device) {
1672
2343
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2344
+ // Check if the plugin is registered
1673
2345
  const plugin = this.plugins.get(pluginName);
1674
2346
  if (!plugin) {
1675
2347
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1676
2348
  return;
1677
2349
  }
2350
+ // Register and add the device to the matterbridge aggregator node
1678
2351
  if (this.bridgeMode === 'bridge') {
1679
2352
  if (!this.aggregatorNode) {
1680
2353
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1687,6 +2360,7 @@ export class Matterbridge extends EventEmitter {
1687
2360
  }
1688
2361
  else if (this.bridgeMode === 'childbridge') {
1689
2362
  if (plugin.type === 'AccessoryPlatform') {
2363
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1690
2364
  }
1691
2365
  else if (plugin.type === 'DynamicPlatform') {
1692
2366
  if (!plugin.aggregatorNode) {
@@ -1699,8 +2373,21 @@ export class Matterbridge extends EventEmitter {
1699
2373
  if (plugin.registeredDevices !== undefined)
1700
2374
  plugin.registeredDevices--;
1701
2375
  }
2376
+ // Remove the device from the DeviceManager
1702
2377
  this.devices.remove(device);
1703
2378
  }
2379
+ /**
2380
+ * Removes all bridged endpoints from the specified plugin.
2381
+ *
2382
+ * @param {string} pluginName - The name of the plugin.
2383
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2384
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2385
+ *
2386
+ * @remarks
2387
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2388
+ * It also applies a delay between each removal if specified.
2389
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2390
+ */
1704
2391
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1705
2392
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1706
2393
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1711,6 +2398,15 @@ export class Matterbridge extends EventEmitter {
1711
2398
  if (delay > 0)
1712
2399
  await wait(2000);
1713
2400
  }
2401
+ /**
2402
+ * Subscribes to the attribute change event for the given device and plugin.
2403
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2404
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2405
+ *
2406
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2407
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2408
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2409
+ */
1714
2410
  async subscribeAttributeChanged(plugin, device) {
1715
2411
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId)
1716
2412
  return;
@@ -1718,16 +2414,24 @@ export class Matterbridge extends EventEmitter {
1718
2414
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1719
2415
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1720
2416
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
2417
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1721
2418
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
1722
2419
  });
1723
2420
  }
1724
2421
  if (device.hasClusterServer(BridgedDeviceBasicInformationServer)) {
1725
2422
  device.eventsOf(BridgedDeviceBasicInformationServer).reachable$Changed.on((reachable) => {
1726
2423
  this.log.info(`Bridged endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
2424
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1727
2425
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BridgedDeviceBasicInformationServer', 'reachable', reachable);
1728
2426
  });
1729
2427
  }
1730
2428
  }
2429
+ /**
2430
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2431
+ *
2432
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2433
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2434
+ */
1731
2435
  sanitizeFabricInformations(fabricInfo) {
1732
2436
  return fabricInfo.map((info) => {
1733
2437
  return {
@@ -1741,6 +2445,12 @@ export class Matterbridge extends EventEmitter {
1741
2445
  };
1742
2446
  });
1743
2447
  }
2448
+ /**
2449
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2450
+ *
2451
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2452
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2453
+ */
1744
2454
  sanitizeSessionInformation(sessions) {
1745
2455
  return sessions
1746
2456
  .filter((session) => session.isPeerActive)
@@ -1767,7 +2477,21 @@ export class Matterbridge extends EventEmitter {
1767
2477
  };
1768
2478
  });
1769
2479
  }
2480
+ /**
2481
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2482
+ *
2483
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2484
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2485
+ */
2486
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1770
2487
  async setAggregatorReachability(aggregatorNode, reachable) {
2488
+ /*
2489
+ for (const child of aggregatorNode.parts) {
2490
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2491
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2492
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2493
+ }
2494
+ */
1771
2495
  }
1772
2496
  getVendorIdName = (vendorId) => {
1773
2497
  if (!vendorId)
@@ -1807,10 +2531,11 @@ export class Matterbridge extends EventEmitter {
1807
2531
  case 0x1488:
1808
2532
  vendorName = '(ShortcutLabsFlic)';
1809
2533
  break;
1810
- case 65521:
2534
+ case 65521: // 0xFFF1
1811
2535
  vendorName = '(MatterTest)';
1812
2536
  break;
1813
2537
  }
1814
2538
  return vendorName;
1815
2539
  };
1816
2540
  }
2541
+ //# sourceMappingURL=matterbridge.js.map