matterbridge 3.3.0-dev-20251002-bbaa166 → 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 (283) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/cli.d.ts +26 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +91 -2
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cliEmitter.d.ts +34 -0
  7. package/dist/cliEmitter.d.ts.map +1 -0
  8. package/dist/cliEmitter.js +30 -0
  9. package/dist/cliEmitter.js.map +1 -0
  10. package/dist/clusters/export.d.ts +2 -0
  11. package/dist/clusters/export.d.ts.map +1 -0
  12. package/dist/clusters/export.js +2 -0
  13. package/dist/clusters/export.js.map +1 -0
  14. package/dist/defaultConfigSchema.d.ts +28 -0
  15. package/dist/defaultConfigSchema.d.ts.map +1 -0
  16. package/dist/defaultConfigSchema.js +24 -0
  17. package/dist/defaultConfigSchema.js.map +1 -0
  18. package/dist/deviceManager.d.ts +112 -0
  19. package/dist/deviceManager.d.ts.map +1 -0
  20. package/dist/deviceManager.js +94 -1
  21. package/dist/deviceManager.js.map +1 -0
  22. package/dist/devices/airConditioner.d.ts +98 -0
  23. package/dist/devices/airConditioner.d.ts.map +1 -0
  24. package/dist/devices/airConditioner.js +57 -0
  25. package/dist/devices/airConditioner.js.map +1 -0
  26. package/dist/devices/batteryStorage.d.ts +48 -0
  27. package/dist/devices/batteryStorage.d.ts.map +1 -0
  28. package/dist/devices/batteryStorage.js +48 -1
  29. package/dist/devices/batteryStorage.js.map +1 -0
  30. package/dist/devices/cooktop.d.ts +60 -0
  31. package/dist/devices/cooktop.d.ts.map +1 -0
  32. package/dist/devices/cooktop.js +55 -0
  33. package/dist/devices/cooktop.js.map +1 -0
  34. package/dist/devices/dishwasher.d.ts +71 -0
  35. package/dist/devices/dishwasher.d.ts.map +1 -0
  36. package/dist/devices/dishwasher.js +57 -0
  37. package/dist/devices/dishwasher.js.map +1 -0
  38. package/dist/devices/evse.d.ts +75 -0
  39. package/dist/devices/evse.d.ts.map +1 -0
  40. package/dist/devices/evse.js +74 -10
  41. package/dist/devices/evse.js.map +1 -0
  42. package/dist/devices/export.d.ts +17 -0
  43. package/dist/devices/export.d.ts.map +1 -0
  44. package/dist/devices/export.js +5 -0
  45. package/dist/devices/export.js.map +1 -0
  46. package/dist/devices/extractorHood.d.ts +46 -0
  47. package/dist/devices/extractorHood.d.ts.map +1 -0
  48. package/dist/devices/extractorHood.js +42 -0
  49. package/dist/devices/extractorHood.js.map +1 -0
  50. package/dist/devices/heatPump.d.ts +47 -0
  51. package/dist/devices/heatPump.d.ts.map +1 -0
  52. package/dist/devices/heatPump.js +50 -2
  53. package/dist/devices/heatPump.js.map +1 -0
  54. package/dist/devices/laundryDryer.d.ts +67 -0
  55. package/dist/devices/laundryDryer.d.ts.map +1 -0
  56. package/dist/devices/laundryDryer.js +62 -3
  57. package/dist/devices/laundryDryer.js.map +1 -0
  58. package/dist/devices/laundryWasher.d.ts +81 -0
  59. package/dist/devices/laundryWasher.d.ts.map +1 -0
  60. package/dist/devices/laundryWasher.js +70 -4
  61. package/dist/devices/laundryWasher.js.map +1 -0
  62. package/dist/devices/microwaveOven.d.ts +168 -0
  63. package/dist/devices/microwaveOven.d.ts.map +1 -0
  64. package/dist/devices/microwaveOven.js +88 -5
  65. package/dist/devices/microwaveOven.js.map +1 -0
  66. package/dist/devices/oven.d.ts +105 -0
  67. package/dist/devices/oven.d.ts.map +1 -0
  68. package/dist/devices/oven.js +85 -0
  69. package/dist/devices/oven.js.map +1 -0
  70. package/dist/devices/refrigerator.d.ts +118 -0
  71. package/dist/devices/refrigerator.d.ts.map +1 -0
  72. package/dist/devices/refrigerator.js +102 -0
  73. package/dist/devices/refrigerator.js.map +1 -0
  74. package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
  75. package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
  76. package/dist/devices/roboticVacuumCleaner.js +100 -9
  77. package/dist/devices/roboticVacuumCleaner.js.map +1 -0
  78. package/dist/devices/solarPower.d.ts +40 -0
  79. package/dist/devices/solarPower.d.ts.map +1 -0
  80. package/dist/devices/solarPower.js +38 -0
  81. package/dist/devices/solarPower.js.map +1 -0
  82. package/dist/devices/speaker.d.ts +87 -0
  83. package/dist/devices/speaker.d.ts.map +1 -0
  84. package/dist/devices/speaker.js +84 -0
  85. package/dist/devices/speaker.js.map +1 -0
  86. package/dist/devices/temperatureControl.d.ts +166 -0
  87. package/dist/devices/temperatureControl.d.ts.map +1 -0
  88. package/dist/devices/temperatureControl.js +25 -3
  89. package/dist/devices/temperatureControl.js.map +1 -0
  90. package/dist/devices/waterHeater.d.ts +111 -0
  91. package/dist/devices/waterHeater.d.ts.map +1 -0
  92. package/dist/devices/waterHeater.js +82 -2
  93. package/dist/devices/waterHeater.js.map +1 -0
  94. package/dist/dgram/coap.d.ts +205 -0
  95. package/dist/dgram/coap.d.ts.map +1 -0
  96. package/dist/dgram/coap.js +126 -13
  97. package/dist/dgram/coap.js.map +1 -0
  98. package/dist/dgram/dgram.d.ts +141 -0
  99. package/dist/dgram/dgram.d.ts.map +1 -0
  100. package/dist/dgram/dgram.js +114 -2
  101. package/dist/dgram/dgram.js.map +1 -0
  102. package/dist/dgram/mb_coap.d.ts +24 -0
  103. package/dist/dgram/mb_coap.d.ts.map +1 -0
  104. package/dist/dgram/mb_coap.js +41 -3
  105. package/dist/dgram/mb_coap.js.map +1 -0
  106. package/dist/dgram/mb_mdns.d.ts +24 -0
  107. package/dist/dgram/mb_mdns.d.ts.map +1 -0
  108. package/dist/dgram/mb_mdns.js +80 -15
  109. package/dist/dgram/mb_mdns.js.map +1 -0
  110. package/dist/dgram/mdns.d.ts +290 -0
  111. package/dist/dgram/mdns.d.ts.map +1 -0
  112. package/dist/dgram/mdns.js +299 -137
  113. package/dist/dgram/mdns.js.map +1 -0
  114. package/dist/dgram/multicast.d.ts +67 -0
  115. package/dist/dgram/multicast.d.ts.map +1 -0
  116. package/dist/dgram/multicast.js +62 -1
  117. package/dist/dgram/multicast.js.map +1 -0
  118. package/dist/dgram/unicast.d.ts +56 -0
  119. package/dist/dgram/unicast.d.ts.map +1 -0
  120. package/dist/dgram/unicast.js +54 -0
  121. package/dist/dgram/unicast.js.map +1 -0
  122. package/dist/frontend.d.ts +232 -0
  123. package/dist/frontend.d.ts.map +1 -0
  124. package/dist/frontend.js +458 -64
  125. package/dist/frontend.js.map +1 -0
  126. package/dist/frontendTypes.d.ts +514 -0
  127. package/dist/frontendTypes.d.ts.map +1 -0
  128. package/dist/frontendTypes.js +45 -0
  129. package/dist/frontendTypes.js.map +1 -0
  130. package/dist/globalMatterbridge.d.ts +59 -0
  131. package/dist/globalMatterbridge.d.ts.map +1 -0
  132. package/dist/globalMatterbridge.js +47 -0
  133. package/dist/globalMatterbridge.js.map +1 -0
  134. package/dist/helpers.d.ts +48 -0
  135. package/dist/helpers.d.ts.map +1 -0
  136. package/dist/helpers.js +53 -0
  137. package/dist/helpers.js.map +1 -0
  138. package/dist/index.d.ts +33 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +30 -1
  141. package/dist/index.js.map +1 -0
  142. package/dist/logger/export.d.ts +2 -0
  143. package/dist/logger/export.d.ts.map +1 -0
  144. package/dist/logger/export.js +1 -0
  145. package/dist/logger/export.js.map +1 -0
  146. package/dist/matter/behaviors.d.ts +2 -0
  147. package/dist/matter/behaviors.d.ts.map +1 -0
  148. package/dist/matter/behaviors.js +2 -0
  149. package/dist/matter/behaviors.js.map +1 -0
  150. package/dist/matter/clusters.d.ts +2 -0
  151. package/dist/matter/clusters.d.ts.map +1 -0
  152. package/dist/matter/clusters.js +2 -0
  153. package/dist/matter/clusters.js.map +1 -0
  154. package/dist/matter/devices.d.ts +2 -0
  155. package/dist/matter/devices.d.ts.map +1 -0
  156. package/dist/matter/devices.js +2 -0
  157. package/dist/matter/devices.js.map +1 -0
  158. package/dist/matter/endpoints.d.ts +2 -0
  159. package/dist/matter/endpoints.d.ts.map +1 -0
  160. package/dist/matter/endpoints.js +2 -0
  161. package/dist/matter/endpoints.js.map +1 -0
  162. package/dist/matter/export.d.ts +5 -0
  163. package/dist/matter/export.d.ts.map +1 -0
  164. package/dist/matter/export.js +3 -0
  165. package/dist/matter/export.js.map +1 -0
  166. package/dist/matter/types.d.ts +3 -0
  167. package/dist/matter/types.d.ts.map +1 -0
  168. package/dist/matter/types.js +3 -0
  169. package/dist/matter/types.js.map +1 -0
  170. package/dist/matterbridge.d.ts +430 -0
  171. package/dist/matterbridge.d.ts.map +1 -0
  172. package/dist/matterbridge.js +831 -108
  173. package/dist/matterbridge.js.map +1 -0
  174. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  175. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  176. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  177. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  178. package/dist/matterbridgeBehaviors.d.ts +1747 -0
  179. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  180. package/dist/matterbridgeBehaviors.js +65 -5
  181. package/dist/matterbridgeBehaviors.js.map +1 -0
  182. package/dist/matterbridgeDeviceTypes.d.ts +761 -0
  183. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  184. package/dist/matterbridgeDeviceTypes.js +630 -17
  185. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  186. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  187. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  188. package/dist/matterbridgeDynamicPlatform.js +36 -0
  189. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  190. package/dist/matterbridgeEndpoint.d.ts +1534 -0
  191. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  192. package/dist/matterbridgeEndpoint.js +1398 -58
  193. package/dist/matterbridgeEndpoint.js.map +1 -0
  194. package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
  195. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  196. package/dist/matterbridgeEndpointHelpers.js +345 -12
  197. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  198. package/dist/matterbridgePlatform.d.ts +402 -0
  199. package/dist/matterbridgePlatform.d.ts.map +1 -0
  200. package/dist/matterbridgePlatform.js +340 -1
  201. package/dist/matterbridgePlatform.js.map +1 -0
  202. package/dist/matterbridgeTypes.d.ts +201 -0
  203. package/dist/matterbridgeTypes.d.ts.map +1 -0
  204. package/dist/matterbridgeTypes.js +30 -0
  205. package/dist/matterbridgeTypes.js.map +1 -0
  206. package/dist/pluginManager.d.ts +270 -0
  207. package/dist/pluginManager.d.ts.map +1 -0
  208. package/dist/pluginManager.js +249 -3
  209. package/dist/pluginManager.js.map +1 -0
  210. package/dist/shelly.d.ts +174 -0
  211. package/dist/shelly.d.ts.map +1 -0
  212. package/dist/shelly.js +168 -7
  213. package/dist/shelly.js.map +1 -0
  214. package/dist/storage/export.d.ts +2 -0
  215. package/dist/storage/export.d.ts.map +1 -0
  216. package/dist/storage/export.js +1 -0
  217. package/dist/storage/export.js.map +1 -0
  218. package/dist/update.d.ts +75 -0
  219. package/dist/update.d.ts.map +1 -0
  220. package/dist/update.js +69 -0
  221. package/dist/update.js.map +1 -0
  222. package/dist/utils/colorUtils.d.ts +99 -0
  223. package/dist/utils/colorUtils.d.ts.map +1 -0
  224. package/dist/utils/colorUtils.js +97 -2
  225. package/dist/utils/colorUtils.js.map +1 -0
  226. package/dist/utils/commandLine.d.ts +59 -0
  227. package/dist/utils/commandLine.d.ts.map +1 -0
  228. package/dist/utils/commandLine.js +54 -0
  229. package/dist/utils/commandLine.js.map +1 -0
  230. package/dist/utils/copyDirectory.d.ts +33 -0
  231. package/dist/utils/copyDirectory.d.ts.map +1 -0
  232. package/dist/utils/copyDirectory.js +38 -1
  233. package/dist/utils/copyDirectory.js.map +1 -0
  234. package/dist/utils/createDirectory.d.ts +34 -0
  235. package/dist/utils/createDirectory.d.ts.map +1 -0
  236. package/dist/utils/createDirectory.js +33 -0
  237. package/dist/utils/createDirectory.js.map +1 -0
  238. package/dist/utils/createZip.d.ts +39 -0
  239. package/dist/utils/createZip.d.ts.map +1 -0
  240. package/dist/utils/createZip.js +47 -2
  241. package/dist/utils/createZip.js.map +1 -0
  242. package/dist/utils/deepCopy.d.ts +32 -0
  243. package/dist/utils/deepCopy.d.ts.map +1 -0
  244. package/dist/utils/deepCopy.js +39 -0
  245. package/dist/utils/deepCopy.js.map +1 -0
  246. package/dist/utils/deepEqual.d.ts +54 -0
  247. package/dist/utils/deepEqual.d.ts.map +1 -0
  248. package/dist/utils/deepEqual.js +72 -1
  249. package/dist/utils/deepEqual.js.map +1 -0
  250. package/dist/utils/error.d.ts +44 -0
  251. package/dist/utils/error.d.ts.map +1 -0
  252. package/dist/utils/error.js +41 -0
  253. package/dist/utils/error.js.map +1 -0
  254. package/dist/utils/export.d.ts +13 -0
  255. package/dist/utils/export.d.ts.map +1 -0
  256. package/dist/utils/export.js +1 -0
  257. package/dist/utils/export.js.map +1 -0
  258. package/dist/utils/hex.d.ts +89 -0
  259. package/dist/utils/hex.d.ts.map +1 -0
  260. package/dist/utils/hex.js +124 -0
  261. package/dist/utils/hex.js.map +1 -0
  262. package/dist/utils/isvalid.d.ts +103 -0
  263. package/dist/utils/isvalid.d.ts.map +1 -0
  264. package/dist/utils/isvalid.js +101 -0
  265. package/dist/utils/isvalid.js.map +1 -0
  266. package/dist/utils/jestHelpers.d.ts +137 -0
  267. package/dist/utils/jestHelpers.d.ts.map +1 -0
  268. package/dist/utils/jestHelpers.js +153 -3
  269. package/dist/utils/jestHelpers.js.map +1 -0
  270. package/dist/utils/network.d.ts +84 -0
  271. package/dist/utils/network.d.ts.map +1 -0
  272. package/dist/utils/network.js +91 -5
  273. package/dist/utils/network.js.map +1 -0
  274. package/dist/utils/spawn.d.ts +34 -0
  275. package/dist/utils/spawn.d.ts.map +1 -0
  276. package/dist/utils/spawn.js +69 -0
  277. package/dist/utils/spawn.js.map +1 -0
  278. package/dist/utils/wait.d.ts +54 -0
  279. package/dist/utils/wait.d.ts.map +1 -0
  280. package/dist/utils/wait.js +60 -8
  281. package/dist/utils/wait.js.map +1 -0
  282. package/npm-shrinkwrap.json +2 -2
  283. 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,18 +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
  }
