matterbridge 3.3.0-dev-20251003-626ea2f → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) 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 +232 -0
  122. package/dist/frontend.d.ts.map +1 -0
  123. package/dist/frontend.js +454 -61
  124. package/dist/frontend.js.map +1 -0
  125. package/dist/frontendTypes.d.ts +514 -0
  126. package/dist/frontendTypes.d.ts.map +1 -0
  127. package/dist/frontendTypes.js +45 -0
  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 +430 -0
  170. package/dist/matterbridge.d.ts.map +1 -0
  171. package/dist/matterbridge.js +788 -68
  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 +1534 -0
  190. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  191. package/dist/matterbridgeEndpoint.js +1398 -58
  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 +402 -0
  198. package/dist/matterbridgePlatform.d.ts.map +1 -0
  199. package/dist/matterbridgePlatform.js +340 -1
  200. package/dist/matterbridgePlatform.js.map +1 -0
  201. package/dist/matterbridgeTypes.d.ts +201 -0
  202. package/dist/matterbridgeTypes.d.ts.map +1 -0
  203. package/dist/matterbridgeTypes.js +30 -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 +168 -7
  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 +34 -0
  274. package/dist/utils/spawn.d.ts.map +1 -0
  275. package/dist/utils/spawn.js +69 -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/npm-shrinkwrap.json +2 -2
  282. package/package.json +2 -1
@@ -1,23 +1,54 @@
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, or } 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';
39
+ // Matterbridge
12
40
  import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
13
41
  import { withTimeout, waiter, wait } from './utils/wait.js';
14
- import { dev, plg, typ } from './matterbridgeTypes.js';
42
+ import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ, } from './matterbridgeTypes.js';
15
43
  import { PluginManager } from './pluginManager.js';
16
44
  import { DeviceManager } from './deviceManager.js';
17
45
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
18
46
  import { bridge } from './matterbridgeDeviceTypes.js';
19
47
  import { Frontend } from './frontend.js';
20
48
  import { addVirtualDevices } from './helpers.js';
