matterbridge 3.2.7-dev-20250913-9d0d095 → 3.2.7

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 (279) hide show
  1. package/CHANGELOG.md +1 -1
  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 +315 -0
  123. package/dist/frontend.d.ts.map +1 -0
  124. package/dist/frontend.js +456 -25
  125. package/dist/frontend.js.map +1 -0
  126. package/dist/globalMatterbridge.d.ts +59 -0
  127. package/dist/globalMatterbridge.d.ts.map +1 -0
  128. package/dist/globalMatterbridge.js +47 -0
  129. package/dist/globalMatterbridge.js.map +1 -0
  130. package/dist/helpers.d.ts +48 -0
  131. package/dist/helpers.d.ts.map +1 -0
  132. package/dist/helpers.js +53 -0
  133. package/dist/helpers.js.map +1 -0
  134. package/dist/index.d.ts +33 -0
  135. package/dist/index.d.ts.map +1 -0
  136. package/dist/index.js +30 -1
  137. package/dist/index.js.map +1 -0
  138. package/dist/logger/export.d.ts +2 -0
  139. package/dist/logger/export.d.ts.map +1 -0
  140. package/dist/logger/export.js +1 -0
  141. package/dist/logger/export.js.map +1 -0
  142. package/dist/matter/behaviors.d.ts +2 -0
  143. package/dist/matter/behaviors.d.ts.map +1 -0
  144. package/dist/matter/behaviors.js +2 -0
  145. package/dist/matter/behaviors.js.map +1 -0
  146. package/dist/matter/clusters.d.ts +2 -0
  147. package/dist/matter/clusters.d.ts.map +1 -0
  148. package/dist/matter/clusters.js +2 -0
  149. package/dist/matter/clusters.js.map +1 -0
  150. package/dist/matter/devices.d.ts +2 -0
  151. package/dist/matter/devices.d.ts.map +1 -0
  152. package/dist/matter/devices.js +2 -0
  153. package/dist/matter/devices.js.map +1 -0
  154. package/dist/matter/endpoints.d.ts +2 -0
  155. package/dist/matter/endpoints.d.ts.map +1 -0
  156. package/dist/matter/endpoints.js +2 -0
  157. package/dist/matter/endpoints.js.map +1 -0
  158. package/dist/matter/export.d.ts +5 -0
  159. package/dist/matter/export.d.ts.map +1 -0
  160. package/dist/matter/export.js +3 -0
  161. package/dist/matter/export.js.map +1 -0
  162. package/dist/matter/types.d.ts +3 -0
  163. package/dist/matter/types.d.ts.map +1 -0
  164. package/dist/matter/types.js +3 -0
  165. package/dist/matter/types.js.map +1 -0
  166. package/dist/matterbridge.d.ts +465 -0
  167. package/dist/matterbridge.d.ts.map +1 -0
  168. package/dist/matterbridge.js +789 -51
  169. package/dist/matterbridge.js.map +1 -0
  170. package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
  171. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  172. package/dist/matterbridgeAccessoryPlatform.js +36 -0
  173. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  174. package/dist/matterbridgeBehaviors.d.ts +1747 -0
  175. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  176. package/dist/matterbridgeBehaviors.js +65 -5
  177. package/dist/matterbridgeBehaviors.js.map +1 -0
  178. package/dist/matterbridgeDeviceTypes.d.ts +761 -0
  179. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  180. package/dist/matterbridgeDeviceTypes.js +630 -17
  181. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  182. package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
  183. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  184. package/dist/matterbridgeDynamicPlatform.js +36 -0
  185. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  186. package/dist/matterbridgeEndpoint.d.ts +1515 -0
  187. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  188. package/dist/matterbridgeEndpoint.js +1379 -55
  189. package/dist/matterbridgeEndpoint.js.map +1 -0
  190. package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
  191. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  192. package/dist/matterbridgeEndpointHelpers.js +345 -12
  193. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  194. package/dist/matterbridgePlatform.d.ts +380 -0
  195. package/dist/matterbridgePlatform.d.ts.map +1 -0
  196. package/dist/matterbridgePlatform.js +304 -0
  197. package/dist/matterbridgePlatform.js.map +1 -0
  198. package/dist/matterbridgeTypes.d.ts +204 -0
  199. package/dist/matterbridgeTypes.d.ts.map +1 -0
  200. package/dist/matterbridgeTypes.js +25 -0
  201. package/dist/matterbridgeTypes.js.map +1 -0
  202. package/dist/pluginManager.d.ts +270 -0
  203. package/dist/pluginManager.d.ts.map +1 -0
  204. package/dist/pluginManager.js +249 -3
  205. package/dist/pluginManager.js.map +1 -0
  206. package/dist/shelly.d.ts +174 -0
  207. package/dist/shelly.d.ts.map +1 -0
  208. package/dist/shelly.js +168 -7
  209. package/dist/shelly.js.map +1 -0
  210. package/dist/storage/export.d.ts +2 -0
  211. package/dist/storage/export.d.ts.map +1 -0
  212. package/dist/storage/export.js +1 -0
  213. package/dist/storage/export.js.map +1 -0
  214. package/dist/update.d.ts +75 -0
  215. package/dist/update.d.ts.map +1 -0
  216. package/dist/update.js +69 -0
  217. package/dist/update.js.map +1 -0
  218. package/dist/utils/colorUtils.d.ts +99 -0
  219. package/dist/utils/colorUtils.d.ts.map +1 -0
  220. package/dist/utils/colorUtils.js +97 -2
  221. package/dist/utils/colorUtils.js.map +1 -0
  222. package/dist/utils/commandLine.d.ts +59 -0
  223. package/dist/utils/commandLine.d.ts.map +1 -0
  224. package/dist/utils/commandLine.js +54 -0
  225. package/dist/utils/commandLine.js.map +1 -0
  226. package/dist/utils/copyDirectory.d.ts +33 -0
  227. package/dist/utils/copyDirectory.d.ts.map +1 -0
  228. package/dist/utils/copyDirectory.js +38 -1
  229. package/dist/utils/copyDirectory.js.map +1 -0
  230. package/dist/utils/createDirectory.d.ts +34 -0
  231. package/dist/utils/createDirectory.d.ts.map +1 -0
  232. package/dist/utils/createDirectory.js +33 -0
  233. package/dist/utils/createDirectory.js.map +1 -0
  234. package/dist/utils/createZip.d.ts +39 -0
  235. package/dist/utils/createZip.d.ts.map +1 -0
  236. package/dist/utils/createZip.js +47 -2
  237. package/dist/utils/createZip.js.map +1 -0
  238. package/dist/utils/deepCopy.d.ts +32 -0
  239. package/dist/utils/deepCopy.d.ts.map +1 -0
  240. package/dist/utils/deepCopy.js +39 -0
  241. package/dist/utils/deepCopy.js.map +1 -0
  242. package/dist/utils/deepEqual.d.ts +54 -0
  243. package/dist/utils/deepEqual.d.ts.map +1 -0
  244. package/dist/utils/deepEqual.js +72 -1
  245. package/dist/utils/deepEqual.js.map +1 -0
  246. package/dist/utils/error.d.ts +44 -0
  247. package/dist/utils/error.d.ts.map +1 -0
  248. package/dist/utils/error.js +41 -0
  249. package/dist/utils/error.js.map +1 -0
  250. package/dist/utils/export.d.ts +13 -0
  251. package/dist/utils/export.d.ts.map +1 -0
  252. package/dist/utils/export.js +1 -0
  253. package/dist/utils/export.js.map +1 -0
  254. package/dist/utils/hex.d.ts +89 -0
  255. package/dist/utils/hex.d.ts.map +1 -0
  256. package/dist/utils/hex.js +124 -0
  257. package/dist/utils/hex.js.map +1 -0
  258. package/dist/utils/isvalid.d.ts +103 -0
  259. package/dist/utils/isvalid.d.ts.map +1 -0
  260. package/dist/utils/isvalid.js +101 -0
  261. package/dist/utils/isvalid.js.map +1 -0
  262. package/dist/utils/jestHelpers.d.ts +137 -0
  263. package/dist/utils/jestHelpers.d.ts.map +1 -0
  264. package/dist/utils/jestHelpers.js +153 -3
  265. package/dist/utils/jestHelpers.js.map +1 -0
  266. package/dist/utils/network.d.ts +84 -0
  267. package/dist/utils/network.d.ts.map +1 -0
  268. package/dist/utils/network.js +91 -5
  269. package/dist/utils/network.js.map +1 -0
  270. package/dist/utils/spawn.d.ts +33 -0
  271. package/dist/utils/spawn.d.ts.map +1 -0
  272. package/dist/utils/spawn.js +40 -0
  273. package/dist/utils/spawn.js.map +1 -0
  274. package/dist/utils/wait.d.ts +54 -0
  275. package/dist/utils/wait.d.ts.map +1 -0
  276. package/dist/utils/wait.js +60 -8
  277. package/dist/utils/wait.js.map +1 -0
  278. package/npm-shrinkwrap.json +2 -2
  279. package/package.json +2 -1