139
- getDevices() {
140
- return this.devices.array();
141
- }
142
- getPlugins() {
143
- return this.plugins.array();
144
- }
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
+ */
145
182
  async setLogLevel(logLevel) {
146
183
  if (this.log)
147
184
  this.log.logLevel = logLevel;
@@ -155,19 +192,31 @@ export class Matterbridge extends EventEmitter {
155
192
  for (const plugin of this.plugins) {
156
193
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
157
194
  continue;
158
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
159
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
160
- }
161
- let callbackLogLevel = "notice";
162
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
163
- callbackLogLevel = "info";
164
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
165
- 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 */;
166
204
  AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
167
205
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
168
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
+ */
169
217
  static async loadInstance(initialize = false) {
170
218
  if (!Matterbridge.instance) {
219
+ // eslint-disable-next-line no-console
171
220
  if (hasParameter('debug'))
172
221
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
173
222
  Matterbridge.instance = new Matterbridge();
@@ -176,8 +225,17 @@ export class Matterbridge extends EventEmitter {
176
225
  }
177
226
  return Matterbridge.instance;
178
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
+ */
179
236
  async destroyInstance(timeout = 1000, pause = 250) {
180
237
  this.log.info(`Destroy instance...`);
238
+ // Save server nodes to close
181
239
  const servers = [];
182
240
  if (this.bridgeMode === 'bridge') {
183
241
  if (this.serverNode)
@@ -195,93 +253,132 @@ export class Matterbridge extends EventEmitter {
195
253
  servers.push(device.serverNode);
196
254
  }
197
255
  }
256
+ // Let any already‐queued microtasks run first
198
257
  await Promise.resolve();
258
+ // Wait for the cleanup to finish
199
259
  await wait(pause, 'destroyInstance start', true);
260
+ // Cleanup
200
261
  await this.cleanup('destroying instance...', false, timeout);
262
+ // Close servers mdns service
201
263
  this.log.info(`Dispose ${servers.length} MdnsService...`);
202
264
  for (const server of servers) {
203
265
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
204
266
  this.log.info(`Closed ${server.id} MdnsService`);
205
267
  }
268
+ // Let any already‐queued microtasks run first
206
269
  await Promise.resolve();
270
+ // Wait for the cleanup to finish
207
271
  await wait(pause, 'destroyInstance stop', true);
208
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
+ */
209
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
210
286
  this.emit('initialize_started');
287
+ // Set the restart mode
211
288
  if (hasParameter('service'))
212
289
  this.restartMode = 'service';
213
290
  if (hasParameter('docker'))
214
291
  this.restartMode = 'docker';
292
+ // Set the matterbridge home directory
215
293
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
216
294
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
217
295
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
296
+ // Set the matterbridge directory
218
297
  this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
219
298
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
220
299
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
221
300
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
222
301
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
302
+ // Set the matterbridge plugin directory
223
303
  this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
224
304
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
225
305
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
306
+ // Set the matterbridge cert directory
226
307
  this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
227
308
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
228
309
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
310
+ // Set the matterbridge root directory
229
311
  const { fileURLToPath } = await import('node:url');
230
312
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
231
313
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
232
314
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
315
+ // Setup the matter environment
233
316
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
234
317
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
235
- 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));
236
319
  this.environment.vars.set('runtime.signals', false);
237
320
  this.environment.vars.set('runtime.exitcode', false);
321
+ // Register process handlers
238
322
  this.registerProcessHandlers();
323
+ // Initialize nodeStorage and nodeContext
239
324
  try {
240
- this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
241
- 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 });
242
327
  this.log.debug('Creating node storage context for matterbridge');
243
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
244
331
  const keys = (await this.nodeStorage?.storage.keys());
245
332
  for (const key of keys) {
246
333
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
334
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
247
335
  await this.nodeStorage?.storage.get(key);
248
336
  }
249
337
  const storages = await this.nodeStorage.getStorageNames();
250
338
  for (const storage of storages) {
251
339
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
252
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
253
343
  const keys = (await nodeContext?.storage.keys());
254
344
  keys.forEach(async (key) => {
255
345
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
256
346
  await nodeContext?.get(key);
257
347
  });
258
348
  }
349
+ // Creating a backup of the node storage since it is not corrupted
259
350
  this.log.debug('Creating node storage backup...');
260
- 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'));
261
352
  this.log.debug('Created node storage backup');
262
353
  }
263
354
  catch (error) {
355
+ // Restoring the backup of the node storage since it is corrupted
264
356
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
265
357
  if (hasParameter('norestore')) {
266
358
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
267
359
  }
268
360
  else {
269
361
  this.log.notice(`The matterbridge storage is corrupted. Restoring it with backup...`);
270
- 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));
271
363
  this.log.notice(`The matterbridge storage has been restored with backup`);
272
364
  }