49
+ /**
50
+ * Represents the Matterbridge application.
51
+ */
21
52
  export class Matterbridge extends EventEmitter {
22
53
  systemInformation = {
23
54
  interfaceName: '',
@@ -58,7 +89,7 @@ export class Matterbridge extends EventEmitter {
58
89
  shellySysUpdate: false,
59
90
  shellyMainUpdate: false,
60
91
  profile: getParameter('profile'),
61
- loggerLevel: "info",
92
+ loggerLevel: "info" /* LogLevel.INFO */,
62
93
  fileLogger: false,
63
94
  matterLoggerLevel: MatterLogLevel.INFO,
64
95
  matterFileLogger: false,
@@ -86,16 +117,18 @@ export class Matterbridge extends EventEmitter {
86
117
  profile = getParameter('profile');
87
118
  shutdown = false;
88
119
  failCountLimit = hasParameter('shelly') ? 600 : 120;
89
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
90
- matterbridgeLoggerFile = 'matterbridge.log';
91
- matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
92
- matterLoggerFile = 'matter.log';
120
+ // Matterbridge logger
121
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
122
+ // Matter logger
123
+ matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
124
+ // Managers
93
125
  plugins = new PluginManager(this);
94
126
  devices = new DeviceManager(this);
95
127
  frontend = new Frontend(this);
96
- nodeStorageName = 'storage';
128
+ // Matterbridge storage
97
129
  nodeStorage;
98
130
  nodeContext;
131
+ // Cleanup
99
132
  hasCleanupStarted = false;
100
133
  initialized = false;
101
134
  startMatterInterval;
@@ -108,19 +141,22 @@ export class Matterbridge extends EventEmitter {
108
141
  sigtermHandler;
109
142
  exceptionHandler;
110
143
  rejectionHandler;
144
+ // Matter environment
111
145
  environment = Environment.default;
112
- matterStorageName = 'matterstorage';
146
+ // Matter storage
113
147
  matterStorageService;
114
148
  matterStorageManager;
115
149
  matterbridgeContext;
116
150
  controllerContext;
117
- mdnsInterface;
118
- ipv4address;
119
- ipv6address;
120
- port;
121
- passcode;
122
- discriminator;
123
- certification;
151
+ // Matter parameters
152
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'Wi-Fi'
153
+ ipv4address; // matter server node listeningAddressIpv4
154
+ ipv6address; // matter server node listeningAddressIpv6
155
+ port; // first server node port
156
+ passcode; // first server node passcode
157
+ discriminator; // first server node discriminator
158
+ certification; // device certification
159
+ // Matter nodes
124
160
  serverNode;
125
161
  aggregatorNode;
126
162
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
@@ -130,12 +166,19 @@ export class Matterbridge extends EventEmitter {
130
166
  aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
131
167
  aggregatorSerialNumber = getParameter('serialNumber');
132
168
  aggregatorUniqueId = getParameter('uniqueId');
169
+ /** Advertising nodes map: time advertising started keyed by storeId */
133
170
  advertisingNodes = new Map();
134
171
  static instance;
172
+ // We load asyncronously so is private
135
173
  constructor() {
136
174
  super();
137
175
  this.log.logNameColor = '\x1b[38;5;115m';
138
176
  }
177
+ /**
178
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
179
+ *
180
+ * @param {LogLevel} logLevel The logger logLevel to set.
181
+ */
139
182
  async setLogLevel(logLevel) {
140
183
  if (this.log)
141
184
  this.log.logLevel = logLevel;
@@ -149,19 +192,31 @@ export class Matterbridge extends EventEmitter {
149
192
  for (const plugin of this.plugins) {
150
193
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
151
194
  continue;
152
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
153
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
154
- }
155
- let callbackLogLevel = "notice";
156
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
157
- callbackLogLevel = "info";
158
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
159
- callbackLogLevel = "debug";
195
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
196
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
197
+ }
198
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
199
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
200
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
201
+ callbackLogLevel = "info" /* LogLevel.INFO */;
202
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
203
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
160
204
  AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
161
205
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
162
206
  }
207
+ //* ************************************************************************************************************************************ */
208
+ // loadInstance() and cleanup() methods */
209
+ //* ************************************************************************************************************************************ */
210
+ /**
211
+ * Loads an instance of the Matterbridge class.
212
+ * If an instance already exists, return that instance.
213
+ *
214
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
215
+ * @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
216
+ */
163
217
  static async loadInstance(initialize = false) {
164
218
  if (!Matterbridge.instance) {
219
+ // eslint-disable-next-line no-console
165
220
  if (hasParameter('debug'))
166
221
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
167
222
  Matterbridge.instance = new Matterbridge();
@@ -170,8 +225,17 @@ export class Matterbridge extends EventEmitter {
170
225
  }
171
226
  return Matterbridge.instance;
172
227
  }
228
+ /**
229
+ * Call cleanup() and dispose MdnsService.
230
+ *
231
+ * @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
232
+ * @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
233
+ *
234
+ * @deprecated This method is deprecated and is ONLY used for jest tests.
235
+ */
173
236
  async destroyInstance(timeout = 1000, pause = 250) {
174
237
  this.log.info(`Destroy instance...`);
238
+ // Save server nodes to close
175
239
  const servers = [];
176
240
  if (this.bridgeMode === 'bridge') {
177
241
  if (this.serverNode)
@@ -189,93 +253,132 @@ export class Matterbridge extends EventEmitter {
189
253
  servers.push(device.serverNode);
190
254
  }
191
255
  }
256
+ // Let any already‐queued microtasks run first
192
257
  await Promise.resolve();
258
+ // Wait for the cleanup to finish
193
259
  await wait(pause, 'destroyInstance start', true);
260
+ // Cleanup
194
261
  await this.cleanup('destroying instance...', false, timeout);
262
+ // Close servers mdns service
195
263
  this.log.info(`Dispose ${servers.length} MdnsService...`);
196
264
  for (const server of servers) {
197
265
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
198
266
  this.log.info(`Closed ${server.id} MdnsService`);
199
267
  }
268
+ // Let any already‐queued microtasks run first
200
269
  await Promise.resolve();
270
+ // Wait for the cleanup to finish
201
271
  await wait(pause, 'destroyInstance stop', true);
202
272
  }
273
+ /**
274
+ * Initializes the Matterbridge application.
275
+ *
276
+ * @remarks
277
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
278
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
279
+ * node version, registers signal handlers, initializes storage, and parses the command line.
280
+ *
281
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
282
+ */
203
283
  async initialize() {
284
+ // for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
285
+ // Emit the initialize_started event
204
286
  this.emit('initialize_started');
287
+ // Set the restart mode
205
288
  if (hasParameter('service'))
206
289
  this.restartMode = 'service';
207
290
  if (hasParameter('docker'))
208
291
  this.restartMode = 'docker';
292
+ // Set the matterbridge home directory
209
293
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
210
294
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
211
295
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
296
+ // Set the matterbridge directory
212
297
  this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
213
298
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
214
299
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
215
300
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
216
301
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
302
+ // Set the matterbridge plugin directory
217
303
  this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
218
304
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
219
305
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
306
+ // Set the matterbridge cert directory
220
307
  this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
221
308
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
222
309
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
310
+ // Set the matterbridge root directory
223
311
  const { fileURLToPath } = await import('node:url');
224
312
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
225
313
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
226
314
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
315
+ // Setup the matter environment
227
316
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
228
317
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
229
- this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
318
+ this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
230
319
  this.environment.vars.set('runtime.signals', false);
231
320
  this.environment.vars.set('runtime.exitcode', false);
321
+ // Register process handlers
232
322
  this.registerProcessHandlers();
323
+ // Initialize nodeStorage and nodeContext
233
324
  try {
234
- this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
235
- this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
325
+ this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
326
+ this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
236
327
  this.log.debug('Creating node storage context for matterbridge');
237
328
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
329
+ // TODO: Remove this code when node-persist-manager is updated
330
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
238
331
  const keys = (await this.nodeStorage?.storage.keys());
239
332
  for (const key of keys) {
240
333
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
334
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
335
  await this.nodeStorage?.storage.get(key);
242
336
  }
243
337
  const storages = await this.nodeStorage.getStorageNames();
244
338
  for (const storage of storages) {
245
339
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
246
340
  const nodeContext = await this.nodeStorage?.createStorage(storage);
341
+ // TODO: Remove this code when node-persist-manager is updated
342
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
247
343
  const keys = (await nodeContext?.storage.keys());
248
344
  keys.forEach(async (key) => {
249
345
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
250
346
  await nodeContext?.get(key);
251
347
  });
252
348
  }
349
+ // Creating a backup of the node storage since it is not corrupted
253
350
  this.log.debug('Creating node storage backup...');
254
- await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
351
+ await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
255
352
  this.log.debug('Created node storage backup');
256
353
  }
257
354
  catch (error) {
355
+ // Restoring the backup of the node storage since it is corrupted
258
356
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
259
357
  if (hasParameter('norestore')) {
260
358
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
261
359
  }
262
360
  else {
263
361
  this.log.notice(`The matterbridge storage is corrupted. Restoring it with backup...`);
264
- await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'), path.join(this.matterbridgeDirectory, this.nodeStorageName));
362
+ await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR));
265
363
  this.log.notice(`The matterbridge storage has been restored with backup`);
266
364
  }
267
365
  }
268
366
  if (!this.nodeStorage || !this.nodeContext) {
269
367
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
270
368
  }
369
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
271
370
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
371
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
272
372
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
373
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
273
374
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
375
+ // Certificate management
274
376
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
275
377
  try {
276
378
  await fs.access(pairingFilePath, fs.constants.R_OK);
277
379
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
278
380
  const pairingFileJson = JSON.parse(pairingFileContent);
381
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
279
382
  if (isValidNumber(pairingFileJson.vendorId)) {
280
383
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
281
384
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -304,11 +407,13 @@ export class Matterbridge extends EventEmitter {
304
407
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
305
408
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
306
409
  }
410
+ // Override the passcode and discriminator if they are present in the pairing file
307
411
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
308
412
  this.passcode = pairingFileJson.passcode;
309
413
  this.discriminator = pairingFileJson.discriminator;
310
414
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
311
415
  }
416
+ // Set the certification for matter.js if it is present in the pairing file
312
417
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
313
418
  const { hexToBuffer } = await import('./utils/hex.js');
314
419
  this.certification = {
@@ -323,49 +428,53 @@ export class Matterbridge extends EventEmitter {
323
428
  catch (error) {
324
429
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
325
430
  }
431
+ // Store the passcode, discriminator and port in the node context
326
432
  await this.nodeContext.set('matterport', this.port);
327
433
  await this.nodeContext.set('matterpasscode', this.passcode);
328
434
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
329
435
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
436
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
330
437
  if (hasParameter('logger')) {
331
438
  const level = getParameter('logger');
332
439
  if (level === 'debug') {
333
- this.log.logLevel = "debug";
440
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
334
441
  }
335
442
  else if (level === 'info') {
336
- this.log.logLevel = "info";
443
+ this.log.logLevel = "info" /* LogLevel.INFO */;
337
444
  }
338
445
  else if (level === 'notice') {
339
- this.log.logLevel = "notice";
446
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
340
447
  }
341
448
  else if (level === 'warn') {
342
- this.log.logLevel = "warn";
449
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
343
450
  }
344
451
  else if (level === 'error') {
345
- this.log.logLevel = "error";
452
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
346
453
  }
347
454
  else if (level === 'fatal') {
348
- this.log.logLevel = "fatal";
455
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
349
456
  }
350
457
  else {
351
458
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
352
- this.log.logLevel = "info";
459
+ this.log.logLevel = "info" /* LogLevel.INFO */;
353
460
  }
354
461
  }
355
462
  else {
356
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
463
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
357
464
  }
358
465
  this.frontend.logLevel = this.log.logLevel;
359
466
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
360
467
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
468
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
361
469
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
362
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
470
+ AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
363
471
  this.matterbridgeInformation.fileLogger = true;
364
472
  }
365
473
  this.log.notice('Matterbridge is starting...');
366
474
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
367
475
  if (this.profile !== undefined)
368
476
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
477
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
369
478
  if (hasParameter('matterlogger')) {
370
479
  const level = getParameter('matterlogger');
371
480
  if (level === 'debug') {
@@ -395,12 +504,14 @@ export class Matterbridge extends EventEmitter {
395
504
  Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
396
505
  }
397
506
  Logger.format = MatterLogFormat.ANSI;
507
+ // Create the logger for matter.js with file logging (context: matterFileLog)
398
508
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
399
509
  this.matterbridgeInformation.matterFileLogger = true;
400
510
  }
401
511
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterbridgeInformation.matterFileLogger);
402
512
  this.matterbridgeInformation.matterLoggerLevel = Logger.level;
403
513
  this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
514
+ // Log network interfaces
404
515
  const networkInterfaces = os.networkInterfaces();
405
516
  const availableAddresses = Object.entries(networkInterfaces);
406
517
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -413,6 +524,7 @@ export class Matterbridge extends EventEmitter {
413
524
  });
414
525
  }
415
526
  }
527
+ // Set the interface to use for matter server node mdnsInterface
416
528
  if (hasParameter('mdnsinterface')) {
417
529
  this.mdnsInterface = getParameter('mdnsinterface');
418
530
  }
@@ -421,6 +533,7 @@ export class Matterbridge extends EventEmitter {
421
533
  if (this.mdnsInterface === '')
422
534
  this.mdnsInterface = undefined;
423
535
  }
536
+ // Validate mdnsInterface
424
537
  if (this.mdnsInterface) {
425
538
  if (!availableInterfaces.includes(this.mdnsInterface)) {
426
539
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -433,6 +546,7 @@ export class Matterbridge extends EventEmitter {
433
546
  }
434
547
  if (this.mdnsInterface)
435
548
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
549
+ // Set the listeningAddressIpv4 for the matter commissioning server
436
550
  if (hasParameter('ipv4address')) {
437
551
  this.ipv4address = getParameter('ipv4address');
438
552
  }
@@ -441,6 +555,7 @@ export class Matterbridge extends EventEmitter {
441
555
  if (this.ipv4address === '')
442
556
  this.ipv4address = undefined;
443
557
  }
558
+ // Validate ipv4address
444
559
  if (this.ipv4address) {
445
560
  let isValid = false;
446
561
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -456,6 +571,7 @@ export class Matterbridge extends EventEmitter {
456
571
  await this.nodeContext.remove('matteripv4address');
457
572
  }
458
573
  }
574
+ // Set the listeningAddressIpv6 for the matter commissioning server
459
575
  if (hasParameter('ipv6address')) {
460
576
  this.ipv6address = getParameter('ipv6address');
461
577
  }
@@ -464,6 +580,7 @@ export class Matterbridge extends EventEmitter {
464
580
  if (this.ipv6address === '')
465
581
  this.ipv6address = undefined;
466
582
  }
583
+ // Validate ipv6address
467
584
  if (this.ipv6address) {
468
585
  let isValid = false;
469
586
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -472,6 +589,7 @@ export class Matterbridge extends EventEmitter {
472
589
  isValid = true;
473
590
  break;
474
591
  }
592
+ /* istanbul ignore next */
475
593
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
476
594
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
477
595
  isValid = true;
@@ -484,6 +602,7 @@ export class Matterbridge extends EventEmitter {
484
602
  await this.nodeContext.remove('matteripv6address');
485
603
  }
486
604
  }
605
+ // Initialize the virtual mode
487
606
  if (hasParameter('novirtual')) {
488
607
  this.matterbridgeInformation.virtualMode = 'disabled';
489
608
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -492,12 +611,17 @@ export class Matterbridge extends EventEmitter {
492
611
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
493
612
  }
494
613
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
614
+ // Initialize PluginManager
495
615
  this.plugins.logLevel = this.log.logLevel;
496
616
  await this.plugins.loadFromStorage();
617
+ // Initialize DeviceManager
497
618
  this.devices.logLevel = this.log.logLevel;
619
+ // Get the plugins from node storage and create the plugins node storage contexts
498
620
  for (const plugin of this.plugins) {
499
621
  const packageJson = await this.plugins.parse(plugin);
500
622
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
623
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
624
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
501
625
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
502
626
  try {
503
627
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -520,6 +644,7 @@ export class Matterbridge extends EventEmitter {
520
644
  await plugin.nodeContext.set('description', plugin.description);
521
645
  await plugin.nodeContext.set('author', plugin.author);
522
646
  }
647
+ // Log system info and create .matterbridge directory
523
648
  await this.logNodeAndSystemInfo();
524
649
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
525
650
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -527,6 +652,7 @@ export class Matterbridge extends EventEmitter {
527
652
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
528
653
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
529
654
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
655
+ // Check node version and throw error
530
656
  const minNodeVersion = 18;
531
657
  const nodeVersion = process.versions.node;
532
658
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -534,10 +660,18 @@ export class Matterbridge extends EventEmitter {
534
660
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
535
661
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
536
662
  }
663
+ // Parse command line
537
664
  await this.parseCommandLine();
665
+ // Emit the initialize_completed event
538
666
  this.emit('initialize_completed');
539
667
  this.initialized = true;
540
668
  }
669
+ /**
670
+ * Parses the command line arguments and performs the corresponding actions.
671
+ *
672
+ * @private
673
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
674
+ */
541
675
  async parseCommandLine() {
542
676
  if (hasParameter('help')) {
543
677
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -599,6 +733,19 @@ export class Matterbridge extends EventEmitter {
599
733
  }
600
734
  index++;
601
735
  }
736
+ /*
737
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
738
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
739
+ serializedRegisteredDevices?.forEach((device, index) => {
740
+ if (index !== serializedRegisteredDevices.length - 1) {
741
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
742
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
743
+ } else {
744
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
745
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
746
+ }
747
+ });
748
+ */
602
749
  this.shutdown = true;
603
750
  return;
604
751
  }
@@ -648,8 +795,10 @@ export class Matterbridge extends EventEmitter {
648
795
  this.shutdown = true;
649
796
  return;
650
797
  }
798
+ // Initialize frontend
651
799
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
652
800
  await this.frontend.start(getIntParameter('frontend'));
801
+ // Start the matter storage and create the matterbridge context
653
802
  try {
654
803
  await this.startMatterStorage();
655
804
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -665,18 +814,21 @@ export class Matterbridge extends EventEmitter {
665
814
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
666
815
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
667
816
  }
817
+ // Clear the matterbridge context if the reset parameter is set
668
818
  if (hasParameter('reset') && getParameter('reset') === undefined) {
669
819
  this.initialized = true;
670
820
  await this.shutdownProcessAndReset();
671
821
  this.shutdown = true;
672
822
  return;
673
823
  }
824
+ // Clear matterbridge plugin context if the reset parameter is set
674
825
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
675
826
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
676
827
  const plugin = this.plugins.get(getParameter('reset'));
677
828
  if (plugin) {
678
829
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
679
830
  if (!matterStorageManager) {
831
+ /* istanbul ignore next */
680
832
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
681
833
  }
682
834
  else {
@@ -695,35 +847,42 @@ export class Matterbridge extends EventEmitter {
695
847
  this.shutdown = true;
696
848
  return;
697
849
  }
850
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
698
851
  clearTimeout(this.checkUpdateTimeout);
699
852
  this.checkUpdateTimeout = setTimeout(async () => {
700
853
  const { checkUpdates } = await import('./update.js');
701
854
  checkUpdates(this);
702
855
  }, 30 * 1000).unref();
856
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
703
857
  clearInterval(this.checkUpdateInterval);
704
858
  this.checkUpdateInterval = setInterval(async () => {
705
859
  const { checkUpdates } = await import('./update.js');
706
860
  checkUpdates(this);
707
861
  }, 12 * 60 * 60 * 1000).unref();
862
+ // Start the matterbridge in mode test
708
863
  if (hasParameter('test')) {
709
864
  this.bridgeMode = 'bridge';
710
865
  return;
711
866
  }
867
+ // Start the matterbridge in mode controller
712
868
  if (hasParameter('controller')) {
713
869
  this.bridgeMode = 'controller';
714
870
  await this.startController();
715
871
  return;
716
872
  }
873
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
717
874
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
718
875
  this.log.info('Setting default matterbridge start mode to bridge');
719
876
  await this.nodeContext?.set('bridgeMode', 'bridge');
720
877
  }
878
+ // Start matterbridge in bridge mode
721
879
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
722
880
  this.bridgeMode = 'bridge';
723
881
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
724
882
  await this.startBridge();
725
883
  return;
726
884
  }
885
+ // Start matterbridge in childbridge mode
727
886
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
728
887
  this.bridgeMode = 'childbridge';
729
888
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -731,10 +890,22 @@ export class Matterbridge extends EventEmitter {
731
890
  return;
732
891
  }
733
892
  }
893
+ /**
894
+ * Asynchronously loads and starts the registered plugins.
895
+ *
896
+ * This method is responsible for initializing and starting all enabled plugins.
897
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
898
+ *
899
+ * @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving.
900
+ * @param {boolean} [start] - If true, the method will start the plugins after loading them.
901
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
902
+ */
734
903
  async startPlugins(wait = false, start = true) {
904
+ // Check, load and start the plugins
735
905
  for (const plugin of this.plugins) {
736
906
  plugin.configJson = await this.plugins.loadConfig(plugin);
737
907
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
908
+ // Check if the plugin is available
738
909
  if (!(await this.plugins.resolve(plugin.path))) {
739
910
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
740
911
  plugin.enabled = false;
@@ -754,10 +925,16 @@ export class Matterbridge extends EventEmitter {
754
925
  if (wait)
755
926
  await this.plugins.load(plugin, start, 'Matterbridge is starting');
756
927
  else
757
- this.plugins.load(plugin, start, 'Matterbridge is starting');
928
+ this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
758
929
  }
759
930
  this.frontend.wssSendRefreshRequired('plugins');
760
931
  }
932
+ /**
933
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
934
+ * - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
935
+ * - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
936
+ * - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
937
+ */
761
938
  registerProcessHandlers() {
762
939
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
763
940
  process.removeAllListeners('uncaughtException');
@@ -784,6 +961,9 @@ export class Matterbridge extends EventEmitter {
784
961
  };
785
962
  process.on('SIGTERM', this.sigtermHandler);
786
963
  }
964
+ /**
965
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
966
+ */
787
967
  deregisterProcessHandlers() {
788
968
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
789
969
  if (this.exceptionHandler)
@@ -800,12 +980,17 @@ export class Matterbridge extends EventEmitter {
800
980
  process.off('SIGTERM', this.sigtermHandler);
801
981
  this.sigtermHandler = undefined;
802
982
  }
983
+ /**
984
+ * Logs the node and system information.
985
+ */
803
986
  async logNodeAndSystemInfo() {
987
+ // IP address information
804
988
  const networkInterfaces = os.networkInterfaces();
805
989
  this.systemInformation.interfaceName = '';
806
990
  this.systemInformation.ipv4Address = '';
807
991
  this.systemInformation.ipv6Address = '';
808
992
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
993
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
809
994
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
810
995
  continue;
811
996
  if (!interfaceDetails) {
@@ -831,19 +1016,22 @@ export class Matterbridge extends EventEmitter {
831
1016
  break;
832
1017
  }
833
1018
  }
1019
+ // Node information
834
1020
  this.systemInformation.nodeVersion = process.versions.node;
835
1021
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
836
1022
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
837
1023
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1024
+ // Host system information
838
1025
  this.systemInformation.hostname = os.hostname();
839
1026
  this.systemInformation.user = os.userInfo().username;
840
- this.systemInformation.osType = os.type();
841
- this.systemInformation.osRelease = os.release();
842
- this.systemInformation.osPlatform = os.platform();
843
- this.systemInformation.osArch = os.arch();
844
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
845
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
846
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
1027
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1028
+ this.systemInformation.osRelease = os.release(); // Kernel version
1029
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1030
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
1031
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1032
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1033
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
1034
+ // Log the system information
847
1035
  this.log.debug('Host System Information:');
848
1036
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
849
1037
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -859,14 +1047,17 @@ export class Matterbridge extends EventEmitter {
859
1047
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
860
1048
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
861
1049
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1050
+ // Log directories
862
1051
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
863
1052
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
864
1053
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
865
1054
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
866
1055
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1056
+ // Global node_modules directory
867
1057
  if (this.nodeContext)
868
1058
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
869
1059
  if (this.globalModulesDirectory === '') {
1060
+ // First run of Matterbridge so the node storage is empty
870
1061
  this.log.debug(`Getting global node_modules directory...`);
871
1062
  try {
872
1063
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -879,6 +1070,7 @@ export class Matterbridge extends EventEmitter {
879
1070
  }
880
1071
  }
881
1072
  else {
1073
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
882
1074
  this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
883
1075
  try {
884
1076
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -890,58 +1082,86 @@ export class Matterbridge extends EventEmitter {
890
1082
  this.log.error(`Error checking global node_modules directory: ${error}`);
891
1083
  }
892
1084
  }
1085
+ // Matterbridge version
893
1086
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
894
1087
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
895
1088
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
896
1089
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1090
+ // Matterbridge latest version (will be set in the checkUpdate function)
897
1091
  if (this.nodeContext)
898
1092
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
899
1093
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1094
+ // Matterbridge dev version (will be set in the checkUpdate function)
900
1095
  if (this.nodeContext)
901
1096
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
902
1097
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1098
+ // Current working directory
903
1099
  const currentDir = process.cwd();
904
1100
  this.log.debug(`Current Working Directory: ${currentDir}`);
1101
+ // Command line arguments (excluding 'node' and the script name)
905
1102
  const cmdArgs = process.argv.slice(2).join(' ');
906
1103
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
907
1104
  }
1105
+ /**
1106
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1107
+ * It also logs to file (matter.log) if fileLogger is true.
1108
+ *
1109
+ * @param {boolean} fileLogger - Whether to log to file or not.
1110
+ * @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
1111
+ */
908
1112
  createDestinationMatterLogger(fileLogger) {
909
- this.matterLog.logNameColor = '\x1b[34m';
1113
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
910
1114
  if (fileLogger) {
911
- this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, this.matterLoggerFile);
1115
+ this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
912
1116
  }
913
1117
  return (text, message) => {
1118
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
914
1119
  const logger = text.slice(44, 44 + 20).trim();
915
1120
  const msg = text.slice(65);
916
1121
  this.matterLog.logName = logger;
917
1122
  switch (message.level) {
918
1123
  case MatterLogLevel.DEBUG:
919
- this.matterLog.log("debug", msg);
1124
+ this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
920
1125
  break;
921
1126
  case MatterLogLevel.INFO:
922
- this.matterLog.log("info", msg);
1127
+ this.matterLog.log("info" /* LogLevel.INFO */, msg);
923
1128
  break;
924
1129
  case MatterLogLevel.NOTICE:
925
- this.matterLog.log("notice", msg);
1130
+ this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
926
1131
  break;
927
1132
  case MatterLogLevel.WARN:
928
- this.matterLog.log("warn", msg);
1133
+ this.matterLog.log("warn" /* LogLevel.WARN */, msg);
929
1134
  break;
930
1135
  case MatterLogLevel.ERROR:
931
- this.matterLog.log("error", msg);
1136
+ this.matterLog.log("error" /* LogLevel.ERROR */, msg);
932
1137
  break;
933
1138
  case MatterLogLevel.FATAL:
934
- this.matterLog.log("fatal", msg);
1139
+ this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
935
1140
  break;
936
1141
  }
937
1142
  };
938
1143
  }
1144
+ /**
1145
+ * Restarts the process by exiting the current instance and loading a new instance (/api/restart).
1146
+ *
1147
+ * @returns {Promise<void>} A promise that resolves when the restart is completed.
1148
+ */
939
1149
  async restartProcess() {
940
1150
  await this.cleanup('restarting...', true);
941
1151
  }
1152
+ /**
1153
+ * Shut down the process (/api/shutdown).
1154
+ *
1155
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1156
+ */
942
1157
  async shutdownProcess() {
943
1158
  await this.cleanup('shutting down...', false);
944
1159
  }
1160
+ /**
1161
+ * Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
1162
+ *
1163
+ * @returns {Promise<void>} A promise that resolves when the update is completed.
1164
+ */
945
1165
  async updateProcess() {
946
1166
  this.log.info('Updating matterbridge...');
947
1167
  try {
@@ -955,6 +1175,13 @@ export class Matterbridge extends EventEmitter {
955
1175
  this.frontend.wssSendRestartRequired();
956
1176
  await this.cleanup('updating...', false);
957
1177
  }
1178
+ /**
1179
+ * Unregister all devices and shut down the process (/api/unregister).
1180
+ *
1181
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1182
+ *
1183
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1184
+ */
958
1185
  async unregisterAndShutdownProcess(timeout = 1000) {
959
1186
  this.log.info('Unregistering all devices and shutting down...');
960
1187
  for (const plugin of this.plugins.array()) {
@@ -966,46 +1193,71 @@ export class Matterbridge extends EventEmitter {
966
1193
  await this.removeAllBridgedEndpoints(plugin.name, 100);
967
1194
  }
968
1195
  this.log.debug('Waiting for the MessageExchange to finish...');
969
- await wait(timeout);
1196
+ await wait(timeout); // Wait for MessageExchange to finish
970
1197
  this.log.debug('Cleaning up and shutting down...');
971
1198
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
972
1199
  }
1200
+ /**
1201
+ * Reset commissioning and shut down the process (/api/reset).
1202
+ *
1203
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1204
+ */
973
1205
  async shutdownProcessAndReset() {
974
1206
  await this.cleanup('shutting down with reset...', false);
975
1207
  }
1208
+ /**
1209
+ * Factory reset and shut down the process (/api/factory-reset).
1210
+ *
1211
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1212
+ */
976
1213
  async shutdownProcessAndFactoryReset() {
977
1214
  await this.cleanup('shutting down with factory reset...', false);
978
1215
  }
1216
+ /**
1217
+ * Cleans up the Matterbridge instance.
1218
+ *
1219
+ * @param {string} message - The cleanup message.
1220
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1221
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1222
+ *
1223
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1224
+ */
979
1225
  async cleanup(message, restart = false, timeout = 1000) {
980
1226
  if (this.initialized && !this.hasCleanupStarted) {
981
1227
  this.emit('cleanup_started');
982
1228
  this.hasCleanupStarted = true;
983
1229
  this.log.info(message);
1230
+ // Clear the start matter interval
984
1231
  if (this.startMatterInterval) {
985
1232
  clearInterval(this.startMatterInterval);
986
1233
  this.startMatterInterval = undefined;
987
1234
  this.log.debug('Start matter interval cleared');
988
1235
  }
1236
+ // Clear the check update timeout
989
1237
  if (this.checkUpdateTimeout) {
990
1238
  clearTimeout(this.checkUpdateTimeout);
991
1239
  this.checkUpdateTimeout = undefined;
992
1240
  this.log.debug('Check update timeout cleared');
993
1241
  }
1242
+ // Clear the check update interval
994
1243
  if (this.checkUpdateInterval) {
995
1244
  clearInterval(this.checkUpdateInterval);
996
1245
  this.checkUpdateInterval = undefined;
997
1246
  this.log.debug('Check update interval cleared');
998
1247
  }
1248
+ // Clear the configure timeout
999
1249
  if (this.configureTimeout) {
1000
1250
  clearTimeout(this.configureTimeout);
1001
1251
  this.configureTimeout = undefined;
1002
1252
  this.log.debug('Matterbridge configure timeout cleared');
1003
1253
  }
1254
+ // Clear the reachability timeout
1004
1255
  if (this.reachabilityTimeout) {
1005
1256
  clearTimeout(this.reachabilityTimeout);
1006
1257
  this.reachabilityTimeout = undefined;
1007
1258
  this.log.debug('Matterbridge reachability timeout cleared');
1008
1259
  }
1260
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
1009
1261
  for (const plugin of this.plugins) {
1010
1262
  if (!plugin.enabled || plugin.error)
1011
1263
  continue;
@@ -1016,6 +1268,7 @@ export class Matterbridge extends EventEmitter {
1016
1268
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1017
1269
  }
1018
1270
  }
1271
+ // Stop matter server nodes
1019
1272
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1020
1273
  if (timeout > 0) {
1021
1274
  this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
@@ -1042,6 +1295,7 @@ export class Matterbridge extends EventEmitter {
1042
1295
  }
1043
1296
  }
1044
1297
  this.log.notice('Stopped matter server nodes');
1298
+ // Matter commisioning reset
1045
1299
  if (message === 'shutting down with reset...') {
1046
1300
  this.log.info('Resetting Matterbridge commissioning information...');
1047
1301
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1051,6 +1305,7 @@ export class Matterbridge extends EventEmitter {
1051
1305
  await this.matterbridgeContext?.clearAll();
1052
1306
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1053
1307
  }
1308
+ // Unregister all devices
1054
1309
  if (message === 'unregistered all devices and shutting down...') {
1055
1310
  if (this.bridgeMode === 'bridge') {
1056
1311
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1068,12 +1323,29 @@ export class Matterbridge extends EventEmitter {
1068
1323
  }
1069
1324
  this.log.info('Matter storage reset done!');
1070
1325
  }
1326
+ // Stop matter storage
1071
1327
  await this.stopMatterStorage();
1328
+ // Stop the frontend
1072
1329
  await this.frontend.stop();
1330
+ // Close the matterbridge node storage and context
1073
1331
  if (this.nodeStorage && this.nodeContext) {
1332
+ /*
1333
+ TODO: Implement serialization of registered devices in edge mode
1334
+ this.log.info('Saving registered devices...');
1335
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1336
+ this.devices.forEach(async (device) => {
1337
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1338
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1339
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1340
+ });
1341
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1342
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1343
+ */
1344
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1074
1345
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1075
1346
  await this.nodeContext.close();
1076
1347
  this.nodeContext = undefined;
1348
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1077
1349
  for (const plugin of this.plugins) {
1078
1350
  if (plugin.nodeContext) {
1079
1351
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1090,41 +1362,47 @@ export class Matterbridge extends EventEmitter {
1090
1362
  }
1091
1363
  this.plugins.clear();
1092
1364
  this.devices.clear();
1365
+ // Factory reset
1093
1366
  if (message === 'shutting down with factory reset...') {
1094
1367
  try {
1095
- const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
1368
+ // Delete matter storage directory with its subdirectories and backup
1369
+ const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
1096
1370
  this.log.info(`Removing matter storage directory: ${dir}`);
1097
1371
  await fs.rm(dir, { recursive: true });
1098
- const backup = path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup');
1372
+ const backup = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup');
1099
1373
  this.log.info(`Removing matter storage backup directory: ${backup}`);
1100
1374
  await fs.rm(backup, { recursive: true });
1101
1375
  }
1102
1376
  catch (error) {
1377
+ // istanbul ignore next if
1103
1378
  if (error instanceof Error && error.code !== 'ENOENT') {
1104
1379
  this.log.error(`Error removing matter storage directory: ${error}`);
1105
1380
  }
1106
1381
  }
1107
1382
  try {
1108
- const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
1383
+ // Delete matterbridge storage directory with its subdirectories and backup
1384
+ const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
1109
1385
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1110
1386
  await fs.rm(dir, { recursive: true });
1111
- const backup = path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup');
1387
+ const backup = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup');
1112
1388
  this.log.info(`Removing matterbridge storage backup directory: ${backup}`);
1113
1389
  await fs.rm(backup, { recursive: true });
1114
1390
  }
1115
1391
  catch (error) {
1392
+ // istanbul ignore next if
1116
1393
  if (error instanceof Error && error.code !== 'ENOENT') {
1117
1394
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1118
1395
  }
1119
1396
  }
1120
1397
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1121
1398
  }
1399
+ // Deregisters the process handlers
1122
1400
  this.deregisterProcessHandlers();
1123
1401
  if (restart) {
1124
1402
  if (message === 'updating...') {
1125
1403
  this.log.info('Cleanup completed. Updating...');
1126
1404
  Matterbridge.instance = undefined;
1127
- this.emit('update');
1405
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1128
1406
  }
1129
1407
  else if (message === 'restarting...') {
1130
1408
  this.log.info('Cleanup completed. Restarting...');
@@ -1148,7 +1426,14 @@ export class Matterbridge extends EventEmitter {
1148
1426
  this.log.debug('Cleanup already started...');
1149
1427
  }
1150
1428
  }
1429
+ /**
1430
+ * Starts the Matterbridge in bridge mode.
1431
+ *
1432
+ * @private
1433
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1434
+ */
1151
1435
  async startBridge() {
1436
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1152
1437
  if (!this.matterStorageManager)
1153
1438
  throw new Error('No storage manager initialized');
1154
1439
  if (!this.matterbridgeContext)
@@ -1187,13 +1472,16 @@ export class Matterbridge extends EventEmitter {
1187
1472
  clearInterval(this.startMatterInterval);
1188
1473
  this.startMatterInterval = undefined;
1189
1474
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1190
- this.startServerNode(this.serverNode);
1475
+ // Start the Matter server node
1476
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1477
+ // Start the Matter server node of single devices in mode 'server'
1191
1478
  for (const device of this.devices.array()) {
1192
1479
  if (device.mode === 'server' && device.serverNode) {
1193
1480
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1194
- this.startServerNode(device.serverNode);
1481
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1195
1482
  }
1196
1483
  }
1484
+ // Configure the plugins
1197
1485
  this.configureTimeout = setTimeout(async () => {
1198
1486
  for (const plugin of this.plugins.array()) {
1199
1487
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1211,28 +1499,40 @@ export class Matterbridge extends EventEmitter {
1211
1499
  }
1212
1500
  this.frontend.wssSendRefreshRequired('plugins');
1213
1501
  }, 30 * 1000).unref();
1502
+ // Setting reachability to true
1214
1503
  this.reachabilityTimeout = setTimeout(() => {
1215
1504
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1216
1505
  if (this.aggregatorNode)
1217
1506
  this.setAggregatorReachability(this.aggregatorNode, true);
1218
1507
  }, 60 * 1000).unref();
1508
+ // Logger.get('LogServerNode').info(this.serverNode);
1219
1509
  this.emit('bridge_started');
1220
1510
  this.log.notice('Matterbridge bridge started successfully');
1221
1511
  this.frontend.wssSendRefreshRequired('settings');
1222
1512
  this.frontend.wssSendRefreshRequired('plugins');
1223
1513
  }, this.startMatterIntervalMs);
1224
1514
  }
1515
+ /**
1516
+ * Starts the Matterbridge in childbridge mode.
1517
+ *
1518
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1519
+ *
1520
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1521
+ */
1225
1522
  async startChildbridge(delay = 1000) {
1226
1523
  if (!this.matterStorageManager)
1227
1524
  throw new Error('No storage manager initialized');
1525
+ // Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
1228
1526
  this.log.debug('Loading all plugins in childbridge mode...');
1229
1527
  await this.startPlugins(true, false);
1528
+ // Create server nodes for DynamicPlatform plugins and start all plugins in the background
1230
1529
  this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
1231
1530
  for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
1232
1531
  if (plugin.type === 'DynamicPlatform')
1233
1532
  await this.createDynamicPlugin(plugin);
1234
- this.plugins.start(plugin, 'Matterbridge is starting');
1533
+ this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
1235
1534
  }
1535
+ // Start the Matterbridge in childbridge mode when all plugins are loaded and started
1236
1536
  this.log.debug('Starting start matter interval in childbridge mode...');
1237
1537
  let failCount = 0;
1238
1538
  this.startMatterInterval = setInterval(async () => {
@@ -1266,8 +1566,9 @@ export class Matterbridge extends EventEmitter {
1266
1566
  clearInterval(this.startMatterInterval);
1267
1567
  this.startMatterInterval = undefined;
1268
1568
  if (delay > 0)
1269
- await wait(delay);
1569
+ await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1270
1570
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1571
+ // Configure the plugins
1271
1572
  this.configureTimeout = setTimeout(async () => {
1272
1573
  for (const plugin of this.plugins.array()) {
1273
1574
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1292,6 +1593,7 @@ export class Matterbridge extends EventEmitter {
1292
1593
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1293
1594
  continue;
1294
1595
  }
1596
+ // istanbul ignore next if cause is just a safety check
1295
1597
  if (!plugin.serverNode) {
1296
1598
  this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1297
1599
  continue;
@@ -1304,28 +1606,252 @@ export class Matterbridge extends EventEmitter {
1304
1606
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1305
1607
  continue;
1306
1608
  }
1307
- this.startServerNode(plugin.serverNode);
1609
+ // Start the Matter server node
1610
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1611
+ // Setting reachability to true
1308
1612
  plugin.reachabilityTimeout = setTimeout(() => {
1309
1613
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1310
1614
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1311
1615
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1312
1616
  }, 60 * 1000).unref();
1313
1617
  }
1618
+ // Start the Matter server node of single devices in mode 'server'
1314
1619
  for (const device of this.devices.array()) {
1315
1620
  if (device.mode === 'server' && device.serverNode) {
1316
1621
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1317
- this.startServerNode(device.serverNode);
1622
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1318
1623
  }
1319
1624
  }
1625
+ // Logger.get('LogServerNode').info(this.serverNode);
1320
1626
  this.emit('childbridge_started');
1321
1627
  this.log.notice('Matterbridge childbridge started successfully');
1322
1628
  this.frontend.wssSendRefreshRequired('settings');
1323
1629
  this.frontend.wssSendRefreshRequired('plugins');
1324
1630
  }, this.startMatterIntervalMs);
1325
1631
  }
1632
+ /**
1633
+ * Starts the Matterbridge controller.
1634
+ *
1635
+ * @private
1636
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1637
+ */
1326
1638
  async startController() {
1639
+ /*
1640
+ if (!this.matterStorageManager) {
1641
+ this.log.error('No storage manager initialized');
1642
+ await this.cleanup('No storage manager initialized');
1643
+ return;
1644
+ }
1645
+ this.log.info('Creating context: mattercontrollerContext');
1646
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1647
+ if (!this.controllerContext) {
1648
+ this.log.error('No storage context mattercontrollerContext initialized');
1649
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1650
+ return;
1651
+ }
1652
+
1653
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1654
+ this.matterServer = await this.createMatterServer(this.storageManager);
1655
+ this.log.info('Creating matter commissioning controller');
1656
+ this.commissioningController = new CommissioningController({
1657
+ autoConnect: false,
1658
+ });
1659
+ this.log.info('Adding matter commissioning controller to matter server');
1660
+ await this.matterServer.addCommissioningController(this.commissioningController);
1661
+
1662
+ this.log.info('Starting matter server');
1663
+ await this.matterServer.start();
1664
+ this.log.info('Matter server started');
1665
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1666
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1667
+ regulatoryCountryCode: 'XX',
1668
+ };
1669
+ const commissioningController = new CommissioningController({
1670
+ environment: {
1671
+ environment,
1672
+ id: uniqueId,
1673
+ },
1674
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1675
+ adminFabricLabel,
1676
+ });
1677
+
1678
+ if (hasParameter('pairingcode')) {
1679
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1680
+ const pairingCode = getParameter('pairingcode');
1681
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1682
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1683
+
1684
+ let longDiscriminator, setupPin, shortDiscriminator;
1685
+ if (pairingCode !== undefined) {
1686
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1687
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1688
+ longDiscriminator = undefined;
1689
+ setupPin = pairingCodeCodec.passcode;
1690
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1691
+ } else {
1692
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1693
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1694
+ setupPin = this.controllerContext.get('pin', 20202021);
1695
+ }
1696
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1697
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1698
+ }
1699
+
1700
+ const options = {
1701
+ commissioning: commissioningOptions,
1702
+ discovery: {
1703
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1704
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1705
+ },
1706
+ passcode: setupPin,
1707
+ } as NodeCommissioningOptions;
1708
+ this.log.info('Commissioning with options:', options);
1709
+ const nodeId = await this.commissioningController.commissionNode(options);
1710
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1711
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1712
+ } // (hasParameter('pairingcode'))
1713
+
1714
+ if (hasParameter('unpairall')) {
1715
+ this.log.info('***Commissioning controller unpairing all nodes...');
1716
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1717
+ for (const nodeId of nodeIds) {
1718
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1719
+ await this.commissioningController.removeNode(nodeId);
1720
+ }
1721
+ return;
1722
+ }
1723
+
1724
+ if (hasParameter('discover')) {
1725
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1726
+ // console.log(discover);
1727
+ }
1728
+
1729
+ if (!this.commissioningController.isCommissioned()) {
1730
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1731
+ return;
1732
+ }
1733
+
1734
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1735
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1736
+ for (const nodeId of nodeIds) {
1737
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1738
+
1739
+ const node = await this.commissioningController.connectNode(nodeId, {
1740
+ autoSubscribe: false,
1741
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1742
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1743
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1744
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1745
+ stateInformationCallback: (peerNodeId, info) => {
1746
+ switch (info) {
1747
+ case NodeStateInformation.Connected:
1748
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1749
+ break;
1750
+ case NodeStateInformation.Disconnected:
1751
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1752
+ break;
1753
+ case NodeStateInformation.Reconnecting:
1754
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1755
+ break;
1756
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1757
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1758
+ break;
1759
+ case NodeStateInformation.StructureChanged:
1760
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1761
+ break;
1762
+ case NodeStateInformation.Decommissioned:
1763
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1764
+ break;
1765
+ default:
1766
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1767
+ break;
1768
+ }
1769
+ },
1770
+ });
1771
+
1772
+ node.logStructure();
1773
+
1774
+ // Get the interaction client
1775
+ this.log.info('Getting the interaction client');
1776
+ const interactionClient = await node.getInteractionClient();
1777
+ let cluster;
1778
+ let attributes;
1779
+
1780
+ // Log BasicInformationCluster
1781
+ cluster = BasicInformationCluster;
1782
+ attributes = await interactionClient.getMultipleAttributes({
1783
+ attributes: [{ clusterId: cluster.id }],
1784
+ });
1785
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1786
+ attributes.forEach((attribute) => {
1787
+ this.log.info(
1788
+ `- 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}`,
1789
+ );
1790
+ });
1791
+
1792
+ // Log PowerSourceCluster
1793
+ cluster = PowerSourceCluster;
1794
+ attributes = await interactionClient.getMultipleAttributes({
1795
+ attributes: [{ clusterId: cluster.id }],
1796
+ });
1797
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1798
+ attributes.forEach((attribute) => {
1799
+ this.log.info(
1800
+ `- 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}`,
1801
+ );
1802
+ });
1803
+
1804
+ // Log ThreadNetworkDiagnostics
1805
+ cluster = ThreadNetworkDiagnosticsCluster;
1806
+ attributes = await interactionClient.getMultipleAttributes({
1807
+ attributes: [{ clusterId: cluster.id }],
1808
+ });
1809
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1810
+ attributes.forEach((attribute) => {
1811
+ this.log.info(
1812
+ `- 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}`,
1813
+ );
1814
+ });
1815
+
1816
+ // Log SwitchCluster
1817
+ cluster = SwitchCluster;
1818
+ attributes = await interactionClient.getMultipleAttributes({
1819
+ attributes: [{ clusterId: cluster.id }],
1820
+ });
1821
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1822
+ attributes.forEach((attribute) => {
1823
+ this.log.info(
1824
+ `- 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}`,
1825
+ );
1826
+ });
1827
+
1828
+ this.log.info('Subscribing to all attributes and events');
1829
+ await node.subscribeAllAttributesAndEvents({
1830
+ ignoreInitialTriggers: false,
1831
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1832
+ this.log.info(
1833
+ `***${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}`,
1834
+ ),
1835
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1836
+ this.log.info(
1837
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1838
+ );
1839
+ },
1840
+ });
1841
+ this.log.info('Subscribed to all attributes and events');
1842
+ }
1843
+ */
1327
1844
  }
1845
+ /** */
1846
+ /** Matter.js methods */
1847
+ /** */
1848
+ /**
1849
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1850
+ *
1851
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1852
+ */
1328
1853
  async startMatterStorage() {
1854
+ // Setup Matter storage
1329
1855
  this.log.info(`Starting matter node storage...`);
1330
1856
  this.matterStorageService = this.environment.get(StorageService);
1331
1857
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1333,8 +1859,17 @@ export class Matterbridge extends EventEmitter {
1333
1859
  this.log.info('Matter node storage manager "Matterbridge" created');
1334
1860
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1335
1861
  this.log.info('Matter node storage started');
1336
- await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1862
+ // Backup matter storage since it is created/opened correctly
1863
+ await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
1337
1864
  }
1865
+ /**
1866
+ * Makes a backup copy of the specified matter storage directory.
1867
+ *
1868
+ * @param {string} storageName - The name of the storage directory to be backed up.
1869
+ * @param {string} backupName - The name of the backup directory to be created.
1870
+ * @private
1871
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1872
+ */
1338
1873
  async backupMatterStorage(storageName, backupName) {
1339
1874
  this.log.info('Creating matter node storage backup...');
1340
1875
  try {
@@ -1345,6 +1880,11 @@ export class Matterbridge extends EventEmitter {
1345
1880
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1346
1881
  }
1347
1882
  }
1883
+ /**
1884
+ * Stops the matter storage.
1885
+ *
1886
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1887
+ */
1348
1888
  async stopMatterStorage() {
1349
1889
  this.log.info('Closing matter node storage...');
1350
1890
  await this.matterStorageManager?.close();
@@ -1353,6 +1893,20 @@ export class Matterbridge extends EventEmitter {
1353
1893
  this.matterbridgeContext = undefined;
1354
1894
  this.log.info('Matter node storage closed');
1355
1895
  }
1896
+ /**
1897
+ * Creates a server node storage context.
1898
+ *
1899
+ * @param {string} storeId - The storeId.
1900
+ * @param {string} deviceName - The name of the device.
1901
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1902
+ * @param {number} vendorId - The vendor ID.
1903
+ * @param {string} vendorName - The vendor name.
1904
+ * @param {number} productId - The product ID.
1905
+ * @param {string} productName - The product name.
1906
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1907
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
1908
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1909
+ */
1356
1910
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1357
1911
  const { randomBytes } = await import('node:crypto');
1358
1912
  if (!this.matterStorageService)
@@ -1392,6 +1946,15 @@ export class Matterbridge extends EventEmitter {
1392
1946
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1393
1947
  return storageContext;
1394
1948
  }
1949
+ /**
1950
+ * Creates a server node.
1951
+ *
1952
+ * @param {StorageContext} storageContext - The storage context for the server node.
1953
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
1954
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
1955
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
1956
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
1957
+ */
1395
1958
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1396
1959
  const storeId = await storageContext.get('storeId');
1397
1960
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1401,24 +1964,37 @@ export class Matterbridge extends EventEmitter {
1401
1964
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1402
1965
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1403
1966
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1967
+ /**
1968
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
1969
+ */
1404
1970
  const serverNode = await ServerNode.create({
1971
+ // Required: Give the Node a unique ID which is used to store the state of this node
1405
1972
  id: storeId,
1973
+ // Provide Network relevant configuration like the port
1974
+ // Optional when operating only one device on a host, Default port is 5540
1406
1975
  network: {
1407
1976
  listeningAddressIpv4: this.ipv4address,
1408
1977
  listeningAddressIpv6: this.ipv6address,
1409
1978
  port,
1410
1979
  },
1980
+ // Provide the certificate for the device
1411
1981
  operationalCredentials: {
1412
1982
  certification: this.certification,
1413
1983
  },
1984
+ // Provide Commissioning relevant settings
1985
+ // Optional for development/testing purposes
1414
1986
  commissioning: {
1415
1987
  passcode,
1416
1988
  discriminator,
1417
1989
  },
1990
+ // Provide Node announcement settings
1991
+ // Optional: If Ommitted some development defaults are used
1418
1992
  productDescription: {
1419
1993
  name: await storageContext.get('deviceName'),
1420
1994
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1421
1995
  },
1996
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1997
+ // Optional: If Omitted some development defaults are used
1422
1998
  basicInformation: {
1423
1999
  vendorId: VendorId(await storageContext.get('vendorId')),
1424
2000
  vendorName: await storageContext.get('vendorName'),
@@ -1435,17 +2011,23 @@ export class Matterbridge extends EventEmitter {
1435
2011
  reachable: true,
1436
2012
  },
1437
2013
  });
2014
+ /**
2015
+ * This event is triggered when the device is initially commissioned successfully.
2016
+ * This means: It is added to the first fabric.
2017
+ */
1438
2018
  serverNode.lifecycle.commissioned.on(() => {
1439
2019
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1440
2020
  this.advertisingNodes.delete(storeId);
1441
2021
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1442
2022
  });
2023
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1443
2024
  serverNode.lifecycle.decommissioned.on(() => {
1444
2025
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1445
2026
  this.advertisingNodes.delete(storeId);
1446
2027
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1447
2028
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1448
2029
  });
2030
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1449
2031
  serverNode.lifecycle.online.on(async () => {
1450
2032
  this.log.notice(`Server node for ${storeId} is online`);
1451
2033
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1456,13 +2038,16 @@ export class Matterbridge extends EventEmitter {
1456
2038
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1457
2039
  }
1458
2040
  else {
2041
+ // istanbul ignore next
1459
2042
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2043
+ // istanbul ignore next
1460
2044
  this.advertisingNodes.delete(storeId);
1461
2045
  }
1462
2046
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1463
2047
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1464
2048
  this.emit('online', storeId);
1465
2049
  });
2050
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1466
2051
  serverNode.lifecycle.offline.on(() => {
1467
2052
  this.log.notice(`Server node for ${storeId} is offline`);
1468
2053
  this.advertisingNodes.delete(storeId);
@@ -1470,11 +2055,15 @@ export class Matterbridge extends EventEmitter {
1470
2055
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1471
2056
  this.emit('offline', storeId);
1472
2057
  });
2058
+ /**
2059
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2060
+ * information is needed.
2061
+ */
1473
2062
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1474
2063
  let action = '';
1475
2064
  switch (fabricAction) {
1476
2065
  case FabricAction.Added:
1477
- this.advertisingNodes.delete(storeId);
2066
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1478
2067
  action = 'added';
1479
2068
  break;
1480
2069
  case FabricAction.Removed:
@@ -1487,14 +2076,22 @@ export class Matterbridge extends EventEmitter {
1487
2076
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1488
2077
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1489
2078
  });
2079
+ /**
2080
+ * This event is triggered when an operative new session was opened by a Controller.
2081
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2082
+ */
1490
2083
  serverNode.events.sessions.opened.on((session) => {
1491
2084
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1492
2085
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1493
2086
  });
2087
+ /**
2088
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2089
+ */
1494
2090
  serverNode.events.sessions.closed.on((session) => {
1495
2091
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1496
2092
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1497
2093
  });
2094
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1498
2095
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1499
2096
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1500
2097
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
@@ -1502,6 +2099,12 @@ export class Matterbridge extends EventEmitter {
1502
2099
  this.log.info(`Created server node for ${storeId}`);
1503
2100
  return serverNode;
1504
2101
  }
2102
+ /**
2103
+ * Gets the matter sanitized data of the specified server node.
2104
+ *
2105
+ * @param {ServerNode} [serverNode] - The server node to start.
2106
+ * @returns {ApiMatter} The sanitized data of the server node.
2107
+ */
1505
2108
  getServerNodeData(serverNode) {
1506
2109
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1507
2110
  return {
@@ -1518,12 +2121,25 @@ export class Matterbridge extends EventEmitter {
1518
2121
  serialNumber: serverNode.state.basicInformation.serialNumber,
1519
2122
  };
1520
2123
  }
2124
+ /**
2125
+ * Starts the specified server node.
2126
+ *
2127
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2128
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2129
+ */
1521
2130
  async startServerNode(matterServerNode) {
1522
2131
  if (!matterServerNode)
1523
2132
  return;
1524
2133
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1525
2134
  await matterServerNode.start();
1526
2135
  }
2136
+ /**
2137
+ * Stops the specified server node.
2138
+ *
2139
+ * @param {ServerNode} matterServerNode - The server node to stop.
2140
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2141
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2142
+ */
1527
2143
  async stopServerNode(matterServerNode, timeout = 30000) {
1528
2144
  if (!matterServerNode)
1529
2145
  return;
@@ -1536,12 +2152,25 @@ export class Matterbridge extends EventEmitter {
1536
2152
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1537
2153
  }
1538
2154
  }
2155
+ /**
2156
+ * Creates an aggregator node with the specified storage context.
2157
+ *
2158
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2159
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2160
+ */
1539
2161
  async createAggregatorNode(storageContext) {
1540
2162
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1541
2163
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1542
2164
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1543
2165
  return aggregatorNode;
1544
2166
  }
2167
+ /**
2168
+ * Creates and configures the server node for an accessory plugin for a given device.
2169
+ *
2170
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
2171
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2172
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2173
+ */
1545
2174
  async createAccessoryPlugin(plugin, device) {
1546
2175
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1547
2176
  plugin.locked = true;
@@ -1553,6 +2182,12 @@ export class Matterbridge extends EventEmitter {
1553
2182
  await plugin.serverNode.add(device);
1554
2183
  }
1555
2184
  }
2185
+ /**
2186
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
2187
+ *
2188
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
2189
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
2190
+ */
1556
2191
  async createDynamicPlugin(plugin) {
1557
2192
  if (!plugin.locked) {
1558
2193
  plugin.locked = true;
@@ -1565,6 +2200,13 @@ export class Matterbridge extends EventEmitter {
1565
2200
  await plugin.serverNode.add(plugin.aggregatorNode);
1566
2201
  }
1567
2202
  }
2203
+ /**
2204
+ * Creates and configures the server node for a single not bridged device.
2205
+ *
2206
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
2207
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
2208
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
2209
+ */
1568
2210
  async createDeviceServerNode(plugin, device) {
1569
2211
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1570
2212
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1575,7 +2217,15 @@ export class Matterbridge extends EventEmitter {
1575
2217
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1576
2218
  }
1577
2219
  }
2220
+ /**
2221
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2222
+ *
2223
+ * @param {string} pluginName - The name of the plugin.
2224
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2225
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2226
+ */
1578
2227
  async addBridgedEndpoint(pluginName, device) {
2228
+ // Check if the plugin is registered
1579
2229
  const plugin = this.plugins.get(pluginName);
1580
2230
  if (!plugin) {
1581
2231
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1595,6 +2245,7 @@ export class Matterbridge extends EventEmitter {
1595
2245
  }
1596
2246
  else if (this.bridgeMode === 'bridge') {
1597
2247
  if (device.mode === 'matter') {
2248
+ // Register and add the device to the matterbridge server node
1598
2249
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1599
2250
  if (!this.serverNode) {
1600
2251
  this.log.error('Server node not found for Matterbridge');
@@ -1611,6 +2262,7 @@ export class Matterbridge extends EventEmitter {
1611
2262
  }
1612
2263
  }
1613
2264
  else {
2265
+ // Register and add the device to the matterbridge aggregator node
1614
2266
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1615
2267
  if (!this.aggregatorNode) {
1616
2268
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1628,6 +2280,7 @@ export class Matterbridge extends EventEmitter {
1628
2280
  }
1629
2281
  }
1630
2282
  else if (this.bridgeMode === 'childbridge') {
2283
+ // Register and add the device to the plugin server node
1631
2284
  if (plugin.type === 'AccessoryPlatform') {
1632
2285
  try {
1633
2286
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1651,10 +2304,12 @@ export class Matterbridge extends EventEmitter {
1651
2304
  return;
1652
2305
  }
1653
2306
  }
2307
+ // Register and add the device to the plugin aggregator node
1654
2308
  if (plugin.type === 'DynamicPlatform') {
1655
2309
  try {
1656
2310
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1657
2311
  await this.createDynamicPlugin(plugin);
2312
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1658
2313
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1659
2314
  if (!plugin.aggregatorNode) {
1660
2315
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1675,17 +2330,28 @@ export class Matterbridge extends EventEmitter {
1675
2330
  }
1676
2331
  if (plugin.registeredDevices !== undefined)
1677
2332
  plugin.registeredDevices++;
2333
+ // Add the device to the DeviceManager
1678
2334
  this.devices.set(device);
2335
+ // Subscribe to the attributes changed event
1679
2336
  await this.subscribeAttributeChanged(plugin, device);
1680
2337
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1681
2338
  }
2339
+ /**
2340
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2341
+ *
2342
+ * @param {string} pluginName - The name of the plugin.
2343
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2344
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2345
+ */
1682
2346
  async removeBridgedEndpoint(pluginName, device) {
1683
2347
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2348
+ // Check if the plugin is registered
1684
2349
  const plugin = this.plugins.get(pluginName);
1685
2350
  if (!plugin) {
1686
2351
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1687
2352
  return;
1688
2353
  }
2354
+ // Register and add the device to the matterbridge aggregator node
1689
2355
  if (this.bridgeMode === 'bridge') {
1690
2356
  if (!this.aggregatorNode) {
1691
2357
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1698,6 +2364,7 @@ export class Matterbridge extends EventEmitter {
1698
2364
  }
1699
2365
  else if (this.bridgeMode === 'childbridge') {
1700
2366
  if (plugin.type === 'AccessoryPlatform') {
2367
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1701
2368
  }
1702
2369
  else if (plugin.type === 'DynamicPlatform') {
1703
2370
  if (!plugin.aggregatorNode) {
@@ -1710,8 +2377,21 @@ export class Matterbridge extends EventEmitter {
1710
2377
  if (plugin.registeredDevices !== undefined)
1711
2378
  plugin.registeredDevices--;
1712
2379
  }
2380
+ // Remove the device from the DeviceManager
1713
2381
  this.devices.remove(device);
1714
2382
  }
2383
+ /**
2384
+ * Removes all bridged endpoints from the specified plugin.
2385
+ *
2386
+ * @param {string} pluginName - The name of the plugin.
2387
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2388
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2389
+ *
2390
+ * @remarks
2391
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2392
+ * It also applies a delay between each removal if specified.
2393
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2394
+ */
1715
2395
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1716
2396
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1717
2397
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1722,13 +2402,24 @@ export class Matterbridge extends EventEmitter {
1722
2402
  if (delay > 0)
1723
2403
  await wait(2000);
1724
2404
  }
2405
+ /**
2406
+ * Subscribes to the attribute change event for the given device and plugin.
2407
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2408
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2409
+ *
2410
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2411
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2412
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2413
+ */
1725
2414
  async subscribeAttributeChanged(plugin, device) {
1726
2415
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
1727
2416
  return;
1728
2417
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
2418
+ // Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
1729
2419
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1730
2420
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1731
2421
  this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
2422
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1732
2423
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
1733
2424
  });
1734
2425
  }
@@ -1776,6 +2467,7 @@ export class Matterbridge extends EventEmitter {
1776
2467
  this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1777
2468
  await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1778
2469
  this.log.debug(`Bridged endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${value}${db}`);
2470
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1779
2471
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
1780
2472
  });
1781
2473
  }
@@ -1785,6 +2477,7 @@ export class Matterbridge extends EventEmitter {
1785
2477
  this.log.debug(`Subscribing to child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
1786
2478
  await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1787
2479
  this.log.debug(`Bridged child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${value}${db}`);
2480
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1788
2481
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
1789
2482
  });
1790
2483
  }
@@ -1792,6 +2485,12 @@ export class Matterbridge extends EventEmitter {
1792
2485
  }
1793
2486
  }
1794
2487
  }
2488
+ /**
2489
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2490
+ *
2491
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2492
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2493
+ */
1795
2494
  sanitizeFabricInformations(fabricInfo) {
1796
2495
  return fabricInfo.map((info) => {
1797
2496
  return {
@@ -1805,6 +2504,12 @@ export class Matterbridge extends EventEmitter {
1805
2504
  };
1806
2505
  });
1807
2506
  }
2507
+ /**
2508
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2509
+ *
2510
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2511
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2512
+ */
1808
2513
  sanitizeSessionInformation(sessions) {
1809
2514
  return sessions
1810
2515
  .filter((session) => session.isPeerActive)
@@ -1831,7 +2536,21 @@ export class Matterbridge extends EventEmitter {
1831
2536
  };
1832
2537
  });
1833
2538
  }
2539
+ /**
2540
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2541
+ *
2542
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2543
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2544
+ */
2545
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1834
2546
  async setAggregatorReachability(aggregatorNode, reachable) {
2547
+ /*
2548
+ for (const child of aggregatorNode.parts) {
2549
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2550
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2551
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2552
+ }
2553
+ */
1835
2554
  }
1836
2555
  getVendorIdName = (vendorId) => {
1837
2556
  if (!vendorId)
@@ -1871,10 +2590,11 @@ export class Matterbridge extends EventEmitter {
1871
2590
  case 0x1488:
1872
2591
  vendorName = '(ShortcutLabsFlic)';
1873
2592
  break;
1874
- case 65521:
2593
+ case 65521: // 0xFFF1
1875
2594
  vendorName = '(MatterTest)';
1876
2595
  break;
1877
2596
  }
1878
2597
  return vendorName;
1879
2598
  };
1880
2599
  }
2600
+ //# sourceMappingURL=matterbridge.js.map