matterbridge 3.2.6-dev-20250906-34345e5 → 3.2.6

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