273
365
  }
274
366
  if (!this.nodeStorage || !this.nodeContext) {
275
367
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
276
368
  }
369
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
277
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)
278
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)
279
374
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
375
+ // Certificate management
280
376
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
281
377
  try {
282
378
  await fs.access(pairingFilePath, fs.constants.R_OK);
283
379
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
284
380
  const pairingFileJson = JSON.parse(pairingFileContent);
381
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
285
382
  if (isValidNumber(pairingFileJson.vendorId)) {
286
383
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
287
384
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -310,11 +407,13 @@ export class Matterbridge extends EventEmitter {
310
407
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
311
408
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
312
409
  }
410
+ // Override the passcode and discriminator if they are present in the pairing file
313
411
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
314
412
  this.passcode = pairingFileJson.passcode;
315
413
  this.discriminator = pairingFileJson.discriminator;
316
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.`);
317
415
  }
416
+ // Set the certification for matter.js if it is present in the pairing file
318
417
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
319
418
  const { hexToBuffer } = await import('./utils/hex.js');
320
419
  this.certification = {
@@ -329,49 +428,53 @@ export class Matterbridge extends EventEmitter {
329
428
  catch (error) {
330
429
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
331
430
  }
431
+ // Store the passcode, discriminator and port in the node context
332
432
  await this.nodeContext.set('matterport', this.port);
333
433
  await this.nodeContext.set('matterpasscode', this.passcode);
334
434
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
335
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)
336
437
  if (hasParameter('logger')) {
337
438
  const level = getParameter('logger');
338
439
  if (level === 'debug') {
339
- this.log.logLevel = "debug";
440
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
340
441
  }
341
442
  else if (level === 'info') {
342
- this.log.logLevel = "info";
443
+ this.log.logLevel = "info" /* LogLevel.INFO */;
343
444
  }
344
445
  else if (level === 'notice') {
345
- this.log.logLevel = "notice";
446
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
346
447
  }
347
448
  else if (level === 'warn') {
348
- this.log.logLevel = "warn";
449
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
349
450
  }
350
451
  else if (level === 'error') {
351
- this.log.logLevel = "error";
452
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
352
453
  }
353
454
  else if (level === 'fatal') {
354
- this.log.logLevel = "fatal";
455
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
355
456
  }
356
457
  else {
357
458
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
358
- this.log.logLevel = "info";
459
+ this.log.logLevel = "info" /* LogLevel.INFO */;
359
460
  }
360
461
  }
361
462
  else {
362
- 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 */);
363
464
  }
364
465
  this.frontend.logLevel = this.log.logLevel;
365
466
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
366
467
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
468
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
367
469
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
368
- 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);
369
471
  this.matterbridgeInformation.fileLogger = true;
370
472
  }
371
473
  this.log.notice('Matterbridge is starting...');
372
474
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
373
475
  if (this.profile !== undefined)
374
476
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
477
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
375
478
  if (hasParameter('matterlogger')) {
376
479
  const level = getParameter('matterlogger');
377
480
  if (level === 'debug') {
@@ -401,12 +504,14 @@ export class Matterbridge extends EventEmitter {
401
504
  Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
402
505
  }
403
506
  Logger.format = MatterLogFormat.ANSI;
507
+ // Create the logger for matter.js with file logging (context: matterFileLog)
404
508
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
405
509
  this.matterbridgeInformation.matterFileLogger = true;
406
510
  }
407
511
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterbridgeInformation.matterFileLogger);
408
512
  this.matterbridgeInformation.matterLoggerLevel = Logger.level;
409
513
  this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
514
+ // Log network interfaces
410
515
  const networkInterfaces = os.networkInterfaces();
411
516
  const availableAddresses = Object.entries(networkInterfaces);
412
517
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -419,6 +524,7 @@ export class Matterbridge extends EventEmitter {
419
524
  });
420
525
  }
421
526
  }
527
+ // Set the interface to use for matter server node mdnsInterface
422
528
  if (hasParameter('mdnsinterface')) {
423
529
  this.mdnsInterface = getParameter('mdnsinterface');
424
530
  }
@@ -427,6 +533,7 @@ export class Matterbridge extends EventEmitter {
427
533
  if (this.mdnsInterface === '')
428
534
  this.mdnsInterface = undefined;
429
535
  }
536
+ // Validate mdnsInterface
430
537
  if (this.mdnsInterface) {
431
538
  if (!availableInterfaces.includes(this.mdnsInterface)) {
432
539
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -439,6 +546,7 @@ export class Matterbridge extends EventEmitter {
439
546
  }
440
547
  if (this.mdnsInterface)
441
548
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
549
+ // Set the listeningAddressIpv4 for the matter commissioning server
442
550
  if (hasParameter('ipv4address')) {
443
551
  this.ipv4address = getParameter('ipv4address');
444
552
  }
@@ -447,6 +555,7 @@ export class Matterbridge extends EventEmitter {
447
555
  if (this.ipv4address === '')
448
556
  this.ipv4address = undefined;
449
557
  }
558
+ // Validate ipv4address
450
559
  if (this.ipv4address) {
451
560
  let isValid = false;
452
561
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -462,6 +571,7 @@ export class Matterbridge extends EventEmitter {
462
571
  await this.nodeContext.remove('matteripv4address');
463
572
  }
464
573
  }
574
+ // Set the listeningAddressIpv6 for the matter commissioning server
465
575
  if (hasParameter('ipv6address')) {
466
576
  this.ipv6address = getParameter('ipv6address');
467
577
  }
@@ -470,6 +580,7 @@ export class Matterbridge extends EventEmitter {
470
580
  if (this.ipv6address === '')
471
581
  this.ipv6address = undefined;
472
582
  }
583
+ // Validate ipv6address
473
584
  if (this.ipv6address) {
474
585
  let isValid = false;
475
586
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -478,6 +589,7 @@ export class Matterbridge extends EventEmitter {
478
589
  isValid = true;
479
590
  break;
480
591
  }
592
+ /* istanbul ignore next */
481
593
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
482
594
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
483
595
  isValid = true;
@@ -490,6 +602,7 @@ export class Matterbridge extends EventEmitter {
490
602
  await this.nodeContext.remove('matteripv6address');
491
603
  }
492
604
  }
605
+ // Initialize the virtual mode
493
606
  if (hasParameter('novirtual')) {
494
607
  this.matterbridgeInformation.virtualMode = 'disabled';
495
608
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -498,12 +611,17 @@ export class Matterbridge extends EventEmitter {
498
611
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
499
612
  }
500
613
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
614
+ // Initialize PluginManager
501
615
  this.plugins.logLevel = this.log.logLevel;
502
616
  await this.plugins.loadFromStorage();
617
+ // Initialize DeviceManager
503
618
  this.devices.logLevel = this.log.logLevel;
619
+ // Get the plugins from node storage and create the plugins node storage contexts
504
620
  for (const plugin of this.plugins) {
505
621
  const packageJson = await this.plugins.parse(plugin);
506
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
507
625
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
508
626
  try {
509
627
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -526,6 +644,7 @@ export class Matterbridge extends EventEmitter {
526
644
  await plugin.nodeContext.set('description', plugin.description);
527
645
  await plugin.nodeContext.set('author', plugin.author);
528
646
  }
647
+ // Log system info and create .matterbridge directory
529
648
  await this.logNodeAndSystemInfo();
530
649
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
531
650
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -533,6 +652,7 @@ export class Matterbridge extends EventEmitter {
533
652
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
534
653
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
535
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
536
656
  const minNodeVersion = 18;
537
657
  const nodeVersion = process.versions.node;
538
658
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -540,10 +660,18 @@ export class Matterbridge extends EventEmitter {
540
660
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
541
661
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
542
662
  }
663
+ // Parse command line
543
664
  await this.parseCommandLine();
665
+ // Emit the initialize_completed event
544
666
  this.emit('initialize_completed');
545
667
  this.initialized = true;
546
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
+ */
547
675
  async parseCommandLine() {
548
676
  if (hasParameter('help')) {
549
677
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -553,8 +681,8 @@ export class Matterbridge extends EventEmitter {
553
681
  --childbridge: start Matterbridge in childbridge mode
554
682
  --port [port]: start the commissioning server on the given port (default 5540)
555
683
  --mdnsinterface [name]: set the interface to use for the matter server mdnsInterface (default all interfaces)
556
- --ipv4address [address]: set the ipv4 interface address to use for the matter listener (default all interfaces)
557
- --ipv6address [address]: set the ipv6 interface address to use for the matter listener (default all interfaces)
684
+ --ipv4address [address]: set the ipv4 interface address to use for the matter listener (default all addresses)
685
+ --ipv6address [address]: set the ipv6 interface address to use for the matter listener (default all addresses)
558
686
  --frontend [port]: start the frontend on the given port (default 8283)
559
687
  --logger: set the matterbridge logger level: debug | info | notice | warn | error | fatal (default info)
560
688
  --filelogger enable the matterbridge file logger (matterbridge.log)
@@ -605,6 +733,19 @@ export class Matterbridge extends EventEmitter {
605
733
  }
606
734
  index++;
607
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
+ */
608
749
  this.shutdown = true;
609
750
  return;
610
751
  }
@@ -654,8 +795,10 @@ export class Matterbridge extends EventEmitter {
654
795
  this.shutdown = true;
655
796
  return;
656
797
  }
798
+ // Initialize frontend
657
799
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
658
800
  await this.frontend.start(getIntParameter('frontend'));
801
+ // Start the matter storage and create the matterbridge context
659
802
  try {
660
803
  await this.startMatterStorage();
661
804
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -671,18 +814,21 @@ export class Matterbridge extends EventEmitter {
671
814
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
672
815
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
673
816
  }
817
+ // Clear the matterbridge context if the reset parameter is set
674
818
  if (hasParameter('reset') && getParameter('reset') === undefined) {
675
819
  this.initialized = true;
676
820
  await this.shutdownProcessAndReset();
677
821
  this.shutdown = true;
678
822
  return;
679
823
  }
824
+ // Clear matterbridge plugin context if the reset parameter is set
680
825
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
681
826
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
682
827
  const plugin = this.plugins.get(getParameter('reset'));
683
828
  if (plugin) {
684
829
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
685
830
  if (!matterStorageManager) {
831
+ /* istanbul ignore next */
686
832
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
687
833
  }
688
834
  else {
@@ -701,35 +847,42 @@ export class Matterbridge extends EventEmitter {
701
847
  this.shutdown = true;
702
848
  return;
703
849
  }
850
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
704
851
  clearTimeout(this.checkUpdateTimeout);
705
852
  this.checkUpdateTimeout = setTimeout(async () => {
706
853
  const { checkUpdates } = await import('./update.js');
707
854
  checkUpdates(this);
708
855
  }, 30 * 1000).unref();
856
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
709
857
  clearInterval(this.checkUpdateInterval);
710
858
  this.checkUpdateInterval = setInterval(async () => {
711
859
  const { checkUpdates } = await import('./update.js');
712
860
  checkUpdates(this);
713
861
  }, 12 * 60 * 60 * 1000).unref();
862
+ // Start the matterbridge in mode test
714
863
  if (hasParameter('test')) {
715
864
  this.bridgeMode = 'bridge';
716
865
  return;
717
866
  }
867
+ // Start the matterbridge in mode controller
718
868
  if (hasParameter('controller')) {
719
869
  this.bridgeMode = 'controller';
720
870
  await this.startController();
721
871
  return;
722
872
  }
873
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
723
874
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
724
875
  this.log.info('Setting default matterbridge start mode to bridge');
725
876
  await this.nodeContext?.set('bridgeMode', 'bridge');
726
877
  }
878
+ // Start matterbridge in bridge mode
727
879
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
728
880
  this.bridgeMode = 'bridge';
729
881
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
730
882
  await this.startBridge();
731
883
  return;
732
884
  }
885
+ // Start matterbridge in childbridge mode
733
886
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
734
887
  this.bridgeMode = 'childbridge';
735
888
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -737,10 +890,22 @@ export class Matterbridge extends EventEmitter {
737
890
  return;
738
891
  }
739
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
+ */
740
903
  async startPlugins(wait = false, start = true) {
904
+ // Check, load and start the plugins
741
905
  for (const plugin of this.plugins) {
742
906
  plugin.configJson = await this.plugins.loadConfig(plugin);
743
907
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
908
+ // Check if the plugin is available
744
909
  if (!(await this.plugins.resolve(plugin.path))) {
745
910
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
746
911
  plugin.enabled = false;
@@ -760,10 +925,16 @@ export class Matterbridge extends EventEmitter {
760
925
  if (wait)
761
926
  await this.plugins.load(plugin, start, 'Matterbridge is starting');
762
927
  else
763
- this.plugins.load(plugin, start, 'Matterbridge is starting');
928
+ this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
764
929
  }
765
930
  this.frontend.wssSendRefreshRequired('plugins');
766
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
+ */
767
938
  registerProcessHandlers() {
768
939
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
769
940
  process.removeAllListeners('uncaughtException');
@@ -790,6 +961,9 @@ export class Matterbridge extends EventEmitter {
790
961
  };
791
962
  process.on('SIGTERM', this.sigtermHandler);
792
963
  }
964
+ /**
965
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
966
+ */
793
967
  deregisterProcessHandlers() {
794
968
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
795
969
  if (this.exceptionHandler)
@@ -806,12 +980,17 @@ export class Matterbridge extends EventEmitter {
806
980
  process.off('SIGTERM', this.sigtermHandler);
807
981
  this.sigtermHandler = undefined;
808
982
  }
983
+ /**
984
+ * Logs the node and system information.
985
+ */
809
986
  async logNodeAndSystemInfo() {
987
+ // IP address information
810
988
  const networkInterfaces = os.networkInterfaces();
811
989
  this.systemInformation.interfaceName = '';
812
990
  this.systemInformation.ipv4Address = '';
813
991
  this.systemInformation.ipv6Address = '';
814
992
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
993
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
815
994
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
816
995
  continue;
817
996
  if (!interfaceDetails) {
@@ -837,19 +1016,22 @@ export class Matterbridge extends EventEmitter {
837
1016
  break;
838
1017
  }
839
1018
  }
1019
+ // Node information
840
1020
  this.systemInformation.nodeVersion = process.versions.node;
841
1021
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
842
1022
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
843
1023
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1024
+ // Host system information
844
1025
  this.systemInformation.hostname = os.hostname();
845
1026
  this.systemInformation.user = os.userInfo().username;
846
- this.systemInformation.osType = os.type();
847
- this.systemInformation.osRelease = os.release();
848
- this.systemInformation.osPlatform = os.platform();
849
- this.systemInformation.osArch = os.arch();
850
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
851
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
852
- 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
853
1035
  this.log.debug('Host System Information:');
854
1036
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
855
1037
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -865,14 +1047,17 @@ export class Matterbridge extends EventEmitter {
865
1047
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
866
1048
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
867
1049
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1050
+ // Log directories
868
1051
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
869
1052
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
870
1053
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
871
1054
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
872
1055
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1056
+ // Global node_modules directory
873
1057
  if (this.nodeContext)
874
1058
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
875
1059
  if (this.globalModulesDirectory === '') {
1060
+ // First run of Matterbridge so the node storage is empty
876
1061
  this.log.debug(`Getting global node_modules directory...`);
877
1062
  try {
878
1063
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -885,6 +1070,7 @@ export class Matterbridge extends EventEmitter {
885
1070
  }
886
1071
  }
887
1072
  else {
1073
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
888
1074
  this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
889
1075
  try {
890
1076
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -896,58 +1082,86 @@ export class Matterbridge extends EventEmitter {
896
1082
  this.log.error(`Error checking global node_modules directory: ${error}`);
897
1083
  }
898
1084
  }
1085
+ // Matterbridge version
899
1086
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
900
1087
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
901
1088
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
902
1089
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1090
+ // Matterbridge latest version (will be set in the checkUpdate function)
903
1091
  if (this.nodeContext)
904
1092
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
905
1093
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1094
+ // Matterbridge dev version (will be set in the checkUpdate function)
906
1095
  if (this.nodeContext)
907
1096
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
908
1097
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1098
+ // Current working directory
909
1099
  const currentDir = process.cwd();
910
1100
  this.log.debug(`Current Working Directory: ${currentDir}`);
1101
+ // Command line arguments (excluding 'node' and the script name)
911
1102
  const cmdArgs = process.argv.slice(2).join(' ');
912
1103
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
913
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
+ */
914
1112
  createDestinationMatterLogger(fileLogger) {
915
- this.matterLog.logNameColor = '\x1b[34m';
1113
+ this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
916
1114
  if (fileLogger) {
917
- this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, this.matterLoggerFile);
1115
+ this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
918
1116
  }
919
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
920
1119
  const logger = text.slice(44, 44 + 20).trim();
921
1120
  const msg = text.slice(65);
922
1121
  this.matterLog.logName = logger;
923
1122
  switch (message.level) {
924
1123
  case MatterLogLevel.DEBUG:
925
- this.matterLog.log("debug", msg);
1124
+ this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
926
1125
  break;
927
1126
  case MatterLogLevel.INFO:
928
- this.matterLog.log("info", msg);
1127
+ this.matterLog.log("info" /* LogLevel.INFO */, msg);
929
1128
  break;
930
1129
  case MatterLogLevel.NOTICE:
931
- this.matterLog.log("notice", msg);
1130
+ this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
932
1131
  break;
933
1132
  case MatterLogLevel.WARN:
934
- this.matterLog.log("warn", msg);
1133
+ this.matterLog.log("warn" /* LogLevel.WARN */, msg);
935
1134
  break;
936
1135
  case MatterLogLevel.ERROR:
937
- this.matterLog.log("error", msg);
1136
+ this.matterLog.log("error" /* LogLevel.ERROR */, msg);
938
1137
  break;
939
1138
  case MatterLogLevel.FATAL:
940
- this.matterLog.log("fatal", msg);
1139
+ this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
941
1140
  break;
942
1141
  }
943
1142
  };
944
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
+ */
945
1149
  async restartProcess() {
946
1150
  await this.cleanup('restarting...', true);
947
1151
  }
1152
+ /**
1153
+ * Shut down the process (/api/shutdown).
1154
+ *
1155
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1156
+ */
948
1157
  async shutdownProcess() {
949
1158
  await this.cleanup('shutting down...', false);
950
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
+ */
951
1165
  async updateProcess() {
952
1166
  this.log.info('Updating matterbridge...');
953
1167
  try {
@@ -961,6 +1175,13 @@ export class Matterbridge extends EventEmitter {
961
1175
  this.frontend.wssSendRestartRequired();
962
1176
  await this.cleanup('updating...', false);
963
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
+ */
964
1185
  async unregisterAndShutdownProcess(timeout = 1000) {
965
1186
  this.log.info('Unregistering all devices and shutting down...');
966
1187
  for (const plugin of this.plugins.array()) {
@@ -972,46 +1193,71 @@ export class Matterbridge extends EventEmitter {
972
1193
  await this.removeAllBridgedEndpoints(plugin.name, 100);
973
1194
  }
974
1195
  this.log.debug('Waiting for the MessageExchange to finish...');
975
- await wait(timeout);
1196
+ await wait(timeout); // Wait for MessageExchange to finish
976
1197
  this.log.debug('Cleaning up and shutting down...');
977
1198
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
978
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
+ */
979
1205
  async shutdownProcessAndReset() {
980
1206
  await this.cleanup('shutting down with reset...', false);
981
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
+ */
982
1213
  async shutdownProcessAndFactoryReset() {
983
1214
  await this.cleanup('shutting down with factory reset...', false);
984
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
+ */
985
1225
  async cleanup(message, restart = false, timeout = 1000) {
986
1226
  if (this.initialized && !this.hasCleanupStarted) {
987
1227
  this.emit('cleanup_started');
988
1228
  this.hasCleanupStarted = true;
989
1229
  this.log.info(message);
1230
+ // Clear the start matter interval
990
1231
  if (this.startMatterInterval) {
991
1232
  clearInterval(this.startMatterInterval);
992
1233
  this.startMatterInterval = undefined;
993
1234
  this.log.debug('Start matter interval cleared');
994
1235
  }
1236
+ // Clear the check update timeout
995
1237
  if (this.checkUpdateTimeout) {
996
1238
  clearTimeout(this.checkUpdateTimeout);
997
1239
  this.checkUpdateTimeout = undefined;
998
1240
  this.log.debug('Check update timeout cleared');
999
1241
  }
1242
+ // Clear the check update interval
1000
1243
  if (this.checkUpdateInterval) {
1001
1244
  clearInterval(this.checkUpdateInterval);
1002
1245
  this.checkUpdateInterval = undefined;
1003
1246
  this.log.debug('Check update interval cleared');
1004
1247
  }
1248
+ // Clear the configure timeout
1005
1249
  if (this.configureTimeout) {
1006
1250
  clearTimeout(this.configureTimeout);
1007
1251
  this.configureTimeout = undefined;
1008
1252
  this.log.debug('Matterbridge configure timeout cleared');
1009
1253
  }
1254
+ // Clear the reachability timeout
1010
1255
  if (this.reachabilityTimeout) {
1011
1256
  clearTimeout(this.reachabilityTimeout);
1012
1257
  this.reachabilityTimeout = undefined;
1013
1258
  this.log.debug('Matterbridge reachability timeout cleared');
1014
1259
  }
1260
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
1015
1261
  for (const plugin of this.plugins) {
1016
1262
  if (!plugin.enabled || plugin.error)
1017
1263
  continue;
@@ -1022,9 +1268,12 @@ export class Matterbridge extends EventEmitter {
1022
1268
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1023
1269
  }
1024
1270
  }
1271
+ // Stop matter server nodes
1025
1272
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1026
- this.log.debug('Waiting for the MessageExchange to finish...');
1027
- await wait(timeout, 'Waiting for the MessageExchange to finish...', true);
1273
+ if (timeout > 0) {
1274
+ this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
1275
+ await wait(timeout, `Waiting ${timeout}ms for the MessageExchange to finish...`, true);
1276
+ }
1028
1277
  if (this.bridgeMode === 'bridge') {
1029
1278
  if (this.serverNode) {
1030
1279
  await this.stopServerNode(this.serverNode);
@@ -1046,6 +1295,7 @@ export class Matterbridge extends EventEmitter {
1046
1295
  }
1047
1296
  }
1048
1297
  this.log.notice('Stopped matter server nodes');
1298
+ // Matter commisioning reset
1049
1299
  if (message === 'shutting down with reset...') {
1050
1300
  this.log.info('Resetting Matterbridge commissioning information...');
1051
1301
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1055,6 +1305,7 @@ export class Matterbridge extends EventEmitter {
1055
1305
  await this.matterbridgeContext?.clearAll();
1056
1306
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1057
1307
  }
1308
+ // Unregister all devices
1058
1309
  if (message === 'unregistered all devices and shutting down...') {
1059
1310
  if (this.bridgeMode === 'bridge') {
1060
1311
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1072,12 +1323,29 @@ export class Matterbridge extends EventEmitter {
1072
1323
  }
1073
1324
  this.log.info('Matter storage reset done!');
1074
1325
  }
1326
+ // Stop matter storage
1075
1327
  await this.stopMatterStorage();
1328
+ // Stop the frontend
1076
1329
  await this.frontend.stop();
1330
+ // Close the matterbridge node storage and context
1077
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)
1078
1345
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1079
1346
  await this.nodeContext.close();
1080
1347
  this.nodeContext = undefined;
1348
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1081
1349
  for (const plugin of this.plugins) {
1082
1350
  if (plugin.nodeContext) {
1083
1351
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1094,41 +1362,47 @@ export class Matterbridge extends EventEmitter {
1094
1362
  }
1095
1363
  this.plugins.clear();
1096
1364
  this.devices.clear();
1365
+ // Factory reset
1097
1366
  if (message === 'shutting down with factory reset...') {
1098
1367
  try {
1099
- 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);
1100
1370
  this.log.info(`Removing matter storage directory: ${dir}`);
1101
1371
  await fs.rm(dir, { recursive: true });
1102
- const backup = path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup');
1372
+ const backup = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup');
1103
1373
  this.log.info(`Removing matter storage backup directory: ${backup}`);
1104
1374
  await fs.rm(backup, { recursive: true });
1105
1375
  }
1106
1376
  catch (error) {
1377
+ // istanbul ignore next if
1107
1378
  if (error instanceof Error && error.code !== 'ENOENT') {
1108
1379
  this.log.error(`Error removing matter storage directory: ${error}`);
1109
1380
  }
1110
1381
  }
1111
1382
  try {
1112
- 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);
1113
1385
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1114
1386
  await fs.rm(dir, { recursive: true });
1115
- const backup = path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup');
1387
+ const backup = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup');
1116
1388
  this.log.info(`Removing matterbridge storage backup directory: ${backup}`);
1117
1389
  await fs.rm(backup, { recursive: true });
1118
1390
  }
1119
1391
  catch (error) {
1392
+ // istanbul ignore next if
1120
1393
  if (error instanceof Error && error.code !== 'ENOENT') {
1121
1394
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1122
1395
  }
1123
1396
  }
1124
1397
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1125
1398
  }
1399
+ // Deregisters the process handlers
1126
1400
  this.deregisterProcessHandlers();
1127
1401
  if (restart) {
1128
1402
  if (message === 'updating...') {
1129
1403
  this.log.info('Cleanup completed. Updating...');
1130
1404
  Matterbridge.instance = undefined;
1131
- this.emit('update');
1405
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1132
1406
  }
1133
1407
  else if (message === 'restarting...') {
1134
1408
  this.log.info('Cleanup completed. Restarting...');
@@ -1146,39 +1420,20 @@ export class Matterbridge extends EventEmitter {
1146
1420
  this.emit('cleanup_completed');
1147
1421
  }
1148
1422
  else {
1149
- this.log.debug('Cleanup already started...');
1150
- }
1151
- }
1152
- async createDeviceServerNode(plugin, device) {
1153
- if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1154
- this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
1155
- const context = await this.createServerNodeContext(device.deviceName.replace(/[ .]/g, ''), device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
1156
- device.serverNode = await this.createServerNode(context, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1157
- this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node...`);
1158
- await device.serverNode.add(device);
1159
- this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1160
- }
1161
- }
1162
- async createAccessoryPlugin(plugin, device) {
1163
- if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1164
- plugin.locked = true;
1165
- plugin.device = device;
1166
- plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
1167
- plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1168
- this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node`);
1169
- await plugin.serverNode.add(device);
1170
- }
1171
- }
1172
- async createDynamicPlugin(plugin) {
1173
- if (!plugin.locked) {
1174
- plugin.locked = true;
1175
- plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, plugin.description);
1176
- plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1177
- plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
1178
- await plugin.serverNode.add(plugin.aggregatorNode);
1423
+ if (!this.initialized)
1424
+ this.log.debug('Cleanup with instance not initialized...');
1425
+ if (this.hasCleanupStarted)
1426
+ this.log.debug('Cleanup already started...');
1179
1427
  }
1180
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
+ */
1181
1435
  async startBridge() {
1436
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1182
1437
  if (!this.matterStorageManager)
1183
1438
  throw new Error('No storage manager initialized');
1184
1439
  if (!this.matterbridgeContext)
@@ -1217,13 +1472,16 @@ export class Matterbridge extends EventEmitter {
1217
1472
  clearInterval(this.startMatterInterval);
1218
1473
  this.startMatterInterval = undefined;
1219
1474
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1220
- 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'
1221
1478
  for (const device of this.devices.array()) {
1222
1479
  if (device.mode === 'server' && device.serverNode) {
1223
1480
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1224
- this.startServerNode(device.serverNode);
1481
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1225
1482
  }
1226
1483
  }
1484
+ // Configure the plugins
1227
1485
  this.configureTimeout = setTimeout(async () => {
1228
1486
  for (const plugin of this.plugins.array()) {
1229
1487
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1241,28 +1499,40 @@ export class Matterbridge extends EventEmitter {
1241
1499
  }
1242
1500
  this.frontend.wssSendRefreshRequired('plugins');
1243
1501
  }, 30 * 1000).unref();
1502
+ // Setting reachability to true
1244
1503
  this.reachabilityTimeout = setTimeout(() => {
1245
1504
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1246
1505
  if (this.aggregatorNode)
1247
1506
  this.setAggregatorReachability(this.aggregatorNode, true);
1248
1507
  }, 60 * 1000).unref();
1508
+ // Logger.get('LogServerNode').info(this.serverNode);
1249
1509
  this.emit('bridge_started');
1250
1510
  this.log.notice('Matterbridge bridge started successfully');
1251
1511
  this.frontend.wssSendRefreshRequired('settings');
1252
1512
  this.frontend.wssSendRefreshRequired('plugins');
1253
1513
  }, this.startMatterIntervalMs);
1254
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
+ */
1255
1522
  async startChildbridge(delay = 1000) {
1256
1523
  if (!this.matterStorageManager)
1257
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
1258
1526
  this.log.debug('Loading all plugins in childbridge mode...');
1259
1527
  await this.startPlugins(true, false);
1528
+ // Create server nodes for DynamicPlatform plugins and start all plugins in the background
1260
1529
  this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
1261
1530
  for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
1262
1531
  if (plugin.type === 'DynamicPlatform')
1263
1532
  await this.createDynamicPlugin(plugin);
1264
- this.plugins.start(plugin, 'Matterbridge is starting');
1533
+ this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
1265
1534
  }
1535
+ // Start the Matterbridge in childbridge mode when all plugins are loaded and started
1266
1536
  this.log.debug('Starting start matter interval in childbridge mode...');
1267
1537
  let failCount = 0;
1268
1538
  this.startMatterInterval = setInterval(async () => {
@@ -1296,8 +1566,9 @@ export class Matterbridge extends EventEmitter {
1296
1566
  clearInterval(this.startMatterInterval);
1297
1567
  this.startMatterInterval = undefined;
1298
1568
  if (delay > 0)
1299
- await wait(delay);
1569
+ await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1300
1570
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1571
+ // Configure the plugins
1301
1572
  this.configureTimeout = setTimeout(async () => {
1302
1573
  for (const plugin of this.plugins.array()) {
1303
1574
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1322,6 +1593,7 @@ export class Matterbridge extends EventEmitter {
1322
1593
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
1323
1594
  continue;
1324
1595
  }
1596
+ // istanbul ignore next if cause is just a safety check
1325
1597
  if (!plugin.serverNode) {
1326
1598
  this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1327
1599
  continue;
@@ -1334,28 +1606,252 @@ export class Matterbridge extends EventEmitter {
1334
1606
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1335
1607
  continue;
1336
1608
  }
1337
- 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
1338
1612
  plugin.reachabilityTimeout = setTimeout(() => {
1339
1613
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
1340
1614
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1341
1615
  this.setAggregatorReachability(plugin.aggregatorNode, true);
1342
1616
  }, 60 * 1000).unref();
1343
1617
  }
1618
+ // Start the Matter server node of single devices in mode 'server'
1344
1619
  for (const device of this.devices.array()) {
1345
1620
  if (device.mode === 'server' && device.serverNode) {
1346
1621
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1347
- this.startServerNode(device.serverNode);
1622
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1348
1623
  }
1349
1624
  }
1625
+ // Logger.get('LogServerNode').info(this.serverNode);
1350
1626
  this.emit('childbridge_started');
1351
1627
  this.log.notice('Matterbridge childbridge started successfully');
1352
1628
  this.frontend.wssSendRefreshRequired('settings');
1353
1629
  this.frontend.wssSendRefreshRequired('plugins');
1354
1630
  }, this.startMatterIntervalMs);
1355
1631
  }
1632
+ /**
1633
+ * Starts the Matterbridge controller.
1634
+ *
1635
+ * @private
1636
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1637
+ */
1356
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
+ */
1357
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
+ */
1358
1853
  async startMatterStorage() {
1854
+ // Setup Matter storage
1359
1855
  this.log.info(`Starting matter node storage...`);
1360
1856
  this.matterStorageService = this.environment.get(StorageService);
1361
1857
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1363,8 +1859,17 @@ export class Matterbridge extends EventEmitter {
1363
1859
  this.log.info('Matter node storage manager "Matterbridge" created');
1364
1860
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1365
1861
  this.log.info('Matter node storage started');
1366
- 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'));
1367
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
+ */
1368
1873
  async backupMatterStorage(storageName, backupName) {
1369
1874
  this.log.info('Creating matter node storage backup...');
1370
1875
  try {
@@ -1375,6 +1880,11 @@ export class Matterbridge extends EventEmitter {
1375
1880
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1376
1881
  }
1377
1882
  }
1883
+ /**
1884
+ * Stops the matter storage.
1885
+ *
1886
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1887
+ */
1378
1888
  async stopMatterStorage() {
1379
1889
  this.log.info('Closing matter node storage...');
1380
1890
  await this.matterStorageManager?.close();
@@ -1383,6 +1893,20 @@ export class Matterbridge extends EventEmitter {
1383
1893
  this.matterbridgeContext = undefined;
1384
1894
  this.log.info('Matter node storage closed');
1385
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
+ */
1386
1910
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1387
1911
  const { randomBytes } = await import('node:crypto');
1388
1912
  if (!this.matterStorageService)
@@ -1422,6 +1946,15 @@ export class Matterbridge extends EventEmitter {
1422
1946
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1423
1947
  return storageContext;
1424
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
+ */
1425
1958
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1426
1959
  const storeId = await storageContext.get('storeId');
1427
1960
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1431,24 +1964,37 @@ export class Matterbridge extends EventEmitter {
1431
1964
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1432
1965
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1433
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
+ */
1434
1970
  const serverNode = await ServerNode.create({
1971
+ // Required: Give the Node a unique ID which is used to store the state of this node
1435
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
1436
1975
  network: {
1437
1976
  listeningAddressIpv4: this.ipv4address,
1438
1977
  listeningAddressIpv6: this.ipv6address,
1439
1978
  port,
1440
1979
  },
1980
+ // Provide the certificate for the device
1441
1981
  operationalCredentials: {
1442
1982
  certification: this.certification,
1443
1983
  },
1984
+ // Provide Commissioning relevant settings
1985
+ // Optional for development/testing purposes
1444
1986
  commissioning: {
1445
1987
  passcode,
1446
1988
  discriminator,
1447
1989
  },
1990
+ // Provide Node announcement settings
1991
+ // Optional: If Ommitted some development defaults are used
1448
1992
  productDescription: {
1449
1993
  name: await storageContext.get('deviceName'),
1450
1994
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1451
1995
  },
1996
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
1997
+ // Optional: If Omitted some development defaults are used
1452
1998
  basicInformation: {
1453
1999
  vendorId: VendorId(await storageContext.get('vendorId')),
1454
2000
  vendorName: await storageContext.get('vendorName'),
@@ -1465,17 +2011,23 @@ export class Matterbridge extends EventEmitter {
1465
2011
  reachable: true,
1466
2012
  },
1467
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
+ */
1468
2018
  serverNode.lifecycle.commissioned.on(() => {
1469
2019
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1470
2020
  this.advertisingNodes.delete(storeId);
1471
2021
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1472
2022
  });
2023
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1473
2024
  serverNode.lifecycle.decommissioned.on(() => {
1474
2025
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1475
2026
  this.advertisingNodes.delete(storeId);
1476
2027
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1477
2028
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1478
2029
  });
2030
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1479
2031
  serverNode.lifecycle.online.on(async () => {
1480
2032
  this.log.notice(`Server node for ${storeId} is online`);
1481
2033
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1486,13 +2038,16 @@ export class Matterbridge extends EventEmitter {
1486
2038
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1487
2039
  }
1488
2040
  else {
2041
+ // istanbul ignore next
1489
2042
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2043
+ // istanbul ignore next
1490
2044
  this.advertisingNodes.delete(storeId);
1491
2045
  }
1492
2046
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1493
2047
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1494
2048
  this.emit('online', storeId);
1495
2049
  });
2050
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1496
2051
  serverNode.lifecycle.offline.on(() => {
1497
2052
  this.log.notice(`Server node for ${storeId} is offline`);
1498
2053
  this.advertisingNodes.delete(storeId);
@@ -1500,11 +2055,15 @@ export class Matterbridge extends EventEmitter {
1500
2055
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1501
2056
  this.emit('offline', storeId);
1502
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
+ */
1503
2062
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1504
2063
  let action = '';
1505
2064
  switch (fabricAction) {
1506
2065
  case FabricAction.Added:
1507
- this.advertisingNodes.delete(storeId);
2066
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1508
2067
  action = 'added';
1509
2068
  break;
1510
2069
  case FabricAction.Removed:
@@ -1517,14 +2076,22 @@ export class Matterbridge extends EventEmitter {
1517
2076
  this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1518
2077
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1519
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
+ */
1520
2083
  serverNode.events.sessions.opened.on((session) => {
1521
2084
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1522
2085
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1523
2086
  });
2087
+ /**
2088
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2089
+ */
1524
2090
  serverNode.events.sessions.closed.on((session) => {
1525
2091
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1526
2092
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1527
2093
  });
2094
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1528
2095
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1529
2096
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1530
2097
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
@@ -1532,6 +2099,12 @@ export class Matterbridge extends EventEmitter {
1532
2099
  this.log.info(`Created server node for ${storeId}`);
1533
2100
  return serverNode;
1534
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
+ */
1535
2108
  getServerNodeData(serverNode) {
1536
2109
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1537
2110
  return {
@@ -1548,12 +2121,25 @@ export class Matterbridge extends EventEmitter {
1548
2121
  serialNumber: serverNode.state.basicInformation.serialNumber,
1549
2122
  };
1550
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
+ */
1551
2130
  async startServerNode(matterServerNode) {
1552
2131
  if (!matterServerNode)
1553
2132
  return;
1554
2133
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1555
2134
  await matterServerNode.start();
1556
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
+ */
1557
2143
  async stopServerNode(matterServerNode, timeout = 30000) {
1558
2144
  if (!matterServerNode)
1559
2145
  return;
@@ -1566,13 +2152,80 @@ export class Matterbridge extends EventEmitter {
1566
2152
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1567
2153
  }
1568
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
+ */
1569
2161
  async createAggregatorNode(storageContext) {
1570
2162
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1571
2163
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1572
2164
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1573
2165
  return aggregatorNode;
1574
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
+ */
2174
+ async createAccessoryPlugin(plugin, device) {
2175
+ if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
2176
+ plugin.locked = true;
2177
+ plugin.device = device;
2178
+ this.log.debug(`Creating accessory plugin ${plg}${plugin.name}${db} server node...`);
2179
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
2180
+ plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
2181
+ this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node...`);
2182
+ await plugin.serverNode.add(device);
2183
+ }
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
+ */
2191
+ async createDynamicPlugin(plugin) {
2192
+ if (!plugin.locked) {
2193
+ plugin.locked = true;
2194
+ this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} server node...`);
2195
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, plugin.description);
2196
+ plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
2197
+ this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
2198
+ plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
2199
+ this.log.debug(`Adding dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
2200
+ await plugin.serverNode.add(plugin.aggregatorNode);
2201
+ }
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
+ */
2210
+ async createDeviceServerNode(plugin, device) {
2211
+ if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
2212
+ this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
2213
+ const context = await this.createServerNodeContext(device.deviceName.replace(/[ .]/g, ''), device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
2214
+ device.serverNode = await this.createServerNode(context, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
2215
+ this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node...`);
2216
+ await device.serverNode.add(device);
2217
+ this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
2218
+ }
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
+ */
1575
2227
  async addBridgedEndpoint(pluginName, device) {
2228
+ // Check if the plugin is registered
1576
2229
  const plugin = this.plugins.get(pluginName);
1577
2230
  if (!plugin) {
1578
2231
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1592,6 +2245,7 @@ export class Matterbridge extends EventEmitter {
1592
2245
  }
1593
2246
  else if (this.bridgeMode === 'bridge') {
1594
2247
  if (device.mode === 'matter') {
2248
+ // Register and add the device to the matterbridge server node
1595
2249
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1596
2250
  if (!this.serverNode) {
1597
2251
  this.log.error('Server node not found for Matterbridge');
@@ -1608,6 +2262,7 @@ export class Matterbridge extends EventEmitter {
1608
2262
  }
1609
2263
  }
1610
2264
  else {
2265
+ // Register and add the device to the matterbridge aggregator node
1611
2266
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1612
2267
  if (!this.aggregatorNode) {
1613
2268
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1625,6 +2280,7 @@ export class Matterbridge extends EventEmitter {
1625
2280
  }
1626
2281
  }
1627
2282
  else if (this.bridgeMode === 'childbridge') {
2283
+ // Register and add the device to the plugin server node
1628
2284
  if (plugin.type === 'AccessoryPlatform') {
1629
2285
  try {
1630
2286
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1648,10 +2304,12 @@ export class Matterbridge extends EventEmitter {
1648
2304
  return;
1649
2305
  }
1650
2306
  }
2307
+ // Register and add the device to the plugin aggregator node
1651
2308
  if (plugin.type === 'DynamicPlatform') {
1652
2309
  try {
1653
2310
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1654
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
1655
2313
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1656
2314
  if (!plugin.aggregatorNode) {
1657
2315
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1672,17 +2330,28 @@ export class Matterbridge extends EventEmitter {
1672
2330
  }
1673
2331
  if (plugin.registeredDevices !== undefined)
1674
2332
  plugin.registeredDevices++;
2333
+ // Add the device to the DeviceManager
1675
2334
  this.devices.set(device);
2335
+ // Subscribe to the attributes changed event
1676
2336
  await this.subscribeAttributeChanged(plugin, device);
1677
2337
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1678
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
+ */
1679
2346
  async removeBridgedEndpoint(pluginName, device) {
1680
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
1681
2349
  const plugin = this.plugins.get(pluginName);
1682
2350
  if (!plugin) {
1683
2351
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1684
2352
  return;
1685
2353
  }
2354
+ // Register and add the device to the matterbridge aggregator node
1686
2355
  if (this.bridgeMode === 'bridge') {
1687
2356
  if (!this.aggregatorNode) {
1688
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`);
@@ -1695,6 +2364,7 @@ export class Matterbridge extends EventEmitter {
1695
2364
  }
1696
2365
  else if (this.bridgeMode === 'childbridge') {
1697
2366
  if (plugin.type === 'AccessoryPlatform') {
2367
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1698
2368
  }
1699
2369
  else if (plugin.type === 'DynamicPlatform') {
1700
2370
  if (!plugin.aggregatorNode) {
@@ -1707,8 +2377,21 @@ export class Matterbridge extends EventEmitter {
1707
2377
  if (plugin.registeredDevices !== undefined)
1708
2378
  plugin.registeredDevices--;
1709
2379
  }
2380
+ // Remove the device from the DeviceManager
1710
2381
  this.devices.remove(device);
1711
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
+ */
1712
2395
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1713
2396
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1714
2397
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1719,13 +2402,24 @@ export class Matterbridge extends EventEmitter {
1719
2402
  if (delay > 0)
1720
2403
  await wait(2000);
1721
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
+ */
1722
2414
  async subscribeAttributeChanged(plugin, device) {
1723
2415
  if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
1724
2416
  return;
1725
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
1726
2419
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
1727
2420
  plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
1728
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
1729
2423
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
1730
2424
  });
1731
2425
  }
@@ -1773,6 +2467,7 @@ export class Matterbridge extends EventEmitter {
1773
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...`);
1774
2468
  await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1775
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
1776
2471
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
1777
2472
  });
1778
2473
  }
@@ -1782,6 +2477,7 @@ export class Matterbridge extends EventEmitter {
1782
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...`);
1783
2478
  await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
1784
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
1785
2481
  this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
1786
2482
  });
1787
2483
  }
@@ -1789,6 +2485,12 @@ export class Matterbridge extends EventEmitter {
1789
2485
  }
1790
2486
  }
1791
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
+ */
1792
2494
  sanitizeFabricInformations(fabricInfo) {
1793
2495
  return fabricInfo.map((info) => {
1794
2496
  return {
@@ -1802,6 +2504,12 @@ export class Matterbridge extends EventEmitter {
1802
2504
  };
1803
2505
  });
1804
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
+ */
1805
2513
  sanitizeSessionInformation(sessions) {
1806
2514
  return sessions
1807
2515
  .filter((session) => session.isPeerActive)
@@ -1828,7 +2536,21 @@ export class Matterbridge extends EventEmitter {
1828
2536
  };
1829
2537
  });
1830
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
1831
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
+ */
1832
2554
  }
1833
2555
  getVendorIdName = (vendorId) => {
1834
2556
  if (!vendorId)
@@ -1868,10 +2590,11 @@ export class Matterbridge extends EventEmitter {
1868
2590
  case 0x1488:
1869
2591
  vendorName = '(ShortcutLabsFlic)';
1870
2592
  break;
1871
- case 65521:
2593
+ case 65521: // 0xFFF1
1872
2594
  vendorName = '(MatterTest)';
1873
2595
  break;
1874
2596
  }
1875
2597
  return vendorName;
1876
2598
  };
1877
2599
  }
2600
+ //# sourceMappingURL=matterbridge.js.map