@@ -1,15 +1,43 @@
1
+ /**
2
+ * This file contains the class Matterbridge.
3
+ *
4
+ * @file matterbridge.ts
5
+ * @author Luca Liguori
6
+ * @created 2023-12-29
7
+ * @version 1.6.0
8
+ * @license Apache-2.0
9
+ *
10
+ * Copyright 2023, 2024, 2025 Luca Liguori.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ // Node.js modules
1
25
  import os from 'node:os';
2
26
  import path from 'node:path';
3
27
  import { promises as fs } from 'node:fs';
4
28
  import EventEmitter from 'node:events';
5
29
  import { inspect } from 'node:util';
30
+ // AnsiLogger module
6
31
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE } from 'node-ansi-logger';
32
+ // NodeStorage module
7
33
  import { NodeStorageManager } from 'node-persist-manager';
34
+ // @matter
8
35
  import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
9
36
  import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
10
37
  import { AggregatorEndpoint } from '@matter/main/endpoints';
11
38
  import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
12
39
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
40
+ // Matterbridge
13
41
  import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
14
42
  import { withTimeout, waiter, wait } from './utils/wait.js';
15
43
  import { dev, plg, typ } from './matterbridgeTypes.js';
@@ -19,6 +47,9 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
19
47
  import { bridge } from './matterbridgeDeviceTypes.js';
20
48
  import { Frontend } from './frontend.js';
21
49
  import { addVirtualDevices } from './helpers.js';
50
+ /**
51
+ * Represents the Matterbridge application.
52
+ */
22
53
  export class Matterbridge extends EventEmitter {
23
54
  systemInformation = {
24
55
  interfaceName: '',
@@ -67,7 +98,7 @@ export class Matterbridge extends EventEmitter {
67
98
  shellySysUpdate: false,
68
99
  shellyMainUpdate: false,
69
100
  profile: getParameter('profile'),
70
- loggerLevel: "info",
101
+ loggerLevel: "info" /* LogLevel.INFO */,
71
102
  fileLogger: false,
72
103
  matterLoggerLevel: MatterLogLevel.INFO,
73
104
  matterFileLogger: false,
@@ -95,15 +126,18 @@ export class Matterbridge extends EventEmitter {
95
126
  profile = getParameter('profile');
96
127
  shutdown = false;
97
128
  failCountLimit = hasParameter('shelly') ? 600 : 120;
98
- log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
129
+ // Matterbridge log files
130
+ log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
99
131
  matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
100
132
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
101
133
  plugins = new PluginManager(this);
102
134
  devices = new DeviceManager(this);
103
135
  frontend = new Frontend(this);
136
+ // Matterbridge storage
104
137
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
105
138
  nodeStorage;
106
139
  nodeContext;
140
+ // Cleanup
107
141
  hasCleanupStarted = false;
108
142
  initialized = false;
109
143
  startMatterInterval;
@@ -117,20 +151,25 @@ export class Matterbridge extends EventEmitter {
117
151
  sigtermHandler;
118
152
  exceptionHandler;
119
153
  rejectionHandler;
120
- matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
154
+ // Matter logger
155
+ matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
156
+ // Matter environment
121
157
  environment = Environment.default;
158
+ // Matter storage
122
159
  matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
123
160
  matterStorageService;
124
161
  matterStorageManager;
125
162
  matterbridgeContext;
126
163
  controllerContext;
127
- mdnsInterface;
128
- ipv4address;
129
- ipv6address;
130
- port;
131
- passcode;
132
- discriminator;
133
- certification;
164
+ // Matter parameters
165
+ mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
166
+ ipv4address; // matter server node listeningAddressIpv4
167
+ ipv6address; // matter server node listeningAddressIpv6
168
+ port; // first server node port
169
+ passcode; // first server node passcode
170
+ discriminator; // first server node discriminator
171
+ certification; // device certification
172
+ // Matter nodes
134
173
  serverNode;
135
174
  aggregatorNode;
136
175
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
@@ -142,15 +181,31 @@ export class Matterbridge extends EventEmitter {
142
181
  aggregatorUniqueId = getParameter('uniqueId');
143
182
  advertisingNodes = new Map();
144
183
  static instance;
184
+ // We load asyncronously so is private
145
185
  constructor() {
146
186
  super();
147
187
  }
188
+ /**
189
+ * Retrieves the list of Matterbridge devices.
190
+ *
191
+ * @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
192
+ */
148
193
  getDevices() {
149
194
  return this.devices.array();
150
195
  }
196
+ /**
197
+ * Retrieves the list of registered plugins.
198
+ *
199
+ * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
200
+ */
151
201
  getPlugins() {
152
202
  return this.plugins.array();
153
203
  }
204
+ /**
205
+ * Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
206
+ *
207
+ * @param {LogLevel} logLevel The logger logLevel to set.
208
+ */
154
209
  async setLogLevel(logLevel) {
155
210
  if (this.log)
156
211
  this.log.logLevel = logLevel;
@@ -164,19 +219,31 @@ export class Matterbridge extends EventEmitter {
164
219
  for (const plugin of this.plugins) {
165
220
  if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
166
221
  continue;
167
- plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
168
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
169
- }
170
- let callbackLogLevel = "notice";
171
- if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
172
- callbackLogLevel = "info";
173
- if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
174
- callbackLogLevel = "debug";
222
+ plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
223
+ await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
224
+ }
225
+ // Set the global logger callback for the WebSocketServer to the common minimum logLevel
226
+ let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
227
+ if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
228
+ callbackLogLevel = "info" /* LogLevel.INFO */;
229
+ if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
230
+ callbackLogLevel = "debug" /* LogLevel.DEBUG */;
175
231
  AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
176
232
  this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
177
233
  }
234
+ //* ************************************************************************************************************************************ */
235
+ // loadInstance() and cleanup() methods */
236
+ //* ************************************************************************************************************************************ */
237
+ /**
238
+ * Loads an instance of the Matterbridge class.
239
+ * If an instance already exists, return that instance.
240
+ *
241
+ * @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
242
+ * @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
243
+ */
178
244
  static async loadInstance(initialize = false) {
179
245
  if (!Matterbridge.instance) {
246
+ // eslint-disable-next-line no-console
180
247
  if (hasParameter('debug'))
181
248
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
182
249
  Matterbridge.instance = new Matterbridge();
@@ -185,8 +252,17 @@ export class Matterbridge extends EventEmitter {
185
252
  }
186
253
  return Matterbridge.instance;
187
254
  }
255
+ /**
256
+ * Call cleanup() and dispose MdnsService.
257
+ *
258
+ * @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
259
+ * @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
260
+ *
261
+ * @deprecated This method is deprecated and is ONLY used for jest tests.
262
+ */
188
263
  async destroyInstance(timeout = 1000, pause = 250) {
189
264
  this.log.info(`Destroy instance...`);
265
+ // Save server nodes to close
190
266
  const servers = [];
191
267
  if (this.bridgeMode === 'bridge') {
192
268
  if (this.serverNode)
@@ -204,72 +280,105 @@ export class Matterbridge extends EventEmitter {
204
280
  servers.push(device.serverNode);
205
281
  }
206
282
  }
283
+ // Let any already‐queued microtasks run first
207
284
  await Promise.resolve();
285
+ // Wait for the cleanup to finish
208
286
  await wait(pause, 'destroyInstance start', true);
287
+ // Cleanup
209
288
  await this.cleanup('destroying instance...', false, timeout);
289
+ // Close servers mdns service
210
290
  this.log.info(`Dispose ${servers.length} MdnsService...`);
211
291
  for (const server of servers) {
212
292
  await server.env.get(MdnsService)[Symbol.asyncDispose]();
213
293
  this.log.info(`Closed ${server.id} MdnsService`);
214
294
  }
295
+ // Let any already‐queued microtasks run first
215
296
  await Promise.resolve();
297
+ // Wait for the cleanup to finish
216
298
  await wait(pause, 'destroyInstance stop', true);
217
299
  }
300
+ /**
301
+ * Initializes the Matterbridge application.
302
+ *
303
+ * @remarks
304
+ * This method performs the necessary setup and initialization steps for the Matterbridge application.
305
+ * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
306
+ * node version, registers signal handlers, initializes storage, and parses the command line.
307
+ *
308
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
309
+ */
218
310
  async initialize() {
311
+ // Emit the initialize_started event
219
312
  this.emit('initialize_started');
313
+ // Set the restart mode
220
314
  if (hasParameter('service'))
221
315
  this.restartMode = 'service';
222
316
  if (hasParameter('docker'))
223
317
  this.restartMode = 'docker';
318
+ // Set the matterbridge home directory
224
319
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
225
320
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
226
321
  await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
322
+ // Set the matterbridge directory
227
323
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
228
324
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
229
325
  await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
230
326
  await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
231
327
  await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
328
+ // Set the matterbridge plugin directory
232
329
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
233
330
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
234
331
  await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
332
+ // Set the matterbridge cert directory
235
333
  this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
236
334
  this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
237
335
  await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
336
+ // Set the matterbridge root directory
238
337
  const { fileURLToPath } = await import('node:url');
239
338
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
240
339
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
241
340
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
341
+ // Setup the matter environment
242
342
  this.environment.vars.set('log.level', MatterLogLevel.INFO);
243
343
  this.environment.vars.set('log.format', MatterLogFormat.ANSI);
244
344
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
245
345
  this.environment.vars.set('runtime.signals', false);
246
346
  this.environment.vars.set('runtime.exitcode', false);
347
+ // Register process handlers
247
348
  this.registerProcessHandlers();
349
+ // Initialize nodeStorage and nodeContext
248
350
  try {
249
351
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
250
352
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
251
353
  this.log.debug('Creating node storage context for matterbridge');
252
354
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
355
+ // TODO: Remove this code when node-persist-manager is updated
356
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
253
357
  const keys = (await this.nodeStorage?.storage.keys());
254
358
  for (const key of keys) {
255
359
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
360
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
361
  await this.nodeStorage?.storage.get(key);
257
362
  }
258
363
  const storages = await this.nodeStorage.getStorageNames();
259
364
  for (const storage of storages) {
260
365
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
261
366
  const nodeContext = await this.nodeStorage?.createStorage(storage);
367
+ // TODO: Remove this code when node-persist-manager is updated
368
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
262
369
  const keys = (await nodeContext?.storage.keys());
263
370
  keys.forEach(async (key) => {
264
371
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
265
372
  await nodeContext?.get(key);
266
373
  });
267
374
  }
375
+ // Creating a backup of the node storage since it is not corrupted
268
376
  this.log.debug('Creating node storage backup...');
269
377
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
270
378
  this.log.debug('Created node storage backup');
271
379
  }
272
380
  catch (error) {
381
+ // Restoring the backup of the node storage since it is corrupted
273
382
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
274
383
  if (hasParameter('norestore')) {
275
384
  this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
@@ -283,14 +392,19 @@ export class Matterbridge extends EventEmitter {
283
392
  if (!this.nodeStorage || !this.nodeContext) {
284
393
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
285
394
  }
395
+ // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
286
396
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
397
+ // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
287
398
  this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
399
+ // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
288
400
  this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
401
+ // Certificate management
289
402
  const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
290
403
  try {
291
404
  await fs.access(pairingFilePath, fs.constants.R_OK);
292
405
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
293
406
  const pairingFileJson = JSON.parse(pairingFileContent);
407
+ // Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
294
408
  if (isValidNumber(pairingFileJson.vendorId)) {
295
409
  this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
296
410
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
@@ -319,11 +433,13 @@ export class Matterbridge extends EventEmitter {
319
433
  this.aggregatorUniqueId = pairingFileJson.uniqueId;
320
434
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
321
435
  }
436
+ // Override the passcode and discriminator if they are present in the pairing file
322
437
  if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
323
438
  this.passcode = pairingFileJson.passcode;
324
439
  this.discriminator = pairingFileJson.discriminator;
325
440
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
326
441
  }
442
+ // Set the certification for matter.js if it is present in the pairing file
327
443
  if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
328
444
  const { hexToBuffer } = await import('./utils/hex.js');
329
445
  this.certification = {
@@ -338,41 +454,44 @@ export class Matterbridge extends EventEmitter {
338
454
  catch (error) {
339
455
  this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
340
456
  }
457
+ // Store the passcode, discriminator and port in the node context
341
458
  await this.nodeContext.set('matterport', this.port);
342
459
  await this.nodeContext.set('matterpasscode', this.passcode);
343
460
  await this.nodeContext.set('matterdiscriminator', this.discriminator);
344
461
  this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
462
+ // Set matterbridge logger level (context: matterbridgeLogLevel)
345
463
  if (hasParameter('logger')) {
346
464
  const level = getParameter('logger');
347
465
  if (level === 'debug') {
348
- this.log.logLevel = "debug";
466
+ this.log.logLevel = "debug" /* LogLevel.DEBUG */;
349
467
  }
350
468
  else if (level === 'info') {
351
- this.log.logLevel = "info";
469
+ this.log.logLevel = "info" /* LogLevel.INFO */;
352
470
  }
353
471
  else if (level === 'notice') {
354
- this.log.logLevel = "notice";
472
+ this.log.logLevel = "notice" /* LogLevel.NOTICE */;
355
473
  }
356
474
  else if (level === 'warn') {
357
- this.log.logLevel = "warn";
475
+ this.log.logLevel = "warn" /* LogLevel.WARN */;
358
476
  }
359
477
  else if (level === 'error') {
360
- this.log.logLevel = "error";
478
+ this.log.logLevel = "error" /* LogLevel.ERROR */;
361
479
  }
362
480
  else if (level === 'fatal') {
363
- this.log.logLevel = "fatal";
481
+ this.log.logLevel = "fatal" /* LogLevel.FATAL */;
364
482
  }
365
483
  else {
366
484
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
367
- this.log.logLevel = "info";
485
+ this.log.logLevel = "info" /* LogLevel.INFO */;
368
486
  }
369
487
  }
370
488
  else {
371
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
489
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
372
490
  }
373
491
  this.frontend.logLevel = this.log.logLevel;
374
492
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
375
493
  this.matterbridgeInformation.loggerLevel = this.log.logLevel;
494
+ // Create the file logger for matterbridge (context: matterbridgeFileLog)
376
495
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
377
496
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
378
497
  this.matterbridgeInformation.fileLogger = true;
@@ -381,6 +500,7 @@ export class Matterbridge extends EventEmitter {
381
500
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
382
501
  if (this.profile !== undefined)
383
502
  this.log.debug(`Matterbridge profile: ${this.profile}.`);
503
+ // Set matter.js logger level, format and logger (context: matterLogLevel)
384
504
  if (hasParameter('matterlogger')) {
385
505
  const level = getParameter('matterlogger');
386
506
  if (level === 'debug') {
@@ -410,12 +530,14 @@ export class Matterbridge extends EventEmitter {
410
530
  Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
411
531
  }
412
532
  Logger.format = MatterLogFormat.ANSI;
533
+ // Create the logger for matter.js with file logging (context: matterFileLog)
413
534
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
414
535
  this.matterbridgeInformation.matterFileLogger = true;
415
536
  }
416
537
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterbridgeInformation.matterFileLogger);
417
538
  this.matterbridgeInformation.matterLoggerLevel = Logger.level;
418
539
  this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
540
+ // Log network interfaces
419
541
  const networkInterfaces = os.networkInterfaces();
420
542
  const availableAddresses = Object.entries(networkInterfaces);
421
543
  const availableInterfaces = Object.keys(networkInterfaces);
@@ -428,6 +550,7 @@ export class Matterbridge extends EventEmitter {
428
550
  });
429
551
  }
430
552
  }
553
+ // Set the interface to use for matter server node mdnsInterface
431
554
  if (hasParameter('mdnsinterface')) {
432
555
  this.mdnsInterface = getParameter('mdnsinterface');
433
556
  }
@@ -436,6 +559,7 @@ export class Matterbridge extends EventEmitter {
436
559
  if (this.mdnsInterface === '')
437
560
  this.mdnsInterface = undefined;
438
561
  }
562
+ // Validate mdnsInterface
439
563
  if (this.mdnsInterface) {
440
564
  if (!availableInterfaces.includes(this.mdnsInterface)) {
441
565
  this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
@@ -448,6 +572,7 @@ export class Matterbridge extends EventEmitter {
448
572
  }
449
573
  if (this.mdnsInterface)
450
574
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
575
+ // Set the listeningAddressIpv4 for the matter commissioning server
451
576
  if (hasParameter('ipv4address')) {
452
577
  this.ipv4address = getParameter('ipv4address');
453
578
  }
@@ -456,6 +581,7 @@ export class Matterbridge extends EventEmitter {
456
581
  if (this.ipv4address === '')
457
582
  this.ipv4address = undefined;
458
583
  }
584
+ // Validate ipv4address
459
585
  if (this.ipv4address) {
460
586
  let isValid = false;
461
587
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -471,6 +597,7 @@ export class Matterbridge extends EventEmitter {
471
597
  await this.nodeContext.remove('matteripv4address');
472
598
  }
473
599
  }
600
+ // Set the listeningAddressIpv6 for the matter commissioning server
474
601
  if (hasParameter('ipv6address')) {
475
602
  this.ipv6address = getParameter('ipv6address');
476
603
  }
@@ -479,6 +606,7 @@ export class Matterbridge extends EventEmitter {
479
606
  if (this.ipv6address === '')
480
607
  this.ipv6address = undefined;
481
608
  }
609
+ // Validate ipv6address
482
610
  if (this.ipv6address) {
483
611
  let isValid = false;
484
612
  for (const [ifaceName, ifaces] of availableAddresses) {
@@ -487,6 +615,7 @@ export class Matterbridge extends EventEmitter {
487
615
  isValid = true;
488
616
  break;
489
617
  }
618
+ /* istanbul ignore next */
490
619
  if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
491
620
  this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
492
621
  isValid = true;
@@ -499,6 +628,7 @@ export class Matterbridge extends EventEmitter {
499
628
  await this.nodeContext.remove('matteripv6address');
500
629
  }
501
630
  }
631
+ // Initialize the virtual mode
502
632
  if (hasParameter('novirtual')) {
503
633
  this.matterbridgeInformation.virtualMode = 'disabled';
504
634
  await this.nodeContext.set('virtualmode', 'disabled');
@@ -507,12 +637,17 @@ export class Matterbridge extends EventEmitter {
507
637
  this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
508
638
  }
509
639
  this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
640
+ // Initialize PluginManager
510
641
  this.plugins.logLevel = this.log.logLevel;
511
642
  await this.plugins.loadFromStorage();
643
+ // Initialize DeviceManager
512
644
  this.devices.logLevel = this.log.logLevel;
645
+ // Get the plugins from node storage and create the plugins node storage contexts
513
646
  for (const plugin of this.plugins) {
514
647
  const packageJson = await this.plugins.parse(plugin);
515
648
  if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
649
+ // Try to reinstall the plugin from npm (for Docker pull and external plugins)
650
+ // We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
516
651
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
517
652
  try {
518
653
  const { spawnCommand } = await import('./utils/spawn.js');
@@ -535,6 +670,7 @@ export class Matterbridge extends EventEmitter {
535
670
  await plugin.nodeContext.set('description', plugin.description);
536
671
  await plugin.nodeContext.set('author', plugin.author);
537
672
  }
673
+ // Log system info and create .matterbridge directory
538
674
  await this.logNodeAndSystemInfo();
539
675
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
540
676
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -542,6 +678,7 @@ export class Matterbridge extends EventEmitter {
542
678
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
543
679
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
544
680
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
681
+ // Check node version and throw error
545
682
  const minNodeVersion = 18;
546
683
  const nodeVersion = process.versions.node;
547
684
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -549,10 +686,18 @@ export class Matterbridge extends EventEmitter {
549
686
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
550
687
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
551
688
  }
689
+ // Parse command line
552
690
  await this.parseCommandLine();
691
+ // Emit the initialize_completed event
553
692
  this.emit('initialize_completed');
554
693
  this.initialized = true;
555
694
  }
695
+ /**
696
+ * Parses the command line arguments and performs the corresponding actions.
697
+ *
698
+ * @private
699
+ * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
700
+ */
556
701
  async parseCommandLine() {
557
702
  if (hasParameter('help')) {
558
703
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -614,6 +759,19 @@ export class Matterbridge extends EventEmitter {
614
759
  }
615
760
  index++;
616
761
  }
762
+ /*
763
+ const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
764
+ this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
765
+ serializedRegisteredDevices?.forEach((device, index) => {
766
+ if (index !== serializedRegisteredDevices.length - 1) {
767
+ this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
768
+ this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
769
+ } else {
770
+ this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
771
+ this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
772
+ }
773
+ });
774
+ */
617
775
  this.shutdown = true;
618
776
  return;
619
777
  }
@@ -663,6 +821,7 @@ export class Matterbridge extends EventEmitter {
663
821
  this.shutdown = true;
664
822
  return;
665
823
  }
824
+ // Start the matter storage and create the matterbridge context
666
825
  try {
667
826
  await this.startMatterStorage();
668
827
  if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
@@ -679,18 +838,21 @@ export class Matterbridge extends EventEmitter {
679
838
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
680
839
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
681
840
  }
841
+ // Clear the matterbridge context if the reset parameter is set
682
842
  if (hasParameter('reset') && getParameter('reset') === undefined) {
683
843
  this.initialized = true;
684
844
  await this.shutdownProcessAndReset();
685
845
  this.shutdown = true;
686
846
  return;
687
847
  }
848
+ // Clear matterbridge plugin context if the reset parameter is set
688
849
  if (hasParameter('reset') && getParameter('reset') !== undefined) {
689
850
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
690
851
  const plugin = this.plugins.get(getParameter('reset'));
691
852
  if (plugin) {
692
853
  const matterStorageManager = await this.matterStorageService?.open(plugin.name);
693
854
  if (!matterStorageManager) {
855
+ /* istanbul ignore next */
694
856
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
695
857
  }
696
858
  else {
@@ -709,37 +871,45 @@ export class Matterbridge extends EventEmitter {
709
871
  this.shutdown = true;
710
872
  return;
711
873
  }
874
+ // Initialize frontend
712
875
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
713
876
  await this.frontend.start(getIntParameter('frontend'));
877
+ // Check in 30 seconds the latest and dev versions of matterbridge and the plugins
714
878
  clearTimeout(this.checkUpdateTimeout);
715
879
  this.checkUpdateTimeout = setTimeout(async () => {
716
880
  const { checkUpdates } = await import('./update.js');
717
881
  checkUpdates(this);
718
882
  }, 30 * 1000).unref();
883
+ // Check each 12 hours the latest and dev versions of matterbridge and the plugins
719
884
  clearInterval(this.checkUpdateInterval);
720
885
  this.checkUpdateInterval = setInterval(async () => {
721
886
  const { checkUpdates } = await import('./update.js');
722
887
  checkUpdates(this);
723
888
  }, 12 * 60 * 60 * 1000).unref();
889
+ // Start the matterbridge in mode test
724
890
  if (hasParameter('test')) {
725
891
  this.bridgeMode = 'bridge';
726
892
  return;
727
893
  }
894
+ // Start the matterbridge in mode controller
728
895
  if (hasParameter('controller')) {
729
896
  this.bridgeMode = 'controller';
730
897
  await this.startController();
731
898
  return;
732
899
  }
900
+ // Check if the bridge mode is set and start matterbridge in bridge mode if not set
733
901
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
734
902
  this.log.info('Setting default matterbridge start mode to bridge');
735
903
  await this.nodeContext?.set('bridgeMode', 'bridge');
736
904
  }
905
+ // Start matterbridge in bridge mode
737
906
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
738
907
  this.bridgeMode = 'bridge';
739
908
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
740
909
  await this.startBridge();
741
910
  return;
742
911
  }
912
+ // Start matterbridge in childbridge mode
743
913
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
744
914
  this.bridgeMode = 'childbridge';
745
915
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
@@ -747,10 +917,20 @@ export class Matterbridge extends EventEmitter {
747
917
  return;
748
918
  }
749
919
  }
920
+ /**
921
+ * Asynchronously loads and starts the registered plugins.
922
+ *
923
+ * This method is responsible for initializing and starting all enabled plugins.
924
+ * It ensures that each plugin is properly loaded and started before the bridge starts.
925
+ *
926
+ * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
927
+ */
750
928
  async startPlugins() {
929
+ // Check, load and start the plugins
751
930
  for (const plugin of this.plugins) {
752
931
  plugin.configJson = await this.plugins.loadConfig(plugin);
753
932
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
933
+ // Check if the plugin is available
754
934
  if (!(await this.plugins.resolve(plugin.path))) {
755
935
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
756
936
  plugin.enabled = false;
@@ -768,10 +948,14 @@ export class Matterbridge extends EventEmitter {
768
948
  plugin.configured = false;
769
949
  plugin.registeredDevices = undefined;
770
950
  plugin.addedDevices = undefined;
771
- this.plugins.load(plugin, true, 'Matterbridge is starting');
951
+ this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
772
952
  }
773
953
  this.frontend.wssSendRefreshRequired('plugins');
774
954
  }
955
+ /**
956
+ * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
957
+ * When either of these signals are received, the cleanup method is called with an appropriate message.
958
+ */
775
959
  registerProcessHandlers() {
776
960
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
777
961
  process.removeAllListeners('uncaughtException');
@@ -798,6 +982,9 @@ export class Matterbridge extends EventEmitter {
798
982
  };
799
983
  process.on('SIGTERM', this.sigtermHandler);
800
984
  }
985
+ /**
986
+ * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
987
+ */
801
988
  deregisterProcessHandlers() {
802
989
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
803
990
  if (this.exceptionHandler)
@@ -814,12 +1001,17 @@ export class Matterbridge extends EventEmitter {
814
1001
  process.off('SIGTERM', this.sigtermHandler);
815
1002
  this.sigtermHandler = undefined;
816
1003
  }
1004
+ /**
1005
+ * Logs the node and system information.
1006
+ */
817
1007
  async logNodeAndSystemInfo() {
1008
+ // IP address information
818
1009
  const networkInterfaces = os.networkInterfaces();
819
1010
  this.systemInformation.interfaceName = '';
820
1011
  this.systemInformation.ipv4Address = '';
821
1012
  this.systemInformation.ipv6Address = '';
822
1013
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
1014
+ // this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
823
1015
  if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
824
1016
  continue;
825
1017
  if (!interfaceDetails) {
@@ -845,19 +1037,22 @@ export class Matterbridge extends EventEmitter {
845
1037
  break;
846
1038
  }
847
1039
  }
1040
+ // Node information
848
1041
  this.systemInformation.nodeVersion = process.versions.node;
849
1042
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
850
1043
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
851
1044
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
1045
+ // Host system information
852
1046
  this.systemInformation.hostname = os.hostname();
853
1047
  this.systemInformation.user = os.userInfo().username;
854
- this.systemInformation.osType = os.type();
855
- this.systemInformation.osRelease = os.release();
856
- this.systemInformation.osPlatform = os.platform();
857
- this.systemInformation.osArch = os.arch();
858
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
859
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
860
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
1048
+ this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
1049
+ this.systemInformation.osRelease = os.release(); // Kernel version
1050
+ this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
1051
+ this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
1052
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1053
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
1054
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
1055
+ // Log the system information
861
1056
  this.log.debug('Host System Information:');
862
1057
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
863
1058
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -873,14 +1068,17 @@ export class Matterbridge extends EventEmitter {
873
1068
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
874
1069
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
875
1070
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
1071
+ // Log directories
876
1072
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
877
1073
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
878
1074
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
879
1075
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
880
1076
  this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
1077
+ // Global node_modules directory
881
1078
  if (this.nodeContext)
882
1079
  this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
883
1080
  if (this.globalModulesDirectory === '') {
1081
+ // First run of Matterbridge so the node storage is empty
884
1082
  this.log.debug(`Getting global node_modules directory...`);
885
1083
  try {
886
1084
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -893,6 +1091,7 @@ export class Matterbridge extends EventEmitter {
893
1091
  }
894
1092
  }
895
1093
  else {
1094
+ // The global node_modules directory is already set in the node storage and we check if it is still valid
896
1095
  this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
897
1096
  try {
898
1097
  const { getGlobalNodeModules } = await import('./utils/network.js');
@@ -904,57 +1103,85 @@ export class Matterbridge extends EventEmitter {
904
1103
  this.log.error(`Error checking global node_modules directory: ${error}`);
905
1104
  }
906
1105
  }
1106
+ // Matterbridge version
907
1107
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
908
1108
  this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
909
1109
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
910
1110
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
1111
+ // Matterbridge latest version (will be set in the checkUpdate function)
911
1112
  if (this.nodeContext)
912
1113
  this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
913
1114
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
1115
+ // Matterbridge dev version (will be set in the checkUpdate function)
914
1116
  if (this.nodeContext)
915
1117
  this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
916
1118
  this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
1119
+ // Current working directory
917
1120
  const currentDir = process.cwd();
918
1121
  this.log.debug(`Current Working Directory: ${currentDir}`);
1122
+ // Command line arguments (excluding 'node' and the script name)
919
1123
  const cmdArgs = process.argv.slice(2).join(' ');
920
1124
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
921
1125
  }
1126
+ /**
1127
+ * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1128
+ * It also logs to file (matter.log) if fileLogger is true.
1129
+ *
1130
+ * @param {boolean} fileLogger - Whether to log to file or not.
1131
+ * @returns {Function} The MatterLogger function.
1132
+ */
922
1133
  createDestinationMatterLogger(fileLogger) {
923
1134
  if (fileLogger) {
924
1135
  this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, this.matterLoggerFile);
925
1136
  }
926
1137
  return (text, message) => {
1138
+ // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
927
1139
  const logger = text.slice(44, 44 + 20).trim();
928
1140
  const msg = text.slice(65);
929
1141
  this.matterLog.logName = logger;
930
1142
  switch (message.level) {
931
1143
  case MatterLogLevel.DEBUG:
932
- this.matterLog.log("debug", msg);
1144
+ this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
933
1145
  break;
934
1146
  case MatterLogLevel.INFO:
935
- this.matterLog.log("info", msg);
1147
+ this.matterLog.log("info" /* LogLevel.INFO */, msg);
936
1148
  break;
937
1149
  case MatterLogLevel.NOTICE:
938
- this.matterLog.log("notice", msg);
1150
+ this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
939
1151
  break;
940
1152
  case MatterLogLevel.WARN:
941
- this.matterLog.log("warn", msg);
1153
+ this.matterLog.log("warn" /* LogLevel.WARN */, msg);
942
1154
  break;
943
1155
  case MatterLogLevel.ERROR:
944
- this.matterLog.log("error", msg);
1156
+ this.matterLog.log("error" /* LogLevel.ERROR */, msg);
945
1157
  break;
946
1158
  case MatterLogLevel.FATAL:
947
- this.matterLog.log("fatal", msg);
1159
+ this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
948
1160
  break;
949
1161
  }
950
1162
  };
951
1163
  }
1164
+ /**
1165
+ * Restarts the process by exiting the current instance and loading a new instance (/api/restart).
1166
+ *
1167
+ * @returns {Promise<void>} A promise that resolves when the restart is completed.
1168
+ */
952
1169
  async restartProcess() {
953
1170
  await this.cleanup('restarting...', true);
954
1171
  }
1172
+ /**
1173
+ * Shut down the process (/api/shutdown).
1174
+ *
1175
+ * @returns {Promise<void>} A promise that resolves when the shutdown is completed.
1176
+ */
955
1177
  async shutdownProcess() {
956
1178
  await this.cleanup('shutting down...', false);
957
1179
  }
1180
+ /**
1181
+ * Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
1182
+ *
1183
+ * @returns {Promise<void>} A promise that resolves when the update is completed.
1184
+ */
958
1185
  async updateProcess() {
959
1186
  this.log.info('Updating matterbridge...');
960
1187
  try {
@@ -968,6 +1195,13 @@ export class Matterbridge extends EventEmitter {
968
1195
  this.frontend.wssSendRestartRequired();
969
1196
  await this.cleanup('updating...', false);
970
1197
  }
1198
+ /**
1199
+ * Unregister all devices and shut down the process (/api/unregister).
1200
+ *
1201
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1202
+ *
1203
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1204
+ */
971
1205
  async unregisterAndShutdownProcess(timeout = 1000) {
972
1206
  this.log.info('Unregistering all devices and shutting down...');
973
1207
  for (const plugin of this.plugins.array()) {
@@ -981,46 +1215,71 @@ export class Matterbridge extends EventEmitter {
981
1215
  await this.removeAllBridgedEndpoints(plugin.name, 100);
982
1216
  }
983
1217
  this.log.debug('Waiting for the MessageExchange to finish...');
984
- await wait(timeout);
1218
+ await wait(timeout); // Wait for MessageExchange to finish
985
1219
  this.log.debug('Cleaning up and shutting down...');
986
1220
  await this.cleanup('unregistered all devices and shutting down...', false, timeout);
987
1221
  }
1222
+ /**
1223
+ * Reset commissioning and shut down the process (/api/reset).
1224
+ *
1225
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1226
+ */
988
1227
  async shutdownProcessAndReset() {
989
1228
  await this.cleanup('shutting down with reset...', false);
990
1229
  }
1230
+ /**
1231
+ * Factory reset and shut down the process (/api/factory-reset).
1232
+ *
1233
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1234
+ */
991
1235
  async shutdownProcessAndFactoryReset() {
992
1236
  await this.cleanup('shutting down with factory reset...', false);
993
1237
  }
1238
+ /**
1239
+ * Cleans up the Matterbridge instance.
1240
+ *
1241
+ * @param {string} message - The cleanup message.
1242
+ * @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
1243
+ * @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
1244
+ *
1245
+ * @returns {Promise<void>} A promise that resolves when the cleanup is completed.
1246
+ */
994
1247
  async cleanup(message, restart = false, timeout = 1000) {
995
1248
  if (this.initialized && !this.hasCleanupStarted) {
996
1249
  this.emit('cleanup_started');
997
1250
  this.hasCleanupStarted = true;
998
1251
  this.log.info(message);
1252
+ // Clear the start matter interval
999
1253
  if (this.startMatterInterval) {
1000
1254
  clearInterval(this.startMatterInterval);
1001
1255
  this.startMatterInterval = undefined;
1002
1256
  this.log.debug('Start matter interval cleared');
1003
1257
  }
1258
+ // Clear the check update timeout
1004
1259
  if (this.checkUpdateTimeout) {
1005
1260
  clearTimeout(this.checkUpdateTimeout);
1006
1261
  this.checkUpdateTimeout = undefined;
1007
1262
  this.log.debug('Check update timeout cleared');
1008
1263
  }
1264
+ // Clear the check update interval
1009
1265
  if (this.checkUpdateInterval) {
1010
1266
  clearInterval(this.checkUpdateInterval);
1011
1267
  this.checkUpdateInterval = undefined;
1012
1268
  this.log.debug('Check update interval cleared');
1013
1269
  }
1270
+ // Clear the configure timeout
1014
1271
  if (this.configureTimeout) {
1015
1272
  clearTimeout(this.configureTimeout);
1016
1273
  this.configureTimeout = undefined;
1017
1274
  this.log.debug('Matterbridge configure timeout cleared');
1018
1275
  }
1276
+ // Clear the reachability timeout
1019
1277
  if (this.reachabilityTimeout) {
1020
1278
  clearTimeout(this.reachabilityTimeout);
1021
1279
  this.reachabilityTimeout = undefined;
1022
1280
  this.log.debug('Matterbridge reachability timeout cleared');
1023
1281
  }
1282
+ // Call the shutdown method of each plugin and clear the plugins reachability timeout
1024
1283
  for (const plugin of this.plugins) {
1025
1284
  if (!plugin.enabled || plugin.error)
1026
1285
  continue;
@@ -1031,6 +1290,7 @@ export class Matterbridge extends EventEmitter {
1031
1290
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1032
1291
  }
1033
1292
  }
1293
+ // Stop matter server nodes
1034
1294
  this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1035
1295
  this.log.debug('Waiting for the MessageExchange to finish...');
1036
1296
  await wait(timeout, 'Waiting for the MessageExchange to finish...', true);
@@ -1055,6 +1315,7 @@ export class Matterbridge extends EventEmitter {
1055
1315
  }
1056
1316
  }
1057
1317
  this.log.notice('Stopped matter server nodes');
1318
+ // Matter commisioning reset
1058
1319
  if (message === 'shutting down with reset...') {
1059
1320
  this.log.info('Resetting Matterbridge commissioning information...');
1060
1321
  await this.matterStorageManager?.createContext('events')?.clearAll();
@@ -1064,6 +1325,7 @@ export class Matterbridge extends EventEmitter {
1064
1325
  await this.matterbridgeContext?.clearAll();
1065
1326
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1066
1327
  }
1328
+ // Unregister all devices
1067
1329
  if (message === 'unregistered all devices and shutting down...') {
1068
1330
  if (this.bridgeMode === 'bridge') {
1069
1331
  await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
@@ -1081,18 +1343,36 @@ export class Matterbridge extends EventEmitter {
1081
1343
  }
1082
1344
  this.log.info('Matter storage reset done!');
1083
1345
  }
1346
+ // Stop matter storage
1084
1347
  await this.stopMatterStorage();
1348
+ // Stop the frontend
1085
1349
  await this.frontend.stop();
1350
+ // Remove the matterfilelogger
1086
1351
  try {
1087
1352
  Logger.removeLogger('matterfilelogger');
1088
1353
  }
1089
1354
  catch (error) {
1090
1355
  this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
1091
1356
  }
1357
+ // Close the matterbridge node storage and context
1092
1358
  if (this.nodeStorage && this.nodeContext) {
1359
+ /*
1360
+ TODO: Implement serialization of registered devices in edge mode
1361
+ this.log.info('Saving registered devices...');
1362
+ const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
1363
+ this.devices.forEach(async (device) => {
1364
+ const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
1365
+ // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1366
+ if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1367
+ });
1368
+ await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
1369
+ this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1370
+ */
1371
+ // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1093
1372
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1094
1373
  await this.nodeContext.close();
1095
1374
  this.nodeContext = undefined;
1375
+ // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1096
1376
  for (const plugin of this.plugins) {
1097
1377
  if (plugin.nodeContext) {
1098
1378
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1109,8 +1389,10 @@ export class Matterbridge extends EventEmitter {
1109
1389
  }
1110
1390
  this.plugins.clear();
1111
1391
  this.devices.clear();
1392
+ // Factory reset
1112
1393
  if (message === 'shutting down with factory reset...') {
1113
1394
  try {
1395
+ // Delete matter storage directory with its subdirectories and backup
1114
1396
  const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
1115
1397
  this.log.info(`Removing matter storage directory: ${dir}`);
1116
1398
  await fs.rm(dir, { recursive: true });
@@ -1119,11 +1401,13 @@ export class Matterbridge extends EventEmitter {
1119
1401
  await fs.rm(backup, { recursive: true });
1120
1402
  }
1121
1403
  catch (error) {
1404
+ // istanbul ignore next if
1122
1405
  if (error instanceof Error && error.code !== 'ENOENT') {
1123
1406
  this.log.error(`Error removing matter storage directory: ${error}`);
1124
1407
  }
1125
1408
  }
1126
1409
  try {
1410
+ // Delete matterbridge storage directory with its subdirectories and backup
1127
1411
  const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
1128
1412
  this.log.info(`Removing matterbridge storage directory: ${dir}`);
1129
1413
  await fs.rm(dir, { recursive: true });
@@ -1132,18 +1416,20 @@ export class Matterbridge extends EventEmitter {
1132
1416
  await fs.rm(backup, { recursive: true });
1133
1417
  }
1134
1418
  catch (error) {
1419
+ // istanbul ignore next if
1135
1420
  if (error instanceof Error && error.code !== 'ENOENT') {
1136
1421
  this.log.error(`Error removing matterbridge storage directory: ${error}`);
1137
1422
  }
1138
1423
  }
1139
1424
  this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1140
1425
  }
1426
+ // Deregisters the process handlers
1141
1427
  this.deregisterProcessHandlers();
1142
1428
  if (restart) {
1143
1429
  if (message === 'updating...') {
1144
1430
  this.log.info('Cleanup completed. Updating...');
1145
1431
  Matterbridge.instance = undefined;
1146
- this.emit('update');
1432
+ this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
1147
1433
  }
1148
1434
  else if (message === 'restarting...') {
1149
1435
  this.log.info('Cleanup completed. Restarting...');
@@ -1164,6 +1450,13 @@ export class Matterbridge extends EventEmitter {
1164
1450
  this.log.debug('Cleanup already started...');
1165
1451
  }
1166
1452
  }
1453
+ /**
1454
+ * Creates and configures the server node for a single not bridged device.
1455
+ *
1456
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1457
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1458
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1459
+ */
1167
1460
  async createDeviceServerNode(plugin, device) {
1168
1461
  if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
1169
1462
  this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
@@ -1174,6 +1467,13 @@ export class Matterbridge extends EventEmitter {
1174
1467
  this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
1175
1468
  }
1176
1469
  }
1470
+ /**
1471
+ * Creates and configures the server node for an accessory plugin for a given device.
1472
+ *
1473
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1474
+ * @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
1475
+ * @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
1476
+ */
1177
1477
  async createAccessoryPlugin(plugin, device) {
1178
1478
  if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1179
1479
  plugin.locked = true;
@@ -1185,6 +1485,12 @@ export class Matterbridge extends EventEmitter {
1185
1485
  await plugin.serverNode.add(device);
1186
1486
  }
1187
1487
  }
1488
+ /**
1489
+ * Creates and configures the server node and the aggregator node for a dynamic plugin.
1490
+ *
1491
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
1492
+ * @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
1493
+ */
1188
1494
  async createDynamicPlugin(plugin) {
1189
1495
  if (!plugin.locked) {
1190
1496
  plugin.locked = true;
@@ -1195,7 +1501,14 @@ export class Matterbridge extends EventEmitter {
1195
1501
  await plugin.serverNode.add(plugin.aggregatorNode);
1196
1502
  }
1197
1503
  }
1504
+ /**
1505
+ * Starts the Matterbridge in bridge mode.
1506
+ *
1507
+ * @private
1508
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1509
+ */
1198
1510
  async startBridge() {
1511
+ // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1199
1512
  if (!this.matterStorageManager)
1200
1513
  throw new Error('No storage manager initialized');
1201
1514
  if (!this.matterbridgeContext)
@@ -1234,13 +1547,16 @@ export class Matterbridge extends EventEmitter {
1234
1547
  clearInterval(this.startMatterInterval);
1235
1548
  this.startMatterInterval = undefined;
1236
1549
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1237
- this.startServerNode(this.serverNode);
1550
+ // Start the Matter server node
1551
+ this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
1552
+ // Start the Matter server node of single devices in mode 'server'
1238
1553
  for (const device of this.devices.array()) {
1239
1554
  if (device.mode === 'server' && device.serverNode) {
1240
1555
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1241
- this.startServerNode(device.serverNode);
1556
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1242
1557
  }
1243
1558
  }
1559
+ // Configure the plugins
1244
1560
  this.configureTimeout = setTimeout(async () => {
1245
1561
  for (const plugin of this.plugins.array()) {
1246
1562
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1258,16 +1574,25 @@ export class Matterbridge extends EventEmitter {
1258
1574
  }
1259
1575
  this.frontend.wssSendRefreshRequired('plugins');
1260
1576
  }, 30 * 1000).unref();
1577
+ // Setting reachability to true
1261
1578
  this.reachabilityTimeout = setTimeout(() => {
1262
1579
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1263
1580
  if (this.aggregatorNode)
1264
1581
  this.setAggregatorReachability(this.aggregatorNode, true);
1265
1582
  this.frontend.wssSendRefreshRequired('reachability');
1266
1583
  }, 60 * 1000).unref();
1584
+ // Logger.get('LogServerNode').info(this.serverNode);
1267
1585
  this.emit('bridge_started');
1268
1586
  this.log.notice('Matterbridge bridge started successfully');
1269
1587
  }, this.startMatterIntervalMs);
1270
1588
  }
1589
+ /**
1590
+ * Starts the Matterbridge in childbridge mode.
1591
+ *
1592
+ * @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
1593
+ *
1594
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1595
+ */
1271
1596
  async startChildbridge(delay = 1000) {
1272
1597
  if (!this.matterStorageManager)
1273
1598
  throw new Error('No storage manager initialized');
@@ -1308,8 +1633,9 @@ export class Matterbridge extends EventEmitter {
1308
1633
  clearInterval(this.startMatterInterval);
1309
1634
  this.startMatterInterval = undefined;
1310
1635
  if (delay > 0)
1311
- await wait(delay);
1636
+ await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
1312
1637
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1638
+ // Configure the plugins
1313
1639
  this.configureTimeout = setTimeout(async () => {
1314
1640
  for (const plugin of this.plugins.array()) {
1315
1641
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
@@ -1346,7 +1672,9 @@ export class Matterbridge extends EventEmitter {
1346
1672
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1347
1673
  continue;
1348
1674
  }
1349
- this.startServerNode(plugin.serverNode);
1675
+ // Start the Matter server node
1676
+ this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
1677
+ // Setting reachability to true
1350
1678
  plugin.reachabilityTimeout = setTimeout(() => {
1351
1679
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggregator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
1352
1680
  if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
@@ -1354,19 +1682,241 @@ export class Matterbridge extends EventEmitter {
1354
1682
  this.frontend.wssSendRefreshRequired('reachability');
1355
1683
  }, 60 * 1000).unref();
1356
1684
  }
1685
+ // Start the Matter server node of single devices in mode 'server'
1357
1686
  for (const device of this.devices.array()) {
1358
1687
  if (device.mode === 'server' && device.serverNode) {
1359
1688
  this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
1360
- this.startServerNode(device.serverNode);
1689
+ this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
1361
1690
  }
1362
1691
  }
1692
+ // Logger.get('LogServerNode').info(this.serverNode);
1363
1693
  this.emit('childbridge_started');
1364
1694
  this.log.notice('Matterbridge childbridge started successfully');
1365
1695
  }, this.startMatterIntervalMs);
1366
1696
  }
1697
+ /**
1698
+ * Starts the Matterbridge controller.
1699
+ *
1700
+ * @private
1701
+ * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1702
+ */
1367
1703
  async startController() {
1704
+ /*
1705
+ if (!this.matterStorageManager) {
1706
+ this.log.error('No storage manager initialized');
1707
+ await this.cleanup('No storage manager initialized');
1708
+ return;
1709
+ }
1710
+ this.log.info('Creating context: mattercontrollerContext');
1711
+ this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
1712
+ if (!this.controllerContext) {
1713
+ this.log.error('No storage context mattercontrollerContext initialized');
1714
+ await this.cleanup('No storage context mattercontrollerContext initialized');
1715
+ return;
1716
+ }
1717
+
1718
+ this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1719
+ this.matterServer = await this.createMatterServer(this.storageManager);
1720
+ this.log.info('Creating matter commissioning controller');
1721
+ this.commissioningController = new CommissioningController({
1722
+ autoConnect: false,
1723
+ });
1724
+ this.log.info('Adding matter commissioning controller to matter server');
1725
+ await this.matterServer.addCommissioningController(this.commissioningController);
1726
+
1727
+ this.log.info('Starting matter server');
1728
+ await this.matterServer.start();
1729
+ this.log.info('Matter server started');
1730
+ const commissioningOptions: ControllerCommissioningFlowOptions = {
1731
+ regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1732
+ regulatoryCountryCode: 'XX',
1733
+ };
1734
+ const commissioningController = new CommissioningController({
1735
+ environment: {
1736
+ environment,
1737
+ id: uniqueId,
1738
+ },
1739
+ autoConnect: false, // Do not auto connect to the commissioned nodes
1740
+ adminFabricLabel,
1741
+ });
1742
+
1743
+ if (hasParameter('pairingcode')) {
1744
+ this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1745
+ const pairingCode = getParameter('pairingcode');
1746
+ const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
1747
+ const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
1748
+
1749
+ let longDiscriminator, setupPin, shortDiscriminator;
1750
+ if (pairingCode !== undefined) {
1751
+ const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1752
+ shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1753
+ longDiscriminator = undefined;
1754
+ setupPin = pairingCodeCodec.passcode;
1755
+ this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1756
+ } else {
1757
+ longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
1758
+ if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
1759
+ setupPin = this.controllerContext.get('pin', 20202021);
1760
+ }
1761
+ if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1762
+ throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1763
+ }
1764
+
1765
+ const options = {
1766
+ commissioning: commissioningOptions,
1767
+ discovery: {
1768
+ knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1769
+ identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1770
+ },
1771
+ passcode: setupPin,
1772
+ } as NodeCommissioningOptions;
1773
+ this.log.info('Commissioning with options:', options);
1774
+ const nodeId = await this.commissioningController.commissionNode(options);
1775
+ this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1776
+ this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1777
+ } // (hasParameter('pairingcode'))
1778
+
1779
+ if (hasParameter('unpairall')) {
1780
+ this.log.info('***Commissioning controller unpairing all nodes...');
1781
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1782
+ for (const nodeId of nodeIds) {
1783
+ this.log.info('***Commissioning controller unpairing node:', nodeId);
1784
+ await this.commissioningController.removeNode(nodeId);
1785
+ }
1786
+ return;
1787
+ }
1788
+
1789
+ if (hasParameter('discover')) {
1790
+ // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1791
+ // console.log(discover);
1792
+ }
1793
+
1794
+ if (!this.commissioningController.isCommissioned()) {
1795
+ this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1796
+ return;
1797
+ }
1798
+
1799
+ const nodeIds = this.commissioningController.getCommissionedNodes();
1800
+ this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1801
+ for (const nodeId of nodeIds) {
1802
+ this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1803
+
1804
+ const node = await this.commissioningController.connectNode(nodeId, {
1805
+ autoSubscribe: false,
1806
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
1807
+ this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1808
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
1809
+ this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1810
+ stateInformationCallback: (peerNodeId, info) => {
1811
+ switch (info) {
1812
+ case NodeStateInformation.Connected:
1813
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1814
+ break;
1815
+ case NodeStateInformation.Disconnected:
1816
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1817
+ break;
1818
+ case NodeStateInformation.Reconnecting:
1819
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1820
+ break;
1821
+ case NodeStateInformation.WaitingForDeviceDiscovery:
1822
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1823
+ break;
1824
+ case NodeStateInformation.StructureChanged:
1825
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1826
+ break;
1827
+ case NodeStateInformation.Decommissioned:
1828
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1829
+ break;
1830
+ default:
1831
+ this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1832
+ break;
1833
+ }
1834
+ },
1835
+ });
1836
+
1837
+ node.logStructure();
1838
+
1839
+ // Get the interaction client
1840
+ this.log.info('Getting the interaction client');
1841
+ const interactionClient = await node.getInteractionClient();
1842
+ let cluster;
1843
+ let attributes;
1844
+
1845
+ // Log BasicInformationCluster
1846
+ cluster = BasicInformationCluster;
1847
+ attributes = await interactionClient.getMultipleAttributes({
1848
+ attributes: [{ clusterId: cluster.id }],
1849
+ });
1850
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1851
+ attributes.forEach((attribute) => {
1852
+ this.log.info(
1853
+ `- 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}`,
1854
+ );
1855
+ });
1856
+
1857
+ // Log PowerSourceCluster
1858
+ cluster = PowerSourceCluster;
1859
+ attributes = await interactionClient.getMultipleAttributes({
1860
+ attributes: [{ clusterId: cluster.id }],
1861
+ });
1862
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1863
+ attributes.forEach((attribute) => {
1864
+ this.log.info(
1865
+ `- 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}`,
1866
+ );
1867
+ });
1868
+
1869
+ // Log ThreadNetworkDiagnostics
1870
+ cluster = ThreadNetworkDiagnosticsCluster;
1871
+ attributes = await interactionClient.getMultipleAttributes({
1872
+ attributes: [{ clusterId: cluster.id }],
1873
+ });
1874
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1875
+ attributes.forEach((attribute) => {
1876
+ this.log.info(
1877
+ `- 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}`,
1878
+ );
1879
+ });
1880
+
1881
+ // Log SwitchCluster
1882
+ cluster = SwitchCluster;
1883
+ attributes = await interactionClient.getMultipleAttributes({
1884
+ attributes: [{ clusterId: cluster.id }],
1885
+ });
1886
+ if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1887
+ attributes.forEach((attribute) => {
1888
+ this.log.info(
1889
+ `- 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}`,
1890
+ );
1891
+ });
1892
+
1893
+ this.log.info('Subscribing to all attributes and events');
1894
+ await node.subscribeAllAttributesAndEvents({
1895
+ ignoreInitialTriggers: false,
1896
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
1897
+ this.log.info(
1898
+ `***${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}`,
1899
+ ),
1900
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1901
+ this.log.info(
1902
+ `***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
1903
+ );
1904
+ },
1905
+ });
1906
+ this.log.info('Subscribed to all attributes and events');
1907
+ }
1908
+ */
1368
1909
  }
1910
+ /** */
1911
+ /** Matter.js methods */
1912
+ /** */
1913
+ /**
1914
+ * Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
1915
+ *
1916
+ * @returns {Promise<void>} - A promise that resolves when the storage is started.
1917
+ */
1369
1918
  async startMatterStorage() {
1919
+ // Setup Matter storage
1370
1920
  this.log.info(`Starting matter node storage...`);
1371
1921
  this.matterStorageService = this.environment.get(StorageService);
1372
1922
  this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
@@ -1375,8 +1925,17 @@ export class Matterbridge extends EventEmitter {
1375
1925
  this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
1376
1926
  this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
1377
1927
  this.log.info('Matter node storage started');
1928
+ // Backup matter storage since it is created/opened correctly
1378
1929
  await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1379
1930
  }
1931
+ /**
1932
+ * Makes a backup copy of the specified matter storage directory.
1933
+ *
1934
+ * @param {string} storageName - The name of the storage directory to be backed up.
1935
+ * @param {string} backupName - The name of the backup directory to be created.
1936
+ * @private
1937
+ * @returns {Promise<void>} A promise that resolves when the has been done.
1938
+ */
1380
1939
  async backupMatterStorage(storageName, backupName) {
1381
1940
  this.log.info('Creating matter node storage backup...');
1382
1941
  try {
@@ -1387,6 +1946,11 @@ export class Matterbridge extends EventEmitter {
1387
1946
  this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
1388
1947
  }
1389
1948
  }
1949
+ /**
1950
+ * Stops the matter storage.
1951
+ *
1952
+ * @returns {Promise<void>} A promise that resolves when the storage is stopped.
1953
+ */
1390
1954
  async stopMatterStorage() {
1391
1955
  this.log.info('Closing matter node storage...');
1392
1956
  await this.matterStorageManager?.close();
@@ -1395,6 +1959,20 @@ export class Matterbridge extends EventEmitter {
1395
1959
  this.matterbridgeContext = undefined;
1396
1960
  this.log.info('Matter node storage closed');
1397
1961
  }
1962
+ /**
1963
+ * Creates a server node storage context.
1964
+ *
1965
+ * @param {string} storeId - The storeId.
1966
+ * @param {string} deviceName - The name of the device.
1967
+ * @param {DeviceTypeId} deviceType - The device type of the device.
1968
+ * @param {number} vendorId - The vendor ID.
1969
+ * @param {string} vendorName - The vendor name.
1970
+ * @param {number} productId - The product ID.
1971
+ * @param {string} productName - The product name.
1972
+ * @param {string} [serialNumber] - The serial number of the device (optional).
1973
+ * @param {string} [uniqueId] - The unique ID of the device (optional).
1974
+ * @returns {Promise<StorageContext>} The storage context for the commissioning server.
1975
+ */
1398
1976
  async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
1399
1977
  const { randomBytes } = await import('node:crypto');
1400
1978
  if (!this.matterStorageService)
@@ -1434,6 +2012,15 @@ export class Matterbridge extends EventEmitter {
1434
2012
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1435
2013
  return storageContext;
1436
2014
  }
2015
+ /**
2016
+ * Creates a server node.
2017
+ *
2018
+ * @param {StorageContext} storageContext - The storage context for the server node.
2019
+ * @param {number} [port] - The port number for the server node. Defaults to 5540.
2020
+ * @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
2021
+ * @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
2022
+ * @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
2023
+ */
1437
2024
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1438
2025
  const storeId = await storageContext.get('storeId');
1439
2026
  this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
@@ -1443,24 +2030,37 @@ export class Matterbridge extends EventEmitter {
1443
2030
  this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1444
2031
  this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1445
2032
  this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2033
+ /**
2034
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
2035
+ */
1446
2036
  const serverNode = await ServerNode.create({
2037
+ // Required: Give the Node a unique ID which is used to store the state of this node
1447
2038
  id: storeId,
2039
+ // Provide Network relevant configuration like the port
2040
+ // Optional when operating only one device on a host, Default port is 5540
1448
2041
  network: {
1449
2042
  listeningAddressIpv4: this.ipv4address,
1450
2043
  listeningAddressIpv6: this.ipv6address,
1451
2044
  port,
1452
2045
  },
2046
+ // Provide the certificate for the device
1453
2047
  operationalCredentials: {
1454
2048
  certification: this.certification,
1455
2049
  },
2050
+ // Provide Commissioning relevant settings
2051
+ // Optional for development/testing purposes
1456
2052
  commissioning: {
1457
2053
  passcode,
1458
2054
  discriminator,
1459
2055
  },
2056
+ // Provide Node announcement settings
2057
+ // Optional: If Ommitted some development defaults are used
1460
2058
  productDescription: {
1461
2059
  name: await storageContext.get('deviceName'),
1462
2060
  deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1463
2061
  },
2062
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
2063
+ // Optional: If Omitted some development defaults are used
1464
2064
  basicInformation: {
1465
2065
  vendorId: VendorId(await storageContext.get('vendorId')),
1466
2066
  vendorName: await storageContext.get('vendorName'),
@@ -1477,14 +2077,20 @@ export class Matterbridge extends EventEmitter {
1477
2077
  reachable: true,
1478
2078
  },
1479
2079
  });
2080
+ /**
2081
+ * This event is triggered when the device is initially commissioned successfully.
2082
+ * This means: It is added to the first fabric.
2083
+ */
1480
2084
  serverNode.lifecycle.commissioned.on(() => {
1481
2085
  this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
1482
2086
  clearTimeout(this.endAdvertiseTimeout);
1483
2087
  });
2088
+ /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
1484
2089
  serverNode.lifecycle.decommissioned.on(() => {
1485
2090
  this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
1486
2091
  clearTimeout(this.endAdvertiseTimeout);
1487
2092
  });
2093
+ /** This event is triggered when the device went online. This means that it is discoverable in the network. */
1488
2094
  serverNode.lifecycle.online.on(async () => {
1489
2095
  this.log.notice(`Server node for ${storeId} is online`);
1490
2096
  if (!serverNode.lifecycle.isCommissioned) {
@@ -1492,11 +2098,14 @@ export class Matterbridge extends EventEmitter {
1492
2098
  const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
1493
2099
  this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
1494
2100
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
2101
+ // Set a timeout to show that advertising stops after 15 minutes if not commissioned
1495
2102
  this.startEndAdvertiseTimer(serverNode);
1496
2103
  this.advertisingNodes.set(storeId, Date.now());
1497
2104
  }
1498
2105
  else {
2106
+ // istanbul ignore next
1499
2107
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
2108
+ // istanbul ignore next
1500
2109
  this.advertisingNodes.delete(storeId);
1501
2110
  }
1502
2111
  this.frontend.wssSendRefreshRequired('plugins');
@@ -1505,23 +2114,28 @@ export class Matterbridge extends EventEmitter {
1505
2114
  this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
1506
2115
  this.emit('online', storeId);
1507
2116
  });
2117
+ /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
1508
2118
  serverNode.lifecycle.offline.on(() => {
1509
2119
  this.log.notice(`Server node for ${storeId} is offline`);
1510
- this.matterbridgeInformation.matterbridgeEndAdvertise = true;
2120
+ this.matterbridgeInformation.matterbridgeEndAdvertise = true; // Set the end advertise flag to true, so the frontend won't show the QR code anymore
1511
2121
  this.frontend.wssSendRefreshRequired('plugins');
1512
2122
  this.frontend.wssSendRefreshRequired('settings');
1513
2123
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1514
2124
  this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
1515
2125
  this.emit('offline', storeId);
1516
2126
  });
2127
+ /**
2128
+ * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
2129
+ * information is needed.
2130
+ */
1517
2131
  serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1518
2132
  let action = '';
1519
2133
  switch (fabricAction) {
1520
2134
  case FabricAction.Added:
1521
- this.advertisingNodes.delete(storeId);
2135
+ this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
1522
2136
  clearTimeout(this.endAdvertiseTimeout);
1523
2137
  this.endAdvertiseTimeout = undefined;
1524
- this.matterbridgeInformation.matterbridgeEndAdvertise = true;
2138
+ this.matterbridgeInformation.matterbridgeEndAdvertise = true; // Set the end advertise flag to true, so the frontend won't show the QR code anymore
1525
2139
  action = 'added';
1526
2140
  break;
1527
2141
  case FabricAction.Removed:
@@ -1535,16 +2149,24 @@ export class Matterbridge extends EventEmitter {
1535
2149
  this.frontend.wssSendRefreshRequired('fabrics');
1536
2150
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1537
2151
  });
2152
+ /**
2153
+ * This event is triggered when an operative new session was opened by a Controller.
2154
+ * It is not triggered for the initial commissioning process, just afterwards for real connections.
2155
+ */
1538
2156
  serverNode.events.sessions.opened.on((session) => {
1539
2157
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1540
2158
  this.frontend.wssSendRefreshRequired('sessions');
1541
2159
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1542
2160
  });
2161
+ /**
2162
+ * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
2163
+ */
1543
2164
  serverNode.events.sessions.closed.on((session) => {
1544
2165
  this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1545
2166
  this.frontend.wssSendRefreshRequired('sessions');
1546
2167
  this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
1547
2168
  });
2169
+ /** This event is triggered when a subscription gets added or removed on an operative session. */
1548
2170
  serverNode.events.sessions.subscriptionsChanged.on((session) => {
1549
2171
  this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1550
2172
  this.frontend.wssSendRefreshRequired('sessions');
@@ -1553,6 +2175,11 @@ export class Matterbridge extends EventEmitter {
1553
2175
  this.log.info(`Created server node for ${storeId}`);
1554
2176
  return serverNode;
1555
2177
  }
2178
+ /**
2179
+ * Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
2180
+ *
2181
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2182
+ */
1556
2183
  startEndAdvertiseTimer(matterServerNode) {
1557
2184
  if (this.endAdvertiseTimeout) {
1558
2185
  this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
@@ -1572,6 +2199,12 @@ export class Matterbridge extends EventEmitter {
1572
2199
  this.log.notice(`Advertising stopped.`);
1573
2200
  }, 15 * 60 * 1000).unref();
1574
2201
  }
2202
+ /**
2203
+ * Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
2204
+ *
2205
+ * @param {ServerNode} [serverNode] - The server node to start.
2206
+ * @returns {ApiDevicesMatter} The sanitized data of the server node.
2207
+ */
1575
2208
  getServerNodeData(serverNode) {
1576
2209
  const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
1577
2210
  return {
@@ -1587,12 +2220,25 @@ export class Matterbridge extends EventEmitter {
1587
2220
  serialNumber: serverNode.state.basicInformation.serialNumber,
1588
2221
  };
1589
2222
  }
2223
+ /**
2224
+ * Starts the specified server node.
2225
+ *
2226
+ * @param {ServerNode} [matterServerNode] - The server node to start.
2227
+ * @returns {Promise<void>} A promise that resolves when the server node has started.
2228
+ */
1590
2229
  async startServerNode(matterServerNode) {
1591
2230
  if (!matterServerNode)
1592
2231
  return;
1593
2232
  this.log.notice(`Starting ${matterServerNode.id} server node`);
1594
2233
  await matterServerNode.start();
1595
2234
  }
2235
+ /**
2236
+ * Stops the specified server node.
2237
+ *
2238
+ * @param {ServerNode} matterServerNode - The server node to stop.
2239
+ * @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
2240
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped.
2241
+ */
1596
2242
  async stopServerNode(matterServerNode, timeout = 30000) {
1597
2243
  if (!matterServerNode)
1598
2244
  return;
@@ -1605,6 +2251,12 @@ export class Matterbridge extends EventEmitter {
1605
2251
  this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
1606
2252
  }
1607
2253
  }
2254
+ /**
2255
+ * Advertises the specified server node.
2256
+ *
2257
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2258
+ * @returns {Promise<{ qrPairingCode: string, manualPairingCode: string } | undefined>} A promise that resolves to the pairing codes if the server node is advertised, or undefined if not.
2259
+ */
1608
2260
  async advertiseServerNode(matterServerNode) {
1609
2261
  if (matterServerNode) {
1610
2262
  await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
@@ -1614,6 +2266,12 @@ export class Matterbridge extends EventEmitter {
1614
2266
  return { qrPairingCode, manualPairingCode };
1615
2267
  }
1616
2268
  }
2269
+ /**
2270
+ * Stop advertise the specified server node.
2271
+ *
2272
+ * @param {ServerNode} [matterServerNode] - The server node to advertise.
2273
+ * @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
2274
+ */
1617
2275
  async stopAdvertiseServerNode(matterServerNode) {
1618
2276
  if (matterServerNode && matterServerNode.lifecycle.isOnline) {
1619
2277
  await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
@@ -1621,13 +2279,27 @@ export class Matterbridge extends EventEmitter {
1621
2279
  this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
1622
2280
  }
1623
2281
  }
2282
+ /**
2283
+ * Creates an aggregator node with the specified storage context.
2284
+ *
2285
+ * @param {StorageContext} storageContext - The storage context for the aggregator node.
2286
+ * @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
2287
+ */
1624
2288
  async createAggregatorNode(storageContext) {
1625
2289
  this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
1626
2290
  const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1627
2291
  this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
1628
2292
  return aggregatorNode;
1629
2293
  }
2294
+ /**
2295
+ * Adds a MatterbridgeEndpoint to the specified plugin.
2296
+ *
2297
+ * @param {string} pluginName - The name of the plugin.
2298
+ * @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
2299
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
2300
+ */
1630
2301
  async addBridgedEndpoint(pluginName, device) {
2302
+ // Check if the plugin is registered
1631
2303
  const plugin = this.plugins.get(pluginName);
1632
2304
  if (!plugin) {
1633
2305
  this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
@@ -1647,6 +2319,7 @@ export class Matterbridge extends EventEmitter {
1647
2319
  }
1648
2320
  else if (this.bridgeMode === 'bridge') {
1649
2321
  if (device.mode === 'matter') {
2322
+ // Register and add the device to the matterbridge server node
1650
2323
  this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
1651
2324
  if (!this.serverNode) {
1652
2325
  this.log.error('Server node not found for Matterbridge');
@@ -1663,6 +2336,7 @@ export class Matterbridge extends EventEmitter {
1663
2336
  }
1664
2337
  }
1665
2338
  else {
2339
+ // Register and add the device to the matterbridge aggregator node
1666
2340
  this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1667
2341
  if (!this.aggregatorNode) {
1668
2342
  this.log.error('Aggregator node not found for Matterbridge');
@@ -1680,6 +2354,7 @@ export class Matterbridge extends EventEmitter {
1680
2354
  }
1681
2355
  }
1682
2356
  else if (this.bridgeMode === 'childbridge') {
2357
+ // Register and add the device to the plugin server node
1683
2358
  if (plugin.type === 'AccessoryPlatform') {
1684
2359
  try {
1685
2360
  this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
@@ -1703,10 +2378,12 @@ export class Matterbridge extends EventEmitter {
1703
2378
  return;
1704
2379
  }
1705
2380
  }
2381
+ // Register and add the device to the plugin aggregator node
1706
2382
  if (plugin.type === 'DynamicPlatform') {
1707
2383
  try {
1708
2384
  this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
1709
2385
  await this.createDynamicPlugin(plugin);
2386
+ // Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
1710
2387
  await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
1711
2388
  if (!plugin.aggregatorNode) {
1712
2389
  this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
@@ -1729,17 +2406,28 @@ export class Matterbridge extends EventEmitter {
1729
2406
  plugin.registeredDevices++;
1730
2407
  if (plugin.addedDevices !== undefined)
1731
2408
  plugin.addedDevices++;
2409
+ // Add the device to the DeviceManager
1732
2410
  this.devices.set(device);
2411
+ // Subscribe to the reachable$Changed event
1733
2412
  await this.subscribeAttributeChanged(plugin, device);
1734
2413
  this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1735
2414
  }
2415
+ /**
2416
+ * Removes a MatterbridgeEndpoint from the specified plugin.
2417
+ *
2418
+ * @param {string} pluginName - The name of the plugin.
2419
+ * @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
2420
+ * @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
2421
+ */
1736
2422
  async removeBridgedEndpoint(pluginName, device) {
1737
2423
  this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
2424
+ // Check if the plugin is registered
1738
2425
  const plugin = this.plugins.get(pluginName);
1739
2426
  if (!plugin) {
1740
2427
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1741
2428
  return;
1742
2429
  }
2430
+ // Register and add the device to the matterbridge aggregator node
1743
2431
  if (this.bridgeMode === 'bridge') {
1744
2432
  if (!this.aggregatorNode) {
1745
2433
  this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
@@ -1754,6 +2442,7 @@ export class Matterbridge extends EventEmitter {
1754
2442
  }
1755
2443
  else if (this.bridgeMode === 'childbridge') {
1756
2444
  if (plugin.type === 'AccessoryPlatform') {
2445
+ // Nothing to do here since the server node has no aggregator node but only the device itself
1757
2446
  }
1758
2447
  else if (plugin.type === 'DynamicPlatform') {
1759
2448
  if (!plugin.aggregatorNode) {
@@ -1768,8 +2457,21 @@ export class Matterbridge extends EventEmitter {
1768
2457
  if (plugin.addedDevices !== undefined)
1769
2458
  plugin.addedDevices--;
1770
2459
  }
2460
+ // Remove the device from the DeviceManager
1771
2461
  this.devices.remove(device);
1772
2462
  }
2463
+ /**
2464
+ * Removes all bridged endpoints from the specified plugin.
2465
+ *
2466
+ * @param {string} pluginName - The name of the plugin.
2467
+ * @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
2468
+ * @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
2469
+ *
2470
+ * @remarks
2471
+ * This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
2472
+ * It also applies a delay between each removal if specified.
2473
+ * The delay is useful to allow the controllers to receive a single subscription for each device removed.
2474
+ */
1773
2475
  async removeAllBridgedEndpoints(pluginName, delay = 0) {
1774
2476
  this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
1775
2477
  for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
@@ -1780,6 +2482,15 @@ export class Matterbridge extends EventEmitter {
1780
2482
  if (delay > 0)
1781
2483
  await wait(2000);
1782
2484
  }
2485
+ /**
2486
+ * Subscribes to the attribute change event for the given device and plugin.
2487
+ * Specifically, it listens for changes in the 'reachable' attribute of the
2488
+ * BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
2489
+ *
2490
+ * @param {RegisteredPlugin} plugin - The plugin associated with the device.
2491
+ * @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
2492
+ * @returns {Promise<void>} A promise that resolves when the subscription is set up.
2493
+ */
1783
2494
  async subscribeAttributeChanged(plugin, device) {
1784
2495
  this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
1785
2496
  if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
@@ -1795,6 +2506,12 @@ export class Matterbridge extends EventEmitter {
1795
2506
  });
1796
2507
  }
1797
2508
  }
2509
+ /**
2510
+ * Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
2511
+ *
2512
+ * @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
2513
+ * @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
2514
+ */
1798
2515
  sanitizeFabricInformations(fabricInfo) {
1799
2516
  return fabricInfo.map((info) => {
1800
2517
  return {
@@ -1808,6 +2525,12 @@ export class Matterbridge extends EventEmitter {
1808
2525
  };
1809
2526
  });
1810
2527
  }
2528
+ /**
2529
+ * Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
2530
+ *
2531
+ * @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
2532
+ * @returns {SanitizedSession[]} An array of sanitized session information objects.
2533
+ */
1811
2534
  sanitizeSessionInformation(sessions) {
1812
2535
  return sessions
1813
2536
  .filter((session) => session.isPeerActive)
@@ -1834,7 +2557,21 @@ export class Matterbridge extends EventEmitter {
1834
2557
  };
1835
2558
  });
1836
2559
  }
2560
+ /**
2561
+ * Sets the reachability of the specified aggregator node bridged devices and trigger.
2562
+ *
2563
+ * @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
2564
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
2565
+ */
2566
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1837
2567
  async setAggregatorReachability(aggregatorNode, reachable) {
2568
+ /*
2569
+ for (const child of aggregatorNode.parts) {
2570
+ this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
2571
+ await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
2572
+ child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
2573
+ }
2574
+ */
1838
2575
  }
1839
2576
  getVendorIdName = (vendorId) => {
1840
2577
  if (!vendorId)
@@ -1874,10 +2611,11 @@ export class Matterbridge extends EventEmitter {
1874
2611
  case 0x1488:
1875
2612
  vendorName = '(ShortcutLabsFlic)';
1876
2613
  break;
1877
- case 65521:
2614
+ case 65521: // 0xFFF1
1878
2615
  vendorName = '(MatterTest)';
1879
2616
  break;
1880
2617
  }
1881
2618
  return vendorName;
1882
2619
  };
1883
2620
  }
2621
+ //# sourceMappingURL=matterbridge.js.map