homebridge 2.0.0-beta.44 → 2.0.0-beta.45

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 (321) hide show
  1. package/dist/api.d.ts +3 -365
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +13 -85
  4. package/dist/api.js.map +1 -1
  5. package/dist/bridgeService.d.ts +0 -25
  6. package/dist/bridgeService.d.ts.map +1 -1
  7. package/dist/bridgeService.js +32 -85
  8. package/dist/bridgeService.js.map +1 -1
  9. package/dist/childBridgeFork.d.ts +12 -15
  10. package/dist/childBridgeFork.d.ts.map +1 -1
  11. package/dist/childBridgeFork.js +126 -80
  12. package/dist/childBridgeFork.js.map +1 -1
  13. package/dist/childBridgeService.d.ts +21 -105
  14. package/dist/childBridgeService.d.ts.map +1 -1
  15. package/dist/childBridgeService.js +57 -134
  16. package/dist/childBridgeService.js.map +1 -1
  17. package/dist/cli.js +0 -2
  18. package/dist/cli.js.map +1 -1
  19. package/dist/externalPortService.d.ts +0 -21
  20. package/dist/externalPortService.d.ts.map +1 -1
  21. package/dist/externalPortService.js +0 -28
  22. package/dist/externalPortService.js.map +1 -1
  23. package/dist/index.d.ts +0 -112
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +0 -44
  26. package/dist/index.js.map +1 -1
  27. package/dist/ipcService.d.ts +23 -15
  28. package/dist/ipcService.d.ts.map +1 -1
  29. package/dist/ipcService.js +6 -12
  30. package/dist/ipcService.js.map +1 -1
  31. package/dist/logger.d.ts +0 -46
  32. package/dist/logger.d.ts.map +1 -1
  33. package/dist/logger.js +13 -57
  34. package/dist/logger.js.map +1 -1
  35. package/dist/matter/BaseMatterManager.d.ts +19 -0
  36. package/dist/matter/BaseMatterManager.d.ts.map +1 -0
  37. package/dist/matter/BaseMatterManager.js +170 -0
  38. package/dist/matter/BaseMatterManager.js.map +1 -0
  39. package/dist/matter/ChildBridgeMatterManager.d.ts +5 -69
  40. package/dist/matter/ChildBridgeMatterManager.d.ts.map +1 -1
  41. package/dist/matter/ChildBridgeMatterManager.js +36 -227
  42. package/dist/matter/ChildBridgeMatterManager.js.map +1 -1
  43. package/dist/matter/ClusterCommandMapper.d.ts +5 -0
  44. package/dist/matter/ClusterCommandMapper.d.ts.map +1 -0
  45. package/dist/matter/ClusterCommandMapper.js +203 -0
  46. package/dist/matter/ClusterCommandMapper.js.map +1 -0
  47. package/dist/matter/ExternalMatterAccessoryPublisher.d.ts +0 -27
  48. package/dist/matter/ExternalMatterAccessoryPublisher.d.ts.map +1 -1
  49. package/dist/matter/ExternalMatterAccessoryPublisher.js +2 -27
  50. package/dist/matter/ExternalMatterAccessoryPublisher.js.map +1 -1
  51. package/dist/matter/MatterAPIImpl.d.ts +0 -68
  52. package/dist/matter/MatterAPIImpl.d.ts.map +1 -1
  53. package/dist/matter/MatterAPIImpl.js +6 -106
  54. package/dist/matter/MatterAPIImpl.js.map +1 -1
  55. package/dist/matter/MatterBridgeManager.d.ts +9 -60
  56. package/dist/matter/MatterBridgeManager.d.ts.map +1 -1
  57. package/dist/matter/MatterBridgeManager.js +139 -215
  58. package/dist/matter/MatterBridgeManager.js.map +1 -1
  59. package/dist/matter/MatterConfigCollector.d.ts +1 -20
  60. package/dist/matter/MatterConfigCollector.d.ts.map +1 -1
  61. package/dist/matter/MatterConfigCollector.js +14 -27
  62. package/dist/matter/MatterConfigCollector.js.map +1 -1
  63. package/dist/matter/accessoryCache.d.ts +0 -48
  64. package/dist/matter/accessoryCache.d.ts.map +1 -1
  65. package/dist/matter/accessoryCache.js +1 -60
  66. package/dist/matter/accessoryCache.js.map +1 -1
  67. package/dist/matter/behaviors/AirQualityBehavior.d.ts +0 -42
  68. package/dist/matter/behaviors/AirQualityBehavior.d.ts.map +1 -1
  69. package/dist/matter/behaviors/AirQualityBehavior.js +0 -44
  70. package/dist/matter/behaviors/AirQualityBehavior.js.map +1 -1
  71. package/dist/matter/behaviors/BehaviorRegistry.d.ts +4 -42
  72. package/dist/matter/behaviors/BehaviorRegistry.d.ts.map +1 -1
  73. package/dist/matter/behaviors/BehaviorRegistry.js +12 -42
  74. package/dist/matter/behaviors/BehaviorRegistry.js.map +1 -1
  75. package/dist/matter/behaviors/ColorControlBehavior.d.ts +0 -49
  76. package/dist/matter/behaviors/ColorControlBehavior.d.ts.map +1 -1
  77. package/dist/matter/behaviors/ColorControlBehavior.js +0 -90
  78. package/dist/matter/behaviors/ColorControlBehavior.js.map +1 -1
  79. package/dist/matter/behaviors/ConcentrationMeasurementBehavior.d.ts +0 -91
  80. package/dist/matter/behaviors/ConcentrationMeasurementBehavior.d.ts.map +1 -1
  81. package/dist/matter/behaviors/ConcentrationMeasurementBehavior.js +0 -96
  82. package/dist/matter/behaviors/ConcentrationMeasurementBehavior.js.map +1 -1
  83. package/dist/matter/behaviors/DoorLockBehavior.d.ts +0 -11
  84. package/dist/matter/behaviors/DoorLockBehavior.d.ts.map +1 -1
  85. package/dist/matter/behaviors/DoorLockBehavior.js +0 -25
  86. package/dist/matter/behaviors/DoorLockBehavior.js.map +1 -1
  87. package/dist/matter/behaviors/FanControlBehavior.d.ts +0 -11
  88. package/dist/matter/behaviors/FanControlBehavior.d.ts.map +1 -1
  89. package/dist/matter/behaviors/FanControlBehavior.js +0 -25
  90. package/dist/matter/behaviors/FanControlBehavior.js.map +1 -1
  91. package/dist/matter/behaviors/IdentifyBehavior.d.ts +0 -11
  92. package/dist/matter/behaviors/IdentifyBehavior.d.ts.map +1 -1
  93. package/dist/matter/behaviors/IdentifyBehavior.js +0 -17
  94. package/dist/matter/behaviors/IdentifyBehavior.js.map +1 -1
  95. package/dist/matter/behaviors/LevelControlBehavior.d.ts +0 -20
  96. package/dist/matter/behaviors/LevelControlBehavior.d.ts.map +1 -1
  97. package/dist/matter/behaviors/LevelControlBehavior.js +0 -52
  98. package/dist/matter/behaviors/LevelControlBehavior.js.map +1 -1
  99. package/dist/matter/behaviors/OnOffBehavior.d.ts +0 -17
  100. package/dist/matter/behaviors/OnOffBehavior.d.ts.map +1 -1
  101. package/dist/matter/behaviors/OnOffBehavior.js +0 -38
  102. package/dist/matter/behaviors/OnOffBehavior.js.map +1 -1
  103. package/dist/matter/behaviors/RvcCleanModeBehavior.d.ts +0 -11
  104. package/dist/matter/behaviors/RvcCleanModeBehavior.d.ts.map +1 -1
  105. package/dist/matter/behaviors/RvcCleanModeBehavior.js +0 -17
  106. package/dist/matter/behaviors/RvcCleanModeBehavior.js.map +1 -1
  107. package/dist/matter/behaviors/RvcOperationalStateBehavior.d.ts +0 -11
  108. package/dist/matter/behaviors/RvcOperationalStateBehavior.d.ts.map +1 -1
  109. package/dist/matter/behaviors/RvcOperationalStateBehavior.js +0 -29
  110. package/dist/matter/behaviors/RvcOperationalStateBehavior.js.map +1 -1
  111. package/dist/matter/behaviors/RvcRunModeBehavior.d.ts +0 -11
  112. package/dist/matter/behaviors/RvcRunModeBehavior.d.ts.map +1 -1
  113. package/dist/matter/behaviors/RvcRunModeBehavior.js +0 -17
  114. package/dist/matter/behaviors/RvcRunModeBehavior.js.map +1 -1
  115. package/dist/matter/behaviors/ServiceAreaBehavior.d.ts +0 -11
  116. package/dist/matter/behaviors/ServiceAreaBehavior.d.ts.map +1 -1
  117. package/dist/matter/behaviors/ServiceAreaBehavior.js +0 -23
  118. package/dist/matter/behaviors/ServiceAreaBehavior.js.map +1 -1
  119. package/dist/matter/behaviors/ThermostatBehavior.d.ts +0 -11
  120. package/dist/matter/behaviors/ThermostatBehavior.d.ts.map +1 -1
  121. package/dist/matter/behaviors/ThermostatBehavior.js +0 -39
  122. package/dist/matter/behaviors/ThermostatBehavior.js.map +1 -1
  123. package/dist/matter/behaviors/WindowCoveringBehavior.d.ts +0 -17
  124. package/dist/matter/behaviors/WindowCoveringBehavior.d.ts.map +1 -1
  125. package/dist/matter/behaviors/WindowCoveringBehavior.js +0 -56
  126. package/dist/matter/behaviors/WindowCoveringBehavior.js.map +1 -1
  127. package/dist/matter/behaviors/index.d.ts +0 -5
  128. package/dist/matter/behaviors/index.d.ts.map +1 -1
  129. package/dist/matter/behaviors/index.js +0 -5
  130. package/dist/matter/behaviors/index.js.map +1 -1
  131. package/dist/matter/configValidator.d.ts +0 -55
  132. package/dist/matter/configValidator.d.ts.map +1 -1
  133. package/dist/matter/configValidator.js +1 -68
  134. package/dist/matter/configValidator.js.map +1 -1
  135. package/dist/matter/errorHandler.d.ts +0 -22
  136. package/dist/matter/errorHandler.d.ts.map +1 -1
  137. package/dist/matter/errorHandler.js +0 -32
  138. package/dist/matter/errorHandler.js.map +1 -1
  139. package/dist/matter/errors.d.ts +0 -132
  140. package/dist/matter/errors.d.ts.map +1 -1
  141. package/dist/matter/errors.js +0 -132
  142. package/dist/matter/errors.js.map +1 -1
  143. package/dist/matter/index.d.ts +0 -30
  144. package/dist/matter/index.d.ts.map +1 -1
  145. package/dist/matter/index.js +0 -13
  146. package/dist/matter/index.js.map +1 -1
  147. package/dist/matter/logFormatter.d.ts +0 -17
  148. package/dist/matter/logFormatter.d.ts.map +1 -1
  149. package/dist/matter/logFormatter.js +5 -63
  150. package/dist/matter/logFormatter.js.map +1 -1
  151. package/dist/matter/server.d.ts +12 -236
  152. package/dist/matter/server.d.ts.map +1 -1
  153. package/dist/matter/server.js +177 -488
  154. package/dist/matter/server.js.map +1 -1
  155. package/dist/matter/serverHelpers.d.ts +0 -56
  156. package/dist/matter/serverHelpers.d.ts.map +1 -1
  157. package/dist/matter/serverHelpers.js +1 -66
  158. package/dist/matter/serverHelpers.js.map +1 -1
  159. package/dist/matter/sharedTypes.d.ts +0 -83
  160. package/dist/matter/sharedTypes.d.ts.map +1 -1
  161. package/dist/matter/sharedTypes.js +0 -26
  162. package/dist/matter/sharedTypes.js.map +1 -1
  163. package/dist/matter/storage.d.ts +0 -90
  164. package/dist/matter/storage.d.ts.map +1 -1
  165. package/dist/matter/storage.js +2 -130
  166. package/dist/matter/storage.js.map +1 -1
  167. package/dist/matter/typeHelpers.d.ts +0 -30
  168. package/dist/matter/typeHelpers.d.ts.map +1 -1
  169. package/dist/matter/typeHelpers.js +0 -24
  170. package/dist/matter/typeHelpers.js.map +1 -1
  171. package/dist/matter/types.d.ts +0 -273
  172. package/dist/matter/types.d.ts.map +1 -1
  173. package/dist/matter/types.js +0 -83
  174. package/dist/matter/types.js.map +1 -1
  175. package/dist/platformAccessory.d.ts +0 -15
  176. package/dist/platformAccessory.d.ts.map +1 -1
  177. package/dist/platformAccessory.js +6 -32
  178. package/dist/platformAccessory.js.map +1 -1
  179. package/dist/plugin.d.ts +0 -3
  180. package/dist/plugin.d.ts.map +1 -1
  181. package/dist/plugin.js +6 -29
  182. package/dist/plugin.js.map +1 -1
  183. package/dist/pluginManager.d.ts +0 -22
  184. package/dist/pluginManager.d.ts.map +1 -1
  185. package/dist/pluginManager.js +18 -41
  186. package/dist/pluginManager.js.map +1 -1
  187. package/dist/server.d.ts +9 -29
  188. package/dist/server.d.ts.map +1 -1
  189. package/dist/server.js +304 -157
  190. package/dist/server.js.map +1 -1
  191. package/dist/user.d.ts +0 -3
  192. package/dist/user.d.ts.map +1 -1
  193. package/dist/user.js +2 -5
  194. package/dist/user.js.map +1 -1
  195. package/dist/util/mac.js +0 -1
  196. package/dist/util/mac.js.map +1 -1
  197. package/package.json +4 -4
  198. package/dist/api.spec.d.ts +0 -2
  199. package/dist/api.spec.d.ts.map +0 -1
  200. package/dist/api.spec.js +0 -413
  201. package/dist/api.spec.js.map +0 -1
  202. package/dist/logger.spec.d.ts +0 -2
  203. package/dist/logger.spec.d.ts.map +0 -1
  204. package/dist/logger.spec.js +0 -95
  205. package/dist/logger.spec.js.map +0 -1
  206. package/dist/matter/ExternalMatterAccessoryPublisher.spec.d.ts +0 -2
  207. package/dist/matter/ExternalMatterAccessoryPublisher.spec.d.ts.map +0 -1
  208. package/dist/matter/ExternalMatterAccessoryPublisher.spec.js +0 -293
  209. package/dist/matter/ExternalMatterAccessoryPublisher.spec.js.map +0 -1
  210. package/dist/matter/accessoryCache.spec.d.ts +0 -2
  211. package/dist/matter/accessoryCache.spec.d.ts.map +0 -1
  212. package/dist/matter/accessoryCache.spec.js +0 -452
  213. package/dist/matter/accessoryCache.spec.js.map +0 -1
  214. package/dist/matter/behaviors/AirQualityBehavior.spec.d.ts +0 -5
  215. package/dist/matter/behaviors/AirQualityBehavior.spec.d.ts.map +0 -1
  216. package/dist/matter/behaviors/AirQualityBehavior.spec.js +0 -46
  217. package/dist/matter/behaviors/AirQualityBehavior.spec.js.map +0 -1
  218. package/dist/matter/behaviors/BehaviorRegistry.spec.d.ts +0 -2
  219. package/dist/matter/behaviors/BehaviorRegistry.spec.d.ts.map +0 -1
  220. package/dist/matter/behaviors/BehaviorRegistry.spec.js +0 -307
  221. package/dist/matter/behaviors/BehaviorRegistry.spec.js.map +0 -1
  222. package/dist/matter/behaviors/ColorControlBehavior.spec.d.ts +0 -2
  223. package/dist/matter/behaviors/ColorControlBehavior.spec.d.ts.map +0 -1
  224. package/dist/matter/behaviors/ColorControlBehavior.spec.js +0 -29
  225. package/dist/matter/behaviors/ColorControlBehavior.spec.js.map +0 -1
  226. package/dist/matter/behaviors/ConcentrationMeasurementBehavior.spec.d.ts +0 -5
  227. package/dist/matter/behaviors/ConcentrationMeasurementBehavior.spec.d.ts.map +0 -1
  228. package/dist/matter/behaviors/ConcentrationMeasurementBehavior.spec.js +0 -95
  229. package/dist/matter/behaviors/ConcentrationMeasurementBehavior.spec.js.map +0 -1
  230. package/dist/matter/behaviors/DoorLockBehavior.spec.d.ts +0 -2
  231. package/dist/matter/behaviors/DoorLockBehavior.spec.d.ts.map +0 -1
  232. package/dist/matter/behaviors/DoorLockBehavior.spec.js +0 -120
  233. package/dist/matter/behaviors/DoorLockBehavior.spec.js.map +0 -1
  234. package/dist/matter/behaviors/FanControlBehavior.spec.d.ts +0 -2
  235. package/dist/matter/behaviors/FanControlBehavior.spec.d.ts.map +0 -1
  236. package/dist/matter/behaviors/FanControlBehavior.spec.js +0 -23
  237. package/dist/matter/behaviors/FanControlBehavior.spec.js.map +0 -1
  238. package/dist/matter/behaviors/IdentifyBehavior.spec.d.ts +0 -2
  239. package/dist/matter/behaviors/IdentifyBehavior.spec.d.ts.map +0 -1
  240. package/dist/matter/behaviors/IdentifyBehavior.spec.js +0 -64
  241. package/dist/matter/behaviors/IdentifyBehavior.spec.js.map +0 -1
  242. package/dist/matter/behaviors/LevelControlBehavior.spec.d.ts +0 -2
  243. package/dist/matter/behaviors/LevelControlBehavior.spec.d.ts.map +0 -1
  244. package/dist/matter/behaviors/LevelControlBehavior.spec.js +0 -145
  245. package/dist/matter/behaviors/LevelControlBehavior.spec.js.map +0 -1
  246. package/dist/matter/behaviors/OnOffBehavior.spec.d.ts +0 -2
  247. package/dist/matter/behaviors/OnOffBehavior.spec.d.ts.map +0 -1
  248. package/dist/matter/behaviors/OnOffBehavior.spec.js +0 -128
  249. package/dist/matter/behaviors/OnOffBehavior.spec.js.map +0 -1
  250. package/dist/matter/behaviors/RvcCleanModeBehavior.spec.d.ts +0 -2
  251. package/dist/matter/behaviors/RvcCleanModeBehavior.spec.d.ts.map +0 -1
  252. package/dist/matter/behaviors/RvcCleanModeBehavior.spec.js +0 -57
  253. package/dist/matter/behaviors/RvcCleanModeBehavior.spec.js.map +0 -1
  254. package/dist/matter/behaviors/RvcOperationalStateBehavior.spec.d.ts +0 -2
  255. package/dist/matter/behaviors/RvcOperationalStateBehavior.spec.d.ts.map +0 -1
  256. package/dist/matter/behaviors/RvcOperationalStateBehavior.spec.js +0 -55
  257. package/dist/matter/behaviors/RvcOperationalStateBehavior.spec.js.map +0 -1
  258. package/dist/matter/behaviors/RvcRunModeBehavior.spec.d.ts +0 -2
  259. package/dist/matter/behaviors/RvcRunModeBehavior.spec.d.ts.map +0 -1
  260. package/dist/matter/behaviors/RvcRunModeBehavior.spec.js +0 -57
  261. package/dist/matter/behaviors/RvcRunModeBehavior.spec.js.map +0 -1
  262. package/dist/matter/behaviors/ServiceAreaBehavior.spec.d.ts +0 -2
  263. package/dist/matter/behaviors/ServiceAreaBehavior.spec.d.ts.map +0 -1
  264. package/dist/matter/behaviors/ServiceAreaBehavior.spec.js +0 -53
  265. package/dist/matter/behaviors/ServiceAreaBehavior.spec.js.map +0 -1
  266. package/dist/matter/behaviors/ThermostatBehavior.spec.d.ts +0 -2
  267. package/dist/matter/behaviors/ThermostatBehavior.spec.d.ts.map +0 -1
  268. package/dist/matter/behaviors/ThermostatBehavior.spec.js +0 -23
  269. package/dist/matter/behaviors/ThermostatBehavior.spec.js.map +0 -1
  270. package/dist/matter/behaviors/WindowCoveringBehavior.spec.d.ts +0 -2
  271. package/dist/matter/behaviors/WindowCoveringBehavior.spec.d.ts.map +0 -1
  272. package/dist/matter/behaviors/WindowCoveringBehavior.spec.js +0 -27
  273. package/dist/matter/behaviors/WindowCoveringBehavior.spec.js.map +0 -1
  274. package/dist/matter/configValidator.spec.d.ts +0 -2
  275. package/dist/matter/configValidator.spec.d.ts.map +0 -1
  276. package/dist/matter/configValidator.spec.js +0 -390
  277. package/dist/matter/configValidator.spec.js.map +0 -1
  278. package/dist/matter/errorHandler.spec.d.ts +0 -2
  279. package/dist/matter/errorHandler.spec.d.ts.map +0 -1
  280. package/dist/matter/errorHandler.spec.js +0 -159
  281. package/dist/matter/errorHandler.spec.js.map +0 -1
  282. package/dist/matter/logFormatter.spec.d.ts +0 -2
  283. package/dist/matter/logFormatter.spec.d.ts.map +0 -1
  284. package/dist/matter/logFormatter.spec.js +0 -252
  285. package/dist/matter/logFormatter.spec.js.map +0 -1
  286. package/dist/matter/serverHelpers.spec.d.ts +0 -2
  287. package/dist/matter/serverHelpers.spec.d.ts.map +0 -1
  288. package/dist/matter/serverHelpers.spec.js +0 -527
  289. package/dist/matter/serverHelpers.spec.js.map +0 -1
  290. package/dist/matter/storage.spec.d.ts +0 -2
  291. package/dist/matter/storage.spec.d.ts.map +0 -1
  292. package/dist/matter/storage.spec.js +0 -570
  293. package/dist/matter/storage.spec.js.map +0 -1
  294. package/dist/matter/typeHelpers.spec.d.ts +0 -2
  295. package/dist/matter/typeHelpers.spec.d.ts.map +0 -1
  296. package/dist/matter/typeHelpers.spec.js +0 -127
  297. package/dist/matter/typeHelpers.spec.js.map +0 -1
  298. package/dist/platformAccessory.spec.d.ts +0 -2
  299. package/dist/platformAccessory.spec.d.ts.map +0 -1
  300. package/dist/platformAccessory.spec.js +0 -126
  301. package/dist/platformAccessory.spec.js.map +0 -1
  302. package/dist/pluginManager.spec.d.ts +0 -2
  303. package/dist/pluginManager.spec.d.ts.map +0 -1
  304. package/dist/pluginManager.spec.js +0 -43
  305. package/dist/pluginManager.spec.js.map +0 -1
  306. package/dist/server.spec.d.ts +0 -2
  307. package/dist/server.spec.d.ts.map +0 -1
  308. package/dist/server.spec.js +0 -57
  309. package/dist/server.spec.js.map +0 -1
  310. package/dist/user.spec.d.ts +0 -2
  311. package/dist/user.spec.d.ts.map +0 -1
  312. package/dist/user.spec.js +0 -31
  313. package/dist/user.spec.js.map +0 -1
  314. package/dist/util/mac.spec.d.ts +0 -2
  315. package/dist/util/mac.spec.d.ts.map +0 -1
  316. package/dist/util/mac.spec.js +0 -36
  317. package/dist/util/mac.spec.js.map +0 -1
  318. package/dist/version.spec.d.ts +0 -2
  319. package/dist/version.spec.d.ts.map +0 -1
  320. package/dist/version.spec.js +0 -20
  321. package/dist/version.spec.js.map +0 -1
@@ -1,9 +1,3 @@
1
- /**
2
- * Matter.js Server Implementation for Homebridge Plugin API
3
- *
4
- * This provides a Matter bridge that plugins can use to register
5
- * Matter accessories via the Homebridge API.
6
- */
7
1
  import { randomBytes } from 'node:crypto';
8
2
  import { EventEmitter } from 'node:events';
9
3
  import { constants } from 'node:fs';
@@ -32,21 +26,14 @@ import { MatterStorageManager } from './storage.js';
32
26
  import { isDeviceType, withBehaviors, withFeatures } from './typeHelpers.js';
33
27
  import { deviceTypes, MatterDeviceError, } from './types.js';
34
28
  const log = Logger.withPrefix('Matter/Server');
35
- /**
36
- * Constants for Matter server configuration
37
- */
38
29
  const DEFAULT_MATTER_PORT = 5540;
39
- const DEFAULT_VENDOR_ID = 0xFFF1; // test vendor ID from Matter spec
40
- const DEFAULT_PRODUCT_ID = 0x8001; // test product ID
41
- const MAX_DEVICES_PER_BRIDGE = 1000; // matter spec maximum devices per aggregator
30
+ const DEFAULT_VENDOR_ID = 0xFFF1;
31
+ const DEFAULT_PRODUCT_ID = 0x8001;
32
+ const MAX_DEVICES_PER_BRIDGE = 1000;
42
33
  const SERVER_READY_TIMEOUT_MS = 5000;
43
34
  const SERVER_READY_POLL_INTERVAL_MS = 100;
44
35
  const SERVER_INIT_DELAY_MS = 200;
45
36
  const MAX_PASSCODE_ATTEMPTS = 100;
46
- /**
47
- * Matter Server for Homebridge Plugin API
48
- * Allows plugins to register Matter accessories explicitly
49
- */
50
37
  export class MatterServer extends EventEmitter {
51
38
  config;
52
39
  serverNode = null;
@@ -56,28 +43,20 @@ export class MatterServer extends EventEmitter {
56
43
  isRunning = false;
57
44
  MAX_DEVICES = MAX_DEVICES_PER_BRIDGE;
58
45
  shutdownHandler = null;
59
- // Map cluster names to custom behavior classes
60
- // Only clusters with user-triggered commands need custom behaviors
61
46
  static CLUSTER_BEHAVIOR_MAP = {
62
- // Core controls
63
47
  onOff: HomebridgeOnOffServer,
64
48
  levelControl: HomebridgeLevelControlServer,
65
49
  colorControl: HomebridgeColorControlServer,
66
- // Coverings & locks
67
50
  windowCovering: HomebridgeWindowCoveringServer,
68
51
  doorLock: HomebridgeDoorLockServer,
69
- // Climate control
70
52
  fanControl: HomebridgeFanControlServer,
71
53
  thermostat: HomebridgeThermostatServer,
72
- // Robotic vacuum cleaners
73
54
  rvcOperationalState: HomebridgeRvcOperationalStateServer,
74
55
  rvcRunMode: HomebridgeRvcRunModeServer,
75
56
  rvcCleanMode: HomebridgeRvcCleanModeServer,
76
57
  serviceArea: HomebridgeServiceAreaServer,
77
- // Identification
78
58
  identify: HomebridgeIdentifyServer,
79
59
  };
80
- // Internal commissioning values (generated, not user-configurable)
81
60
  passcode = 0;
82
61
  discriminator = 0;
83
62
  vendorId;
@@ -88,12 +67,15 @@ export class MatterServer extends EventEmitter {
88
67
  storageManager = null;
89
68
  matterStoragePath;
90
69
  accessoryCache = null;
70
+ monitoringEnabled = false;
71
+ username;
72
+ bridgeName;
91
73
  constructor(config) {
92
74
  super();
93
- // Store the validated config
94
75
  this.config = this.validateAndSanitizeConfig(config);
95
- // Configure Matter.js library logging
96
- // Suppress DEBUG/INFO logs from Matter.js library unless debug mode is explicitly enabled
76
+ const cleanId = this.config.uniqueId.replace(/[^A-F0-9]/gi, '');
77
+ this.username = cleanId.match(/.{1,2}/g)?.slice(0, 6).join(':').toUpperCase() || this.config.uniqueId;
78
+ this.bridgeName = this.config.serialNumber ? `Matter Bridge ${this.config.serialNumber}` : 'Matter Bridge';
97
79
  if (this.config.debugModeEnabled) {
98
80
  log.info('Matter debug mode enabled - verbose logging active');
99
81
  MatterLogger.level = MatterLogLevel.DEBUG;
@@ -101,22 +83,15 @@ export class MatterServer extends EventEmitter {
101
83
  else {
102
84
  MatterLogger.level = MatterLogLevel.NOTICE;
103
85
  }
104
- // Set custom log format to match homebridge format
105
86
  MatterLogger.format = createHomebridgeLogFormatter();
106
- // Redirect all Matter.js logs to console.log to prevent console.debug() suppression.
107
- // Matter.js uses console.debug() for DEBUG level logs, which is silently ignored in many Node.js environments.
108
87
  MatterLogger.destinations.default.write = (text) => {
109
- // Skip empty strings to avoid blank lines (from suppressed log facilities)
110
88
  if (text.trim() !== '') {
111
- console.log(text); // eslint-disable-line no-console
89
+ console.log(text);
112
90
  }
113
91
  };
114
- // Initialize commissioning values (will be loaded from storage in start())
115
92
  this.vendorId = DEFAULT_VENDOR_ID;
116
93
  this.productId = DEFAULT_PRODUCT_ID;
117
- // Create behavior registry and set it on all behavior classes
118
- this.behaviorRegistry = new BehaviorRegistry(this.accessories);
119
- // Set the registry on all custom behavior classes
94
+ this.behaviorRegistry = new BehaviorRegistry(this.accessories, this);
120
95
  HomebridgeAirQualityServer.setRegistry(this.behaviorRegistry);
121
96
  HomebridgeCarbonMonoxideConcentrationMeasurementServer.setRegistry(this.behaviorRegistry);
122
97
  HomebridgeColorControlServer.setRegistry(this.behaviorRegistry);
@@ -136,19 +111,13 @@ export class MatterServer extends EventEmitter {
136
111
  HomebridgeThermostatServer.setRegistry(this.behaviorRegistry);
137
112
  HomebridgeWindowCoveringServer.setRegistry(this.behaviorRegistry);
138
113
  }
139
- /**
140
- * Validate and sanitize Matter server configuration
141
- * Throws descriptive errors if configuration is invalid
142
- */
143
114
  validateAndSanitizeConfig(config) {
144
115
  const errors = [];
145
- // Validate port
146
116
  const port = config.port || DEFAULT_MATTER_PORT;
147
117
  const portValidation = validatePort(port, false);
148
118
  if (!portValidation.valid) {
149
119
  errors.push(`Invalid port: ${portValidation.error}`);
150
120
  }
151
- // Validate and sanitize uniqueId (REQUIRED)
152
121
  if (!config.uniqueId) {
153
122
  errors.push('uniqueId is required for Matter server configuration');
154
123
  }
@@ -158,36 +127,28 @@ export class MatterServer extends EventEmitter {
158
127
  if (uniqueId.length === 0) {
159
128
  errors.push('Invalid uniqueId: must be a non-empty string');
160
129
  }
161
- // Validate storagePath (if provided)
162
130
  let storagePath = config.storagePath;
163
131
  if (storagePath !== undefined) {
164
- storagePath = resolve(storagePath); // resolve to absolute path
132
+ storagePath = resolve(storagePath);
165
133
  }
166
- // Validate and sanitize manufacturer
167
134
  let manufacturer = config.manufacturer;
168
135
  if (manufacturer !== undefined) {
169
136
  manufacturer = truncateString(manufacturer, 32, 'Manufacturer name').value;
170
137
  }
171
- // Validate and sanitize model
172
138
  let model = config.model;
173
139
  if (model !== undefined) {
174
140
  model = truncateString(model, 32, 'Model name').value;
175
141
  }
176
- // Validate firmwareRevision
177
142
  let firmwareRevision = config.firmwareRevision;
178
143
  if (firmwareRevision !== undefined) {
179
144
  firmwareRevision = truncateString(firmwareRevision, 64, 'Firmware revision').value;
180
145
  }
181
- // Validate serialNumber
182
146
  let serialNumber = config.serialNumber;
183
147
  if (serialNumber !== undefined) {
184
148
  serialNumber = truncateString(serialNumber, 32, 'Serial number').value;
185
149
  }
186
- // Validate debugModeEnabled
187
150
  const debugModeEnabled = config.debugModeEnabled || false;
188
- // Validate externalAccessory
189
151
  const externalAccessory = config.externalAccessory || false;
190
- // Throw if there are validation errors
191
152
  if (errors.length > 0) {
192
153
  throw new MatterDeviceError(`Matter configuration validation failed:\n${errors.map(e => ` - ${e}`).join('\n')}`);
193
154
  }
@@ -203,13 +164,6 @@ export class MatterServer extends EventEmitter {
203
164
  externalAccessory,
204
165
  };
205
166
  }
206
- /**
207
- * Generate a secure random passcode
208
- * According to Matter spec, passcode must be:
209
- * - 8 digits (00000001 to 99999998)
210
- * - Not in the invalid list
211
- * - Not sequential or repeating patterns
212
- */
213
167
  generateSecurePasscode() {
214
168
  let passcode;
215
169
  const maxAttempts = MAX_PASSCODE_ATTEMPTS;
@@ -229,9 +183,7 @@ export class MatterServer extends EventEmitter {
229
183
  87654321,
230
184
  ];
231
185
  do {
232
- // Use cryptographically secure random number generation
233
186
  const randomValue = randomBytes(4).readUInt32BE(0);
234
- // Generate a value between 1 and 99999998
235
187
  passcode = (randomValue % 99999998) + 1;
236
188
  attempts++;
237
189
  if (attempts > maxAttempts) {
@@ -241,17 +193,11 @@ export class MatterServer extends EventEmitter {
241
193
  || !this.isValidPasscode(passcode));
242
194
  return passcode;
243
195
  }
244
- /**
245
- * Validate a passcode according to Matter specifications
246
- */
247
196
  isValidPasscode(passcode) {
248
- // Must be between 1 and 99999998
249
197
  if (passcode < 1 || passcode > 99999998) {
250
198
  return false;
251
199
  }
252
- // Convert to 8-digit string
253
200
  const passcodeStr = passcode.toString().padStart(8, '0');
254
- // Check for sequential patterns (12345678, 23456789, etc.)
255
201
  let isSequential = true;
256
202
  for (let i = 1; i < passcodeStr.length; i++) {
257
203
  if (Number.parseInt(passcodeStr[i]) !== Number.parseInt(passcodeStr[i - 1]) + 1) {
@@ -262,7 +208,6 @@ export class MatterServer extends EventEmitter {
262
208
  if (isSequential) {
263
209
  return false;
264
210
  }
265
- // Check for reverse sequential (87654321, 76543210, etc.)
266
211
  let isReverseSequential = true;
267
212
  for (let i = 1; i < passcodeStr.length; i++) {
268
213
  if (Number.parseInt(passcodeStr[i]) !== Number.parseInt(passcodeStr[i - 1]) - 1) {
@@ -273,7 +218,6 @@ export class MatterServer extends EventEmitter {
273
218
  if (isReverseSequential) {
274
219
  return false;
275
220
  }
276
- // Check for too many repeating digits (more than 3 of same digit)
277
221
  const digitCounts = new Map();
278
222
  for (const digit of passcodeStr) {
279
223
  digitCounts.set(digit, (digitCounts.get(digit) || 0) + 1);
@@ -284,46 +228,18 @@ export class MatterServer extends EventEmitter {
284
228
  }
285
229
  return true;
286
230
  }
287
- /**
288
- * Generate a random discriminator
289
- * According to Matter spec, discriminator must be:
290
- * - 12 bits (0-4095)
291
- * - Should be random for security
292
- */
293
231
  generateRandomDiscriminator() {
294
- // Generate cryptographically secure random 12-bit discriminator (0-4095)
295
- const discriminator = randomBytes(2).readUInt16BE(0) & 0x0FFF; // Mask to 12 bits
296
- // Validate discriminator range
232
+ const discriminator = randomBytes(2).readUInt16BE(0) & 0x0FFF;
297
233
  if (discriminator < 0 || discriminator > 4095) {
298
234
  throw new Error(`Invalid discriminator generated: ${discriminator}`);
299
235
  }
300
236
  return discriminator;
301
237
  }
302
- /**
303
- * Create ServerNode with automatic recovery from corrupted storage
304
- *
305
- * Matter.js can fail to start if fabric data is corrupted (common after
306
- * hard shutdowns or disk errors). This method implements automatic recovery by:
307
- *
308
- * 1. Attempting normal ServerNode creation
309
- * 2. If it fails with storage errors, identifying and removing corrupted files
310
- * 3. Retrying ServerNode creation with fresh storage
311
- *
312
- * This prevents the need for manual intervention while preserving data
313
- * safety by only removing storage on confirmed corruption errors.
314
- *
315
- * @param nodeOptions - Matter.js ServerNode configuration
316
- * @param sanitizedId - Filesystem-safe bridge identifier
317
- * @returns Initialized ServerNode instance
318
- * @throws Error if recovery fails or error is not storage-related
319
- */
320
238
  async createServerNodeWithRecovery(nodeOptions, sanitizedId) {
321
239
  try {
322
- // First attempt to create ServerNode
323
240
  return await ServerNode.create(nodeOptions);
324
241
  }
325
242
  catch (error) {
326
- // Check if this is a storage corruption error
327
243
  const errorMessage = error instanceof Error ? error.message : '';
328
244
  const causeMessage = error instanceof Error && error.cause instanceof Error ? error.cause.message : '';
329
245
  const isStorageError = errorMessage.includes('Invalid public key encoding')
@@ -331,12 +247,9 @@ export class MatterServer extends EventEmitter {
331
247
  || errorMessage.includes('key-input')
332
248
  || causeMessage.includes('Invalid public key encoding');
333
249
  if (!isStorageError) {
334
- // Not a storage error, rethrow
335
250
  throw error;
336
251
  }
337
- // Storage is corrupted - clean up and retry
338
252
  log.warn('Detected corrupted Matter storage, attempting automatic recovery...');
339
- // The ServerNodeStore directory is inside our storage path with the same name as the bridge ID
340
253
  const environment = Environment.default;
341
254
  const storageService = environment.get(StorageService);
342
255
  const storageLocation = storageService.location;
@@ -347,7 +260,6 @@ export class MatterServer extends EventEmitter {
347
260
  const serverNodeStoreJsonFile = `${serverNodeStorePath}.json`;
348
261
  try {
349
262
  let removedSomething = false;
350
- // Delete the ServerNodeStore subdirectory (async check and removal)
351
263
  try {
352
264
  await fse.stat(serverNodeStorePath);
353
265
  log.info(`Removing corrupted ServerNodeStore directory: ${serverNodeStorePath}`);
@@ -360,7 +272,6 @@ export class MatterServer extends EventEmitter {
360
272
  throw err;
361
273
  }
362
274
  }
363
- // Delete the ServerNodeStore JSON file (contains fabric data)
364
275
  try {
365
276
  await fse.stat(serverNodeStoreJsonFile);
366
277
  log.info(`Removing corrupted ServerNodeStore JSON file: ${serverNodeStoreJsonFile}`);
@@ -379,7 +290,6 @@ export class MatterServer extends EventEmitter {
379
290
  else {
380
291
  log.warn('No corrupted storage files found, corruption may be elsewhere');
381
292
  }
382
- // Retry ServerNode creation
383
293
  const serverNode = await ServerNode.create(nodeOptions);
384
294
  log.info('Successfully recovered from corrupted Matter storage');
385
295
  return serverNode;
@@ -392,9 +302,6 @@ export class MatterServer extends EventEmitter {
392
302
  }
393
303
  }
394
304
  }
395
- /**
396
- * Start the Matter server
397
- */
398
305
  async start() {
399
306
  if (this.isRunning) {
400
307
  log.warn('Matter server is already running');
@@ -402,20 +309,14 @@ export class MatterServer extends EventEmitter {
402
309
  }
403
310
  try {
404
311
  log.info('Starting Matter.js server...');
405
- // IMPORTANT: Storage must be configured BEFORE any Matter.js operations
406
- // This ensures persistent fabric data across restarts
407
312
  await this.setupStorage();
408
- // Load or generate commissioning credentials
409
313
  await this.loadOrGenerateCredentials();
410
314
  log.info(`Configuration: Port=${this.config.port}, Passcode=${this.passcode}, Discriminator=${this.discriminator}`);
411
- // Configure network interfaces if specified in the config
412
315
  if (this.config.networkInterfaces && this.config.networkInterfaces.length > 0) {
413
316
  const environment = Environment.default;
414
317
  const interfaceConfig = {};
415
- // Map each interface name to type 2 (Ethernet) as default
416
- // Matter.js will use only these interfaces for the server
417
318
  for (const interfaceName of this.config.networkInterfaces) {
418
- interfaceConfig[interfaceName] = { type: 2 }; // 2 = Ethernet
319
+ interfaceConfig[interfaceName] = { type: 2 };
419
320
  }
420
321
  environment.vars.set('network.interface', interfaceConfig);
421
322
  log.info(`Configured Matter server to use network interfaces: ${this.config.networkInterfaces.join(', ')}`);
@@ -423,33 +324,29 @@ export class MatterServer extends EventEmitter {
423
324
  else {
424
325
  log.debug('No network interfaces specified, using all available interfaces');
425
326
  }
426
- // Create commissioning options
427
327
  const commissioningOptions = {
428
328
  passcode: this.passcode,
429
329
  discriminator: this.discriminator,
430
330
  };
431
331
  log.info(`Using commissioning credentials: passcode=${this.passcode}, discriminator=${this.discriminator}`);
432
- // Use different names based on mode
433
332
  const displayName = this.config.externalAccessory
434
333
  ? (this.config.model || 'Matter Device')
435
334
  : 'Homebridge Matter Bridge';
436
- // uniqueId is already sanitized in validateAndSanitizeConfig()
437
335
  const sanitizedId = this.config.uniqueId;
438
- // Create node options with proper typing
439
336
  const nodeOptions = {
440
337
  id: sanitizedId,
441
338
  network: {
442
339
  port: this.config.port,
443
- ipv4: true, // Always enable IPv4 for Matter
340
+ ipv4: true,
444
341
  },
445
342
  commissioning: commissioningOptions,
446
343
  basicInformation: {
447
- nodeLabel: displayName.slice(0, 32), // Maximum 32 characters
344
+ nodeLabel: displayName.slice(0, 32),
448
345
  vendorId: VendorId(this.vendorId),
449
346
  vendorName: (this.config.manufacturer || 'Homebridge').slice(0, 32),
450
347
  productId: this.productId,
451
348
  productName: displayName.slice(0, 32),
452
- productLabel: displayName.slice(0, 64), // Maximum 64 characters
349
+ productLabel: displayName.slice(0, 64),
453
350
  serialNumber: this.serialNumber = this.config.serialNumber || this.config.uniqueId,
454
351
  hardwareVersion: 1,
455
352
  hardwareVersionString: release(),
@@ -458,51 +355,39 @@ export class MatterServer extends EventEmitter {
458
355
  reachable: true,
459
356
  },
460
357
  };
461
- // Only add productDescription with bridge deviceType in bridge mode
462
358
  if (!this.config.externalAccessory) {
463
359
  nodeOptions.productDescription = {
464
360
  name: displayName,
465
361
  deviceType: AggregatorEndpoint.deviceType,
466
362
  };
467
363
  }
468
- // Create server node with automatic recovery from corrupted storage
469
364
  this.serverNode = await this.createServerNodeWithRecovery(nodeOptions, sanitizedId);
470
- // Set up commissioning event listeners
471
365
  this.setupCommissioningEventListeners();
472
- // Create aggregator endpoint for bridge pattern (skip for external accessories)
473
366
  if (!this.config.externalAccessory) {
474
367
  this.aggregator = new Endpoint(AggregatorEndpoint, {
475
368
  id: 'homebridge-aggregator',
476
369
  });
477
- // Add aggregator to server
478
370
  await this.serverNode.add(this.aggregator);
479
371
  log.debug('Created aggregator endpoint for bridged mode');
480
372
  }
481
373
  else {
482
374
  log.debug('External accessory mode - skipping aggregator creation');
483
375
  }
484
- // Generate and display commissioning information
485
376
  await this.generateCommissioningInfo();
486
- // Set up graceful shutdown handler
487
377
  this.shutdownHandler = async () => {
488
378
  log.info('Shutting down Matter server...');
489
379
  await this.stop();
490
380
  };
491
- // Register shutdown handlers
492
381
  process.on('SIGINT', this.shutdownHandler);
493
382
  process.on('SIGTERM', this.shutdownHandler);
494
- // If external accessory mode, skip running the server now (will be run later via runServer())
495
383
  if (!this.config.externalAccessory) {
496
- // Start the server in a non-blocking way
497
384
  this.serverNode.run().then(() => {
498
385
  log.info('Matter server stopped normally');
499
386
  }, (error) => {
500
387
  log.error('Matter server stopped with error:', error);
501
388
  errorHandler.handleError(error, 'server-runtime');
502
389
  });
503
- // Wait for server to be ready
504
390
  await this.waitForServerReady();
505
- // Load cached accessories (don't restore them yet - wait for plugins to re-register)
506
391
  if (this.accessoryCache) {
507
392
  const loaded = await this.accessoryCache.load();
508
393
  log.debug(`Matter cache loaded: ${loaded.size} accessories`);
@@ -510,7 +395,6 @@ export class MatterServer extends EventEmitter {
510
395
  else {
511
396
  log.debug('No accessory cache available');
512
397
  }
513
- // Update commissioning file to reflect current state
514
398
  this.updateCommissioningFile().catch((error) => {
515
399
  log.warn('Failed to update commissioning file on startup:', error);
516
400
  });
@@ -528,20 +412,6 @@ export class MatterServer extends EventEmitter {
528
412
  throw error;
529
413
  }
530
414
  }
531
- /**
532
- * Run the server after devices have been added (for external accessory mode)
533
- *
534
- * This must be called after registerPlatformAccessories() when using externalAccessory mode.
535
- * In bridge mode, the server starts automatically when accessories are registered.
536
- *
537
- * @throws {MatterDeviceError} If server node is not initialized or server is already running
538
- * @example
539
- * ```typescript
540
- * await matterServer.start()
541
- * await matterServer.registerPlatformAccessories('plugin', 'platform', accessories)
542
- * await matterServer.runServer() // External accessory mode only
543
- * ```
544
- */
545
415
  async runServer() {
546
416
  if (!this.serverNode) {
547
417
  throw new MatterDeviceError('Server node not initialized - call start() first');
@@ -554,16 +424,13 @@ export class MatterServer extends EventEmitter {
554
424
  throw new MatterDeviceError('runServer() should only be called when externalAccessory mode is enabled');
555
425
  }
556
426
  log.debug('Running deferred server with device(s) already attached');
557
- // Start the server in a non-blocking way
558
427
  this.serverNode.run().then(() => {
559
428
  log.info('Matter server stopped normally');
560
429
  }, (error) => {
561
430
  log.error('Matter server stopped with error:', error);
562
431
  errorHandler.handleError(error, 'server-runtime');
563
432
  });
564
- // Wait for server to be ready
565
433
  await this.waitForServerReady();
566
- // Load cached accessories (don't restore them yet - wait for plugins to re-register)
567
434
  if (this.accessoryCache) {
568
435
  const loaded = await this.accessoryCache.load();
569
436
  log.debug(`Matter cache loaded: ${loaded.size} accessories`);
@@ -571,34 +438,27 @@ export class MatterServer extends EventEmitter {
571
438
  else {
572
439
  log.debug('No accessory cache available');
573
440
  }
574
- // Update commissioning file to reflect current state
575
441
  this.updateCommissioningFile().catch((error) => {
576
442
  log.warn('Failed to update commissioning file on startup:', error);
577
443
  });
578
444
  this.isRunning = true;
579
445
  log.info('Matter server is now running');
580
446
  }
581
- /**
582
- * Set up and validate storage
583
- */
584
447
  async setupStorage() {
585
448
  if (!this.config.storagePath) {
586
449
  throw new Error('Storage path is required for Matter server');
587
450
  }
588
- // Resolve to absolute path and validate
589
451
  const storagePath = resolve(this.config.storagePath);
590
452
  const normalizedPath = normalize(storagePath);
591
- // Ensure path is within allowed directories
592
453
  const allowedBasePaths = [
593
454
  resolve(homedir(), '.homebridge'),
594
455
  resolve(process.cwd()),
595
- '/var/lib/homebridge', // Common system location
456
+ '/var/lib/homebridge',
596
457
  ];
597
458
  const isAllowed = allowedBasePaths.some(basePath => normalizedPath.startsWith(basePath));
598
459
  if (!isAllowed || normalizedPath.includes('..')) {
599
460
  throw new Error(`Storage path not allowed: ${normalizedPath}. Must be within homebridge directories.`);
600
461
  }
601
- // Ensure the storage directory exists with proper permissions
602
462
  try {
603
463
  await fse.ensureDir(normalizedPath);
604
464
  await access(normalizedPath, constants.R_OK | constants.W_OK);
@@ -606,34 +466,24 @@ export class MatterServer extends EventEmitter {
606
466
  catch (error) {
607
467
  throw new Error(`Storage path not accessible: ${error}`);
608
468
  }
609
- // Create bridge-specific storage directory
610
- // uniqueId is already sanitized in validateAndSanitizeConfig()
611
469
  const bridgeId = this.config.uniqueId || 'default';
612
470
  this.matterStoragePath = join(normalizedPath, bridgeId);
613
471
  await fse.ensureDir(this.matterStoragePath);
614
- // Create storage manager
615
472
  this.storageManager = new MatterStorageManager(this.matterStoragePath);
616
- // Create accessory cache
617
473
  this.accessoryCache = new MatterAccessoryCache(normalizedPath, bridgeId);
618
- // Configure environment to use our custom storage
619
474
  const environment = Environment.default;
620
475
  const storageService = environment.get(StorageService);
621
476
  storageService.location = this.matterStoragePath;
622
- // CRITICAL: Override storage factory with custom implementation
623
- // This ensures fabric data is properly persisted
624
477
  storageService.factory = (namespace) => {
625
478
  if (!this.storageManager) {
626
479
  throw new Error('Storage manager not initialized');
627
480
  }
628
481
  const storage = this.storageManager.getStorage(namespace);
629
- // Initialize asynchronously - Matter.js handles async storage properly
630
482
  storage.initialize().catch((error) => {
631
483
  log.error(`Failed to initialize storage namespace ${namespace}:`, error);
632
484
  });
633
- // Note: Cast to unknown first to satisfy TypeScript - our storage implements the required interface
634
485
  return storage;
635
486
  };
636
- // Add cleanup handler for storage
637
487
  this.cleanupHandlers.push(async () => {
638
488
  if (this.storageManager) {
639
489
  await this.storageManager.closeAll();
@@ -641,71 +491,54 @@ export class MatterServer extends EventEmitter {
641
491
  });
642
492
  log.info(`Matter storage initialized at: ${this.matterStoragePath}`);
643
493
  }
644
- /**
645
- * Load or generate commissioning credentials (passcode and discriminator)
646
- * These must be persistent across restarts to maintain the same QR code
647
- */
648
494
  async loadOrGenerateCredentials() {
649
495
  if (!this.storageManager) {
650
496
  throw new Error('Storage manager not initialized');
651
497
  }
652
- // Use 'credentials' namespace
653
498
  const storage = this.storageManager.getStorage('credentials');
654
- // CRITICAL: Initialize storage before reading to avoid race condition
655
499
  await storage.initialize();
656
- // Try to load existing credentials
657
500
  const storedPasscode = storage.get([], 'passcode');
658
501
  const storedDiscriminator = storage.get([], 'discriminator');
659
502
  if (storedPasscode && storedDiscriminator) {
660
- // Use stored credentials
661
503
  log.info('Loading existing commissioning credentials from storage');
662
504
  this.passcode = storedPasscode;
663
505
  this.discriminator = storedDiscriminator;
664
506
  }
665
507
  else {
666
- // Generate new credentials and store them
667
508
  log.info('Generating new commissioning credentials');
668
509
  this.passcode = this.generateSecurePasscode();
669
510
  this.discriminator = this.generateRandomDiscriminator();
670
- // Store for future use
671
511
  storage.set([], 'passcode', this.passcode);
672
512
  storage.set([], 'discriminator', this.discriminator);
673
513
  log.info('Commissioning credentials saved to storage');
674
514
  }
675
515
  }
676
- /**
677
- * Generate and display commissioning information
678
- */
679
516
  async generateCommissioningInfo() {
680
517
  const passcode = this.passcode.toString().padStart(8, '0');
681
518
  const discriminator = this.discriminator;
682
519
  const vendorId = this.vendorId;
683
520
  const productId = this.productId;
684
- // Use Matter.js library to generate pairing codes properly
685
521
  const manualCode = ManualPairingCodeCodec.encode({
686
522
  discriminator,
687
523
  passcode: this.passcode,
688
524
  });
689
- // Format as XXXX-XXX-XXXX for display
690
525
  const manualPairingCode = `${manualCode.slice(0, 4)}-${manualCode.slice(4, 7)}-${manualCode.slice(7, 11)}`;
691
526
  log.info(`Encoding QR code with: passcode=${this.passcode}, discriminator=${discriminator}, vendorId=${vendorId}, productId=${productId}`);
692
527
  const qrCodePayload = QrPairingCodeCodec.encode([{
693
528
  version: 0,
694
529
  vendorId,
695
530
  productId,
696
- flowType: 0, // Standard commissioning flow
697
- discoveryCapabilities: 4, // OnNetwork=4
531
+ flowType: 0,
532
+ discoveryCapabilities: 4,
698
533
  discriminator,
699
534
  passcode: this.passcode,
700
535
  }]);
701
536
  log.info(`Generated QR code: ${qrCodePayload}`);
702
537
  log.info(`Generated manual code: ${manualPairingCode}`);
703
- // Store commissioning info
704
538
  this.commissioningInfo = {
705
539
  qrCode: qrCodePayload,
706
540
  manualPairingCode,
707
541
  };
708
- // Save commissioning info to disk for UI access
709
542
  try {
710
543
  if (!this.matterStoragePath) {
711
544
  throw new Error('Matter storage path not initialized');
@@ -726,7 +559,6 @@ export class MatterServer extends EventEmitter {
726
559
  const errorMessage = error instanceof Error ? error.message : String(error);
727
560
  log.warn(`Failed to save commissioning info to disk: ${errorMessage}`);
728
561
  }
729
- // Display commissioning information
730
562
  log.info(`${'='.repeat(60)}`);
731
563
  log.info('📱 MATTER COMMISSIONING INFORMATION');
732
564
  log.info('='.repeat(60));
@@ -734,33 +566,21 @@ export class MatterServer extends EventEmitter {
734
566
  log.info(`Passcode: ${passcode}`);
735
567
  log.info(`Discriminator: ${discriminator}`);
736
568
  log.info('QR Code for commissioning:');
737
- // Generate and display QR code in terminal
738
569
  QRCode.generate(qrCodePayload, { small: true }, (qrcode) => {
739
- // eslint-disable-next-line no-console
740
570
  console.log(qrcode);
741
571
  });
742
572
  log.info(`${'='.repeat(60)}`);
743
573
  }
744
- /**
745
- * Wait for the server to be ready
746
- */
747
574
  async waitForServerReady(maxWaitTime = SERVER_READY_TIMEOUT_MS) {
748
575
  const startTime = Date.now();
749
- // In external accessory mode, only wait for serverNode (no aggregator)
750
- // In bridge mode, wait for both serverNode and aggregator
751
576
  while (!this.serverNode || (!this.config.externalAccessory && !this.aggregator)) {
752
577
  if (Date.now() - startTime > maxWaitTime) {
753
578
  throw new Error('Server failed to become ready within timeout');
754
579
  }
755
580
  await new Promise(resolve => setTimeout(resolve, SERVER_READY_POLL_INTERVAL_MS));
756
581
  }
757
- // Additional small delay to ensure everything is initialized
758
582
  await new Promise(resolve => setTimeout(resolve, SERVER_INIT_DELAY_MS));
759
583
  }
760
- /**
761
- * Set up Matter.js commissioning event listeners
762
- * Uses native Matter.js events instead of file watching for reliability
763
- */
764
584
  setupCommissioningEventListeners() {
765
585
  if (!this.serverNode) {
766
586
  log.warn('Cannot set up commissioning event listeners - serverNode not initialized');
@@ -768,37 +588,28 @@ export class MatterServer extends EventEmitter {
768
588
  }
769
589
  log.debug('Setting up commissioning event listeners');
770
590
  try {
771
- // Listen for fabric changes (add/remove/update)
772
591
  this.serverNode.events.commissioning.fabricsChanged.on((fabricIndex, action) => {
773
592
  log.info(`Fabric ${action}: index ${fabricIndex}`);
774
- // Update commissioning file when fabrics change
775
593
  this.updateCommissioningFile().catch((error) => {
776
594
  log.warn('Failed to update commissioning file after fabric change:', error);
777
595
  });
778
- // Emit event for child bridge to update UI
779
596
  const commissioned = this.isCommissioned();
780
597
  const fabricCount = this.getCommissionedFabricCount();
781
598
  this.emit('commissioning-status-changed', commissioned, fabricCount);
782
599
  });
783
- // Listen for commissioning (first fabric added)
784
600
  this.serverNode.events.commissioning.commissioned.on(() => {
785
601
  log.info('Bridge commissioned');
786
- // Update commissioning file
787
602
  this.updateCommissioningFile().catch((error) => {
788
603
  log.warn('Failed to update commissioning file after commissioning:', error);
789
604
  });
790
- // Emit event for child bridge to update UI
791
605
  const fabricCount = this.getCommissionedFabricCount();
792
606
  this.emit('commissioning-status-changed', true, fabricCount);
793
607
  });
794
- // Listen for decommissioning (last fabric removed)
795
608
  this.serverNode.events.commissioning.decommissioned.on(() => {
796
609
  log.info('Bridge decommissioned');
797
- // Update commissioning file
798
610
  this.updateCommissioningFile().catch((error) => {
799
611
  log.warn('Failed to update commissioning file after decommissioning:', error);
800
612
  });
801
- // Emit event for child bridge to update UI
802
613
  this.emit('commissioning-status-changed', false, 0);
803
614
  });
804
615
  log.debug('Commissioning event listeners registered successfully');
@@ -807,9 +618,6 @@ export class MatterServer extends EventEmitter {
807
618
  log.error('Failed to set up commissioning event listeners:', error);
808
619
  }
809
620
  }
810
- /**
811
- * Update commissioning info file when commissioning state changes
812
- */
813
621
  async updateCommissioningFile() {
814
622
  try {
815
623
  if (!this.matterStoragePath) {
@@ -834,38 +642,16 @@ export class MatterServer extends EventEmitter {
834
642
  log.debug(`Failed to update commissioning info file: ${errorMessage}`);
835
643
  }
836
644
  }
837
- /**
838
- * Register Matter platform accessories (Plugin API - matches HAP pattern)
839
- *
840
- * Registers Matter accessories from a dynamic platform plugin. Accessories are stored
841
- * and automatically restored on server restart.
842
- *
843
- * @param pluginIdentifier - The plugin identifier (e.g., 'homebridge-example')
844
- * @param platformName - The platform name from config.json
845
- * @param accessories - Array of Matter accessories to register
846
- * @throws {MatterDeviceError} If maximum device limit is reached or accessory is invalid
847
- * @see {@link MatterAccessory} for accessory structure
848
- */
849
645
  async registerPlatformAccessories(pluginIdentifier, platformName, accessories) {
850
646
  for (const accessory of accessories) {
851
647
  await this.registerAccessory(pluginIdentifier, platformName, accessory);
852
648
  }
853
649
  }
854
- /**
855
- * Unregister Matter platform accessories (Plugin API - matches HAP pattern)
856
- */
857
650
  async unregisterPlatformAccessories(pluginIdentifier, platformName, accessories) {
858
651
  for (const accessory of accessories) {
859
652
  await this.unregisterAccessory(accessory.uuid);
860
653
  }
861
654
  }
862
- /**
863
- * Update Matter platform accessories in the cache
864
- * Similar to api.updatePlatformAccessories() for HAP accessories
865
- *
866
- * This updates the cached accessory information without unregistering and re-registering.
867
- * Useful when device metadata changes (name, manufacturer, firmware version, etc.)
868
- */
869
655
  async updatePlatformAccessories(accessories) {
870
656
  if (!this.accessoryCache) {
871
657
  log.warn('Cannot update Matter platform accessories - cache not initialized');
@@ -873,7 +659,6 @@ export class MatterServer extends EventEmitter {
873
659
  }
874
660
  for (const accessory of accessories) {
875
661
  const internal = accessory;
876
- // Verify accessory exists in current session and cache
877
662
  if (!this.accessories.has(accessory.uuid)) {
878
663
  log.warn(`Cannot update Matter accessory ${accessory.uuid} - not registered in current session`);
879
664
  continue;
@@ -882,25 +667,16 @@ export class MatterServer extends EventEmitter {
882
667
  log.warn(`Cannot update Matter accessory ${accessory.uuid} - not found in cache`);
883
668
  continue;
884
669
  }
885
- // Update the in-memory accessory
886
670
  this.accessories.set(accessory.uuid, internal);
887
671
  log.debug(`Updated Matter accessory ${accessory.uuid} (${accessory.displayName})`);
888
672
  }
889
- // Save updated accessories to cache
890
673
  this.accessoryCache.requestSave(this.accessories);
891
674
  }
892
- /**
893
- * Register a single Matter accessory (internal method)
894
- */
895
675
  async registerAccessory(pluginIdentifier, platformName, accessory) {
896
- // In external accessory mode, only check for serverNode (no aggregator).
897
- // In bridge mode, check for both serverNode and aggregator.
898
676
  if (!this.serverNode || (!this.config.externalAccessory && !this.aggregator)) {
899
677
  throw new MatterDeviceError('Matter server not started');
900
678
  }
901
- // Validate required fields
902
679
  validateAccessoryRequiredFields(accessory);
903
- // Check if already registered (during this session)
904
680
  if (this.accessories.has(accessory.uuid)) {
905
681
  const existing = this.accessories.get(accessory.uuid);
906
682
  throw new MatterDeviceError(`Matter accessory with UUID "${accessory.uuid}" is already registered.\n`
@@ -908,43 +684,33 @@ export class MatterServer extends EventEmitter {
908
684
  + `New accessory: "${accessory.displayName}"\n`
909
685
  + 'Each accessory must have a unique UUID. Use api.hap.uuid.generate() with a unique string.');
910
686
  }
911
- // Restore cached state if available
912
687
  this.restoreCachedState(accessory);
913
- // Check device limit
914
688
  if (this.accessories.size >= this.MAX_DEVICES) {
915
689
  throw new MatterDeviceError(`Cannot register Matter accessory "${accessory.displayName}": `
916
690
  + `Maximum device limit reached (${this.MAX_DEVICES} devices).\n`
917
691
  + `Current registered devices: ${this.accessories.size}`);
918
692
  }
919
693
  try {
920
- // Prepare device type with WindowCovering features
921
694
  let deviceType = accessory.deviceType;
922
695
  const windowCoveringFeatures = detectWindowCoveringFeatures(accessory);
923
696
  if (windowCoveringFeatures.length > 0) {
924
697
  deviceType = applyWindowCoveringFeatures(deviceType, accessory, windowCoveringFeatures);
925
698
  }
926
- // Detect cluster features for behavior configuration
927
699
  const features = this.detectClusterFeatures(accessory, deviceType);
928
- // Build and apply custom behaviors based on handlers
929
700
  const customBehaviors = await this.buildCustomBehaviors(accessory, deviceType, features);
930
701
  if (customBehaviors.length > 0) {
931
702
  deviceType = withBehaviors(deviceType, customBehaviors);
932
703
  log.info(`Applied ${customBehaviors.length} custom behavior(s) to device type`);
933
704
  }
934
- // Add BridgedDeviceBasicInformationServer for bridged devices only
935
- // This is required by the Matter spec for devices behind an aggregator
936
- // External accessories should NOT have this cluster
937
705
  if (!this.config.externalAccessory) {
938
706
  deviceType = withBehaviors(deviceType, [BridgedDeviceBasicInformationServer]);
939
707
  log.debug(`Added BridgedDeviceBasicInformationServer to ${accessory.displayName}`);
940
708
  }
941
- // Create endpoint with cluster states
942
709
  const endpointOptions = this.createEndpointOptions(accessory);
943
710
  const endpoint = new Endpoint(deviceType, endpointOptions);
944
711
  if (this.config.debugModeEnabled) {
945
712
  log.debug(`Created endpoint for ${accessory.displayName} with initial cluster states`);
946
713
  }
947
- // Add endpoint to aggregator or serverNode depending on mode
948
714
  if (this.config.externalAccessory) {
949
715
  await this.serverNode.add(endpoint);
950
716
  log.debug(`Added ${accessory.displayName} as external accessory to ServerNode`);
@@ -955,11 +721,8 @@ export class MatterServer extends EventEmitter {
955
721
  log.debug(`Added endpoint for ${accessory.displayName} to aggregator`);
956
722
  }
957
723
  }
958
- // Register command handlers
959
724
  this.registerAccessoryHandlers(accessory);
960
- // Create and register child endpoints (parts)
961
725
  const internalParts = await this.createAccessoryParts(accessory);
962
- // Finalize registration (store, emit events, save cache)
963
726
  await this.finalizeAccessoryRegistration(accessory, endpoint, internalParts);
964
727
  }
965
728
  catch (error) {
@@ -967,30 +730,21 @@ export class MatterServer extends EventEmitter {
967
730
  throw new MatterDeviceError(`Failed to register accessory: ${error}`);
968
731
  }
969
732
  }
970
- /**
971
- * Restore cached state for an accessory
972
- */
973
733
  restoreCachedState(accessory) {
974
- // Check if there's a cached version - merge cached cluster states with new registration.
975
- // This ensures state persistence across Homebridge restarts.
976
734
  if (this.accessoryCache && this.accessoryCache.hasCached(accessory.uuid)) {
977
735
  const cached = this.accessoryCache.getCached(accessory.uuid);
978
736
  if (cached?.clusters && accessory.clusters) {
979
- // Merge cached cluster states with new ones (prefer cached state to persist values across restarts)
980
737
  for (const [clusterName, cachedAttrs] of Object.entries(cached.clusters)) {
981
738
  if (!accessory.clusters[clusterName]) {
982
- // Cluster exists in cache but not in new registration - preserve it
983
739
  accessory.clusters[clusterName] = cachedAttrs;
984
740
  }
985
741
  else {
986
- // Cluster exists in both - merge (prefer cached state over initial values)
987
742
  accessory.clusters[clusterName] = {
988
743
  ...accessory.clusters[clusterName],
989
744
  ...cachedAttrs,
990
745
  };
991
746
  }
992
747
  }
993
- // Restore context if available
994
748
  if (cached.context) {
995
749
  accessory.context = cached.context;
996
750
  }
@@ -998,22 +752,14 @@ export class MatterServer extends EventEmitter {
998
752
  }
999
753
  }
1000
754
  }
1001
- /**
1002
- * Detect cluster features for an accessory
1003
- * Returns an object containing detected features for various clusters
1004
- */
1005
755
  detectClusterFeatures(accessory, deviceType) {
1006
- // Detect WindowCovering features
1007
756
  const windowCoveringFeatures = detectWindowCoveringFeatures(accessory);
1008
- // Detect ServiceArea features
1009
757
  let serviceAreaFeatures = null;
1010
758
  if (accessory.clusters?.serviceArea) {
1011
759
  const features = [];
1012
- // Check if Maps feature should be enabled (when supportedMaps is defined)
1013
760
  if (accessory.clusters.serviceArea.supportedMaps) {
1014
761
  features.push('Maps');
1015
762
  }
1016
- // Check if ProgressReporting feature should be enabled (when progress is defined)
1017
763
  if (accessory.clusters.serviceArea.progress !== undefined) {
1018
764
  features.push('ProgressReporting');
1019
765
  }
@@ -1022,7 +768,6 @@ export class MatterServer extends EventEmitter {
1022
768
  log.info(`ServiceArea features will be enabled for ${accessory.displayName}: ${features.join(', ')}`);
1023
769
  }
1024
770
  }
1025
- // Detect ColorControl features
1026
771
  let colorControlFeatures = null;
1027
772
  if (accessory.handlers?.colorControl) {
1028
773
  colorControlFeatures = detectBehaviorFeatures(deviceType, CLUSTER_IDS.COLOR_CONTROL, extractColorControlFeatures);
@@ -1030,7 +775,6 @@ export class MatterServer extends EventEmitter {
1030
775
  colorControlFeatures = determineColorControlFeaturesFromHandlers(accessory.handlers.colorControl);
1031
776
  }
1032
777
  }
1033
- // Detect Thermostat features
1034
778
  let thermostatFeatures = null;
1035
779
  if (accessory.handlers?.thermostat) {
1036
780
  thermostatFeatures = detectBehaviorFeatures(deviceType, CLUSTER_IDS.THERMOSTAT, extractThermostatFeatures);
@@ -1042,42 +786,29 @@ export class MatterServer extends EventEmitter {
1042
786
  thermostatFeatures,
1043
787
  };
1044
788
  }
1045
- /**
1046
- * Build custom behaviors for an accessory based on handlers
1047
- */
1048
789
  async buildCustomBehaviors(accessory, deviceType, features) {
1049
790
  const customBehaviors = [];
1050
791
  if (!accessory.handlers) {
1051
792
  return customBehaviors;
1052
793
  }
1053
794
  log.debug(`[${accessory.displayName}] Has handlers: ${Object.keys(accessory.handlers).join(', ')}`);
1054
- // Use the static cluster behavior map
1055
795
  const behaviorMap = MatterServer.CLUSTER_BEHAVIOR_MAP;
1056
- // For RoboticVacuumCleaner, add optional clusters if they're defined in accessory.clusters
1057
- // These clusters need to be added to the device type even if there are no handlers
1058
796
  if (isDeviceType(deviceType, devices.RoboticVacuumCleanerDevice)) {
1059
- // Import RVC requirements
1060
797
  const { RvcCleanModeServer, ServiceAreaServer } = devices.RoboticVacuumCleanerRequirements;
1061
- // Add RvcCleanMode if defined in clusters
1062
798
  if (accessory.clusters?.rvcCleanMode) {
1063
- // Check if there's a custom behavior with handlers
1064
799
  if (accessory.handlers?.rvcCleanMode) {
1065
800
  const behaviorClass = HomebridgeRvcCleanModeServer;
1066
801
  customBehaviors.push(behaviorClass);
1067
802
  log.info('Adding custom RvcCleanMode behavior with handlers');
1068
803
  }
1069
804
  else {
1070
- // No handlers, use base server
1071
805
  customBehaviors.push(RvcCleanModeServer);
1072
806
  log.info('Adding base RvcCleanMode server');
1073
807
  }
1074
808
  }
1075
- // Add ServiceArea if defined in clusters
1076
809
  if (accessory.clusters?.serviceArea) {
1077
- // Check if there's a custom behavior with handlers
1078
810
  if (accessory.handlers?.serviceArea) {
1079
811
  let behaviorClass = HomebridgeServiceAreaServer;
1080
- // Apply features if detected
1081
812
  if (features.serviceAreaFeatures && features.serviceAreaFeatures.length > 0) {
1082
813
  behaviorClass = withFeatures(behaviorClass, features.serviceAreaFeatures);
1083
814
  log.info(`ServiceArea custom behavior will have features: ${features.serviceAreaFeatures.join(', ')}`);
@@ -1086,7 +817,6 @@ export class MatterServer extends EventEmitter {
1086
817
  log.info('Adding custom ServiceArea behavior with handlers');
1087
818
  }
1088
819
  else {
1089
- // No handlers, use base server with features
1090
820
  let behaviorClass = ServiceAreaServer;
1091
821
  if (features.serviceAreaFeatures && features.serviceAreaFeatures.length > 0) {
1092
822
  behaviorClass = withFeatures(behaviorClass, features.serviceAreaFeatures);
@@ -1096,9 +826,7 @@ export class MatterServer extends EventEmitter {
1096
826
  log.info('Adding base ServiceArea server');
1097
827
  }
1098
828
  }
1099
- // Add PowerSource if defined in clusters (for battery percentage)
1100
829
  if (accessory.clusters?.powerSource) {
1101
- // Detect Battery feature from cluster config
1102
830
  const hasBattery = accessory.clusters.powerSource.batPercentRemaining !== undefined
1103
831
  || accessory.clusters.powerSource.batChargeLevel !== undefined;
1104
832
  let powerSourceBehavior = PowerSourceServer;
@@ -1113,34 +841,27 @@ export class MatterServer extends EventEmitter {
1113
841
  }
1114
842
  }
1115
843
  for (const clusterName of Object.keys(accessory.handlers || {})) {
1116
- // Skip windowCovering if we already applied features via base WindowCoveringServer
1117
844
  const skipWindowCoveringBehavior = accessory.context?._skipWindowCoveringBehavior;
1118
845
  if (clusterName === 'windowCovering' && skipWindowCoveringBehavior) {
1119
846
  log.debug('Skipping custom WindowCovering behavior (using base server with features instead)');
1120
847
  continue;
1121
848
  }
1122
- // Skip RVC clusters - they're handled specially above for RoboticVacuumCleaner
1123
849
  if (clusterName === 'rvcCleanMode' || clusterName === 'serviceArea' || clusterName === 'powerSource') {
1124
850
  continue;
1125
851
  }
1126
852
  let behaviorClass = behaviorMap[clusterName];
1127
- // Apply ColorControl features if we detected them earlier
1128
853
  if (clusterName === 'colorControl' && behaviorClass && features.colorControlFeatures && features.colorControlFeatures.length > 0) {
1129
854
  behaviorClass = withFeatures(behaviorClass, features.colorControlFeatures);
1130
855
  log.info(`ColorControl custom behavior will preserve features: ${features.colorControlFeatures.join(', ')}`);
1131
856
  }
1132
- // Apply Thermostat features if we detected them earlier
1133
857
  if (clusterName === 'thermostat' && behaviorClass && features.thermostatFeatures && features.thermostatFeatures.length > 0) {
1134
858
  behaviorClass = withFeatures(behaviorClass, features.thermostatFeatures);
1135
859
  log.info(`Thermostat custom behavior will preserve features: ${features.thermostatFeatures.join(', ')}`);
1136
860
  }
1137
- // Apply ServiceArea features if we detected them earlier
1138
861
  if (clusterName === 'serviceArea' && behaviorClass && features.serviceAreaFeatures && features.serviceAreaFeatures.length > 0) {
1139
862
  behaviorClass = withFeatures(behaviorClass, features.serviceAreaFeatures);
1140
863
  log.info(`ServiceArea custom behavior will preserve features: ${features.serviceAreaFeatures.join(', ')}`);
1141
864
  }
1142
- // Apply WindowCovering features to custom behavior as well
1143
- // (features were already applied to base device type, but custom behavior needs them too)
1144
865
  if (clusterName === 'windowCovering') {
1145
866
  log.debug(`WindowCovering handler found: behaviorClass=${!!behaviorClass}, windowCoveringFeatures=${features.windowCoveringFeatures}, length=${features.windowCoveringFeatures?.length}`);
1146
867
  if (behaviorClass && features.windowCoveringFeatures && features.windowCoveringFeatures.length > 0) {
@@ -1161,20 +882,15 @@ export class MatterServer extends EventEmitter {
1161
882
  }
1162
883
  return customBehaviors;
1163
884
  }
1164
- /**
1165
- * Create endpoint options for an accessory
1166
- */
1167
885
  createEndpointOptions(accessory) {
1168
886
  const endpointOptions = {
1169
887
  id: accessory.uuid,
1170
- ...accessory.clusters, // Spread cluster states as initial values
888
+ ...accessory.clusters,
1171
889
  };
1172
- // Add bridgedDeviceBasicInformation cluster only for bridged devices
1173
- // For external accessories, use the root basicInformation instead
1174
890
  if (!this.config.externalAccessory) {
1175
891
  endpointOptions.bridgedDeviceBasicInformation = {
1176
892
  vendorName: accessory.manufacturer,
1177
- nodeLabel: accessory.displayName, // Main end user name for the device
893
+ nodeLabel: accessory.displayName,
1178
894
  productName: accessory.model,
1179
895
  productLabel: accessory.displayName,
1180
896
  serialNumber: accessory.serialNumber,
@@ -1183,15 +899,11 @@ export class MatterServer extends EventEmitter {
1183
899
  }
1184
900
  return endpointOptions;
1185
901
  }
1186
- /**
1187
- * Register command handlers for an accessory
1188
- */
1189
902
  registerAccessoryHandlers(accessory) {
1190
903
  if (!accessory.handlers) {
1191
904
  return;
1192
905
  }
1193
906
  log.info(`Setting up handlers for accessory ${accessory.uuid}`);
1194
- // Register handlers with the custom behavior classes
1195
907
  for (const [clusterName, handlers] of Object.entries(accessory.handlers)) {
1196
908
  log.info(` Processing cluster: ${clusterName}`);
1197
909
  for (const [commandName, handler] of Object.entries(handlers)) {
@@ -1199,9 +911,6 @@ export class MatterServer extends EventEmitter {
1199
911
  }
1200
912
  }
1201
913
  }
1202
- /**
1203
- * Create and register child endpoints (parts) for an accessory
1204
- */
1205
914
  async createAccessoryParts(accessory) {
1206
915
  const internalParts = [];
1207
916
  if (!accessory.parts || accessory.parts.length === 0) {
@@ -1209,15 +918,11 @@ export class MatterServer extends EventEmitter {
1209
918
  }
1210
919
  log.info(`Creating ${accessory.parts.length} child endpoint(s) for ${accessory.displayName}`);
1211
920
  for (const part of accessory.parts) {
1212
- // Create unique endpoint ID for this part
1213
921
  const partEndpointId = `${accessory.uuid}-part-${part.id}`;
1214
- // Register the part endpoint mapping for handler context
1215
922
  this.behaviorRegistry.registerPartEndpoint(partEndpointId, accessory.uuid, part.id);
1216
- // Apply custom behaviors to part based on its handlers (same logic as main accessory)
1217
923
  let partDeviceType = part.deviceType;
1218
924
  const partCustomBehaviors = [];
1219
925
  if (part.handlers) {
1220
- // Use the static cluster behavior map for parts as well
1221
926
  const partBehaviorMap = MatterServer.CLUSTER_BEHAVIOR_MAP;
1222
927
  for (const clusterName of Object.keys(part.handlers)) {
1223
928
  const behaviorClass = partBehaviorMap[clusterName];
@@ -1230,21 +935,17 @@ export class MatterServer extends EventEmitter {
1230
935
  }
1231
936
  }
1232
937
  if (partCustomBehaviors.length > 0) {
1233
- // Add custom behaviors to part device type
1234
938
  partDeviceType = withBehaviors(partDeviceType, partCustomBehaviors);
1235
939
  log.info(` Applied ${partCustomBehaviors.length} custom behavior(s) to part ${part.id}`);
1236
940
  }
1237
941
  }
1238
- // Add BridgedDeviceBasicInformationServer for bridged parts
1239
942
  if (!this.config.externalAccessory) {
1240
943
  partDeviceType = withBehaviors(partDeviceType, [BridgedDeviceBasicInformationServer]);
1241
944
  }
1242
- // Create endpoint options with cluster states
1243
945
  const partEndpointOptions = {
1244
946
  id: partEndpointId,
1245
947
  ...part.clusters,
1246
948
  };
1247
- // Add bridgedDeviceBasicInformation for the part
1248
949
  if (!this.config.externalAccessory) {
1249
950
  partEndpointOptions.bridgedDeviceBasicInformation = {
1250
951
  vendorName: accessory.manufacturer,
@@ -1255,9 +956,7 @@ export class MatterServer extends EventEmitter {
1255
956
  reachable: true,
1256
957
  };
1257
958
  }
1258
- // Create the part endpoint
1259
959
  const partEndpoint = new Endpoint(partDeviceType, partEndpointOptions);
1260
- // Add part endpoint to aggregator or serverNode
1261
960
  if (this.config.externalAccessory) {
1262
961
  await this.serverNode.add(partEndpoint);
1263
962
  }
@@ -1265,17 +964,14 @@ export class MatterServer extends EventEmitter {
1265
964
  await this.aggregator.add(partEndpoint);
1266
965
  }
1267
966
  log.info(` Created part endpoint: ${part.displayName || part.id} (${partEndpointId})`);
1268
- // Set up handlers for this part
1269
967
  if (part.handlers) {
1270
968
  for (const [clusterName, handlers] of Object.entries(part.handlers)) {
1271
969
  for (const [commandName, handler] of Object.entries(handlers)) {
1272
- // Register handler with the part's endpoint ID
1273
970
  this.behaviorRegistry.registerHandler(partEndpointId, clusterName, commandName, handler);
1274
971
  }
1275
972
  }
1276
973
  log.debug(` Registered ${Object.keys(part.handlers).length} handler(s) for part ${part.id}`);
1277
974
  }
1278
- // Store the internal part
1279
975
  internalParts.push({
1280
976
  ...part,
1281
977
  endpoint: partEndpoint,
@@ -1283,13 +979,7 @@ export class MatterServer extends EventEmitter {
1283
979
  }
1284
980
  return internalParts;
1285
981
  }
1286
- /**
1287
- * Finalize accessory registration (store, emit events, save cache)
1288
- */
1289
982
  async finalizeAccessoryRegistration(accessory, endpoint, internalParts) {
1290
- // Store accessory with internal metadata and event emitter
1291
- // The event emitter allows plugins to listen for lifecycle events (currently only 'ready')
1292
- // Note: _associatedPlugin and _associatedPlatform are already set by MatterAPIImpl
1293
983
  const internalAccessory = {
1294
984
  ...accessory,
1295
985
  endpoint,
@@ -1302,23 +992,25 @@ export class MatterServer extends EventEmitter {
1302
992
  if (this.config.debugModeEnabled) {
1303
993
  log.debug(`Total registered accessories: ${this.accessories.size}/${this.MAX_DEVICES}`);
1304
994
  }
1305
- // Notify controllers about the new device (parts list changed)
1306
- // This allows the Home app to discover new devices without re-pairing
1307
995
  await this.notifyPartsListChanged();
1308
- // Request debounced save to cache (reduces disk I/O during rapid registration)
1309
996
  if (this.accessoryCache) {
1310
997
  this.accessoryCache.requestSave(this.accessories);
1311
998
  }
999
+ if (this.monitoringEnabled && process.send) {
1000
+ const event = {
1001
+ type: 'accessoryAdded',
1002
+ data: { uuid: accessory.uuid },
1003
+ };
1004
+ process.send({
1005
+ id: "matterEvent",
1006
+ data: event,
1007
+ });
1008
+ }
1312
1009
  }
1313
- /**
1314
- * Unregister a Matter accessory (Plugin API)
1315
- */
1316
1010
  async unregisterAccessory(uuid) {
1317
1011
  const accessory = this.accessories.get(uuid);
1318
1012
  if (!accessory) {
1319
- // Accessory not in memory, but might be in cache - still remove from cache
1320
1013
  log.debug(`Accessory ${uuid} not found or not registered`);
1321
- // Check if it exists in cache and remove it
1322
1014
  if (this.accessoryCache && this.accessoryCache.getCached(uuid)) {
1323
1015
  log.debug(`Removing ${uuid} from cache`);
1324
1016
  this.accessoryCache.removeCached(uuid);
@@ -1333,41 +1025,36 @@ export class MatterServer extends EventEmitter {
1333
1025
  }
1334
1026
  this.accessories.delete(uuid);
1335
1027
  log.info(`Unregistered Matter accessory: ${accessory.displayName} (${uuid})`);
1336
- // Notify controllers about the removed device (parts list changed)
1337
1028
  await this.notifyPartsListChanged();
1338
- // Update cache (remove the accessory)
1339
1029
  if (this.accessoryCache) {
1340
1030
  this.accessoryCache.removeCached(uuid);
1341
1031
  this.accessoryCache.requestSave(this.accessories);
1342
1032
  }
1033
+ if (this.monitoringEnabled && process.send) {
1034
+ const event = {
1035
+ type: 'accessoryRemoved',
1036
+ data: { uuid },
1037
+ };
1038
+ process.send({
1039
+ id: "matterEvent",
1040
+ data: event,
1041
+ });
1042
+ }
1343
1043
  }
1344
1044
  catch (error) {
1345
1045
  log.error(`Failed to unregister Matter accessory ${uuid}:`, error);
1346
1046
  throw new MatterDeviceError(`Failed to unregister accessory: ${error}`);
1347
1047
  }
1348
1048
  }
1349
- /**
1350
- * Update a Matter accessory's state (Plugin API)
1351
- *
1352
- * This method can be called from anywhere, including from within handlers.
1353
- * State updates are automatically deferred to avoid transaction conflicts.
1354
- *
1355
- * @param uuid - The UUID of the accessory
1356
- * @param cluster - The cluster name
1357
- * @param attributes - The attributes to update
1358
- * @param partId - Optional: ID of the part to update (for composed devices)
1359
- */
1360
1049
  async updateAccessoryState(uuid, cluster, attributes, partId) {
1361
1050
  const accessory = this.accessories.get(uuid);
1362
1051
  if (!accessory) {
1363
1052
  throw new MatterDeviceError(`Accessory ${uuid} not found or not registered`);
1364
1053
  }
1365
- // Determine which endpoint to update
1366
1054
  let targetEndpoint;
1367
1055
  let targetClusters;
1368
1056
  let displayName;
1369
1057
  if (partId) {
1370
- // Update a specific part
1371
1058
  const part = accessory._parts?.find(p => p.id === partId);
1372
1059
  if (!part || !part.endpoint) {
1373
1060
  throw new MatterDeviceError(`Part ${partId} not found in accessory ${uuid}`);
@@ -1377,7 +1064,6 @@ export class MatterServer extends EventEmitter {
1377
1064
  displayName = part.displayName || `${accessory.displayName} - ${partId}`;
1378
1065
  }
1379
1066
  else {
1380
- // Update the main accessory
1381
1067
  if (!accessory.endpoint) {
1382
1068
  throw new MatterDeviceError(`Accessory ${uuid} not registered or missing endpoint`);
1383
1069
  }
@@ -1385,18 +1071,11 @@ export class MatterServer extends EventEmitter {
1385
1071
  targetClusters = accessory.clusters;
1386
1072
  displayName = accessory.displayName;
1387
1073
  }
1388
- // Defer the update to avoid "read-only transaction" errors when called from handlers
1389
- // Matter.js uses transactions, and we need to escape the current call stack
1390
- // setImmediate ensures we're in a new event loop tick without arbitrary delays
1391
1074
  return new Promise((resolve, reject) => {
1392
1075
  setImmediate(async () => {
1393
1076
  try {
1394
- // Construct the update object
1395
1077
  const updateObject = { [cluster]: attributes };
1396
- // Use endpoint.set() which properly handles state updates
1397
1078
  await targetEndpoint.set(updateObject);
1398
- // CRITICAL: Also update the cached clusters object so state persists across restarts
1399
- // Merge the new attributes into the existing cluster state
1400
1079
  if (!targetClusters) {
1401
1080
  log.warn(`Target clusters undefined for ${displayName}, cannot cache state`);
1402
1081
  }
@@ -1421,28 +1100,60 @@ export class MatterServer extends EventEmitter {
1421
1100
  });
1422
1101
  });
1423
1102
  }
1424
- /**
1425
- * Get a Matter accessory's current state (Plugin API)
1426
- *
1427
- * Returns the current cluster attribute values that are exposed to Matter controllers.
1428
- * This is useful for:
1429
- * - Reading state after plugin restart (when local variables are lost)
1430
- * - Verifying current state before making changes
1431
- * - Multiple parts of code that need to read state
1432
- * - Debugging and logging
1433
- *
1434
- * @param uuid - The UUID of the accessory
1435
- * @param cluster - The cluster name (e.g., 'onOff', 'levelControl')
1436
- * @param partId - Optional: ID of the part to get state from (for composed devices)
1437
- * @returns Current cluster attribute values, or undefined if cluster not found
1438
- */
1103
+ async triggerCommand(uuid, cluster, command, args, partId) {
1104
+ const accessory = this.accessories.get(uuid);
1105
+ if (!accessory) {
1106
+ throw new MatterDeviceError(`Accessory ${uuid} not found or not registered`);
1107
+ }
1108
+ let targetEndpoint;
1109
+ let displayName;
1110
+ if (partId) {
1111
+ const part = accessory._parts?.find(p => p.id === partId);
1112
+ if (!part || !part.endpoint) {
1113
+ throw new MatterDeviceError(`Part ${partId} not found in accessory ${uuid}`);
1114
+ }
1115
+ targetEndpoint = part.endpoint;
1116
+ displayName = part.displayName || `${accessory.displayName} - ${partId}`;
1117
+ }
1118
+ else {
1119
+ if (!accessory.endpoint) {
1120
+ throw new MatterDeviceError(`Accessory ${uuid} not registered or missing endpoint`);
1121
+ }
1122
+ targetEndpoint = accessory.endpoint;
1123
+ displayName = accessory.displayName;
1124
+ }
1125
+ try {
1126
+ const partInfo = partId ? ` (part: ${partId})` : '';
1127
+ log.debug(`Triggering command ${cluster}.${command} for ${displayName}${partInfo}`, args);
1128
+ await targetEndpoint.act((agent) => {
1129
+ const clusterBehavior = agent[cluster];
1130
+ if (!clusterBehavior) {
1131
+ throw new Error(`Cluster '${cluster}' not found on endpoint`);
1132
+ }
1133
+ if (typeof clusterBehavior[command] !== 'function') {
1134
+ throw new TypeError(`Command '${command}' not found on cluster '${cluster}'`);
1135
+ }
1136
+ if (args && Object.keys(args).length > 0) {
1137
+ return clusterBehavior[command](args);
1138
+ }
1139
+ else {
1140
+ return clusterBehavior[command]();
1141
+ }
1142
+ });
1143
+ log.debug(`Command ${cluster}.${command} succeeded for ${displayName}${partInfo}`);
1144
+ }
1145
+ catch (error) {
1146
+ const partInfo = partId ? ` part ${partId}` : '';
1147
+ log.error(`Failed to trigger command for accessory ${uuid}${partInfo}:`, error);
1148
+ throw new MatterDeviceError(`Failed to trigger command: ${error}`);
1149
+ }
1150
+ }
1439
1151
  getAccessoryState(uuid, cluster, partId) {
1440
1152
  const accessory = this.accessories.get(uuid);
1441
1153
  if (!accessory) {
1442
1154
  log.debug(`Accessory ${uuid} not found`);
1443
1155
  return undefined;
1444
1156
  }
1445
- // Determine which endpoint to read from
1446
1157
  let targetEndpoint;
1447
1158
  let displayName;
1448
1159
  if (partId) {
@@ -1473,22 +1184,17 @@ export class MatterServer extends EventEmitter {
1473
1184
  return undefined;
1474
1185
  }
1475
1186
  const clusterState = targetEndpoint.state[cluster];
1476
- // Build result object by reading each property directly
1477
1187
  const result = {};
1478
- // Get list of properties to read - use both approaches for maximum compatibility
1479
1188
  const allKeys = new Set([
1480
1189
  ...Object.keys(clusterState),
1481
1190
  ...Object.getOwnPropertyNames(clusterState),
1482
1191
  ]);
1483
1192
  for (const key of allKeys) {
1484
1193
  try {
1485
- // Skip internal properties, methods, and symbols
1486
1194
  if (key.startsWith('_') || key.startsWith('$')) {
1487
1195
  continue;
1488
1196
  }
1489
- // Try to read the value directly
1490
1197
  const value = clusterState[key];
1491
- // Skip functions and undefined values
1492
1198
  if (typeof value === 'function' || value === undefined) {
1493
1199
  continue;
1494
1200
  }
@@ -1509,10 +1215,6 @@ export class MatterServer extends EventEmitter {
1509
1215
  return undefined;
1510
1216
  }
1511
1217
  }
1512
- /**
1513
- * Get all cached accessories (Internal - for restore process)
1514
- * @internal
1515
- */
1516
1218
  getAllCachedAccessories() {
1517
1219
  if (!this.accessoryCache) {
1518
1220
  log.debug('getAllCachedAccessories: No cache available');
@@ -1522,33 +1224,20 @@ export class MatterServer extends EventEmitter {
1522
1224
  log.debug(`getAllCachedAccessories: Returning ${cached.length} accessories`);
1523
1225
  return cached;
1524
1226
  }
1525
- /**
1526
- * Get all registered accessories (Plugin API)
1527
- */
1528
1227
  getAccessories() {
1529
1228
  return Array.from(this.accessories.values()).map((acc) => {
1530
- // Return copy without internal fields
1531
- // eslint-disable-next-line unused-imports/no-unused-vars
1532
1229
  const { endpoint, registered, ...publicAccessory } = acc;
1533
1230
  return publicAccessory;
1534
1231
  });
1535
1232
  }
1536
- /**
1537
- * Get a specific accessory by UUID (Plugin API)
1538
- */
1539
1233
  getAccessory(uuid) {
1540
1234
  const accessory = this.accessories.get(uuid);
1541
1235
  if (!accessory) {
1542
1236
  return undefined;
1543
1237
  }
1544
- // Return copy without internal fields
1545
- // eslint-disable-next-line unused-imports/no-unused-vars
1546
1238
  const { endpoint, registered, ...publicAccessory } = accessory;
1547
1239
  return publicAccessory;
1548
1240
  }
1549
- /**
1550
- * Stop the Matter server
1551
- */
1552
1241
  async stop() {
1553
1242
  if (!this.isRunning) {
1554
1243
  log.debug('Matter server is not running');
@@ -1556,19 +1245,14 @@ export class MatterServer extends EventEmitter {
1556
1245
  }
1557
1246
  this.isRunning = false;
1558
1247
  try {
1559
- // Save accessory cache before shutting down (BEFORE clearing accessories!)
1560
1248
  if (this.accessoryCache && this.accessories.size > 0) {
1561
1249
  await this.accessoryCache.save(this.accessories);
1562
1250
  log.debug('Saved accessory cache before shutdown');
1563
1251
  }
1564
- // Stop server (this will close all child endpoints automatically)
1565
- // Note: We don't manually close endpoints here because they're part of the ServerNode
1566
- // hierarchy and will be closed by serverNode.close()
1567
1252
  if (this.serverNode) {
1568
1253
  await this.serverNode.close();
1569
1254
  log.debug('ServerNode closed (all endpoints cleaned up)');
1570
1255
  }
1571
- // Clear accessories map after server is stopped
1572
1256
  this.accessories.clear();
1573
1257
  await this.cleanup();
1574
1258
  log.info('Matter server stopped');
@@ -1582,17 +1266,12 @@ export class MatterServer extends EventEmitter {
1582
1266
  this.isRunning = false;
1583
1267
  }
1584
1268
  }
1585
- /**
1586
- * Cleanup resources
1587
- */
1588
1269
  async cleanup() {
1589
- // Remove signal handlers
1590
1270
  if (this.shutdownHandler) {
1591
1271
  process.off('SIGINT', this.shutdownHandler);
1592
1272
  process.off('SIGTERM', this.shutdownHandler);
1593
1273
  this.shutdownHandler = null;
1594
1274
  }
1595
- // Run all cleanup handlers
1596
1275
  for (const handler of this.cleanupHandlers) {
1597
1276
  try {
1598
1277
  await handler();
@@ -1602,44 +1281,19 @@ export class MatterServer extends EventEmitter {
1602
1281
  }
1603
1282
  }
1604
1283
  this.cleanupHandlers = [];
1605
- // Clear references
1606
1284
  this.serverNode = null;
1607
1285
  this.aggregator = null;
1608
1286
  this.isRunning = false;
1609
1287
  this.commissioningInfo = {};
1610
1288
  }
1611
- /**
1612
- * Get fabric information for commissioned controllers
1613
- *
1614
- * Returns information about each paired controller (fabric) including:
1615
- * - fabricIndex: Unique identifier for the fabric
1616
- * - fabricId: 64-bit fabric identifier
1617
- * - nodeId: Node identifier within the fabric
1618
- * - rootVendorId: Vendor ID of the root node
1619
- * - label: Optional human-readable label
1620
- *
1621
- * @returns Array of fabric information objects, or empty array if no fabrics are commissioned
1622
- * @example
1623
- * ```typescript
1624
- * const fabrics = matterServer.getFabricInfo()
1625
- * console.log(`Commissioned to ${fabrics.length} controller(s)`)
1626
- * fabrics.forEach(fabric => {
1627
- * console.log(` Fabric ${fabric.fabricIndex}: ${fabric.label || 'Unnamed'}`)
1628
- * })
1629
- * ```
1630
- */
1631
1289
  getFabricInfo() {
1632
1290
  try {
1633
1291
  if (!this.storageManager) {
1634
1292
  return [];
1635
1293
  }
1636
- // Fabric data is stored in the main storage file (uniqueId namespace)
1637
- // with the key "fabrics.fabrics"
1638
- // Note: We can read this directly from storage even before serverNode is initialized
1639
1294
  const storage = this.storageManager.getStorage(this.config.uniqueId);
1640
1295
  const fabricsData = storage.get(['fabrics'], 'fabrics');
1641
1296
  if (Array.isArray(fabricsData) && fabricsData.length > 0) {
1642
- // Map the fabric data to our interface
1643
1297
  return fabricsData.map(fabric => ({
1644
1298
  fabricIndex: fabric.fabricIndex || 0,
1645
1299
  fabricId: fabric.fabricId?.value?.toString() || '',
@@ -1655,22 +1309,16 @@ export class MatterServer extends EventEmitter {
1655
1309
  return [];
1656
1310
  }
1657
1311
  }
1658
- /**
1659
- * Check if the server is commissioned
1660
- */
1661
1312
  isCommissioned() {
1662
1313
  try {
1663
1314
  if (!this.storageManager) {
1664
1315
  return false;
1665
1316
  }
1666
- // Commissioned status is stored in the main storage file (uniqueId namespace)
1667
- // at key "root.commissioning.commissioned"
1668
1317
  const storage = this.storageManager.getStorage(this.config.uniqueId);
1669
1318
  const commissioned = storage.get(['root', 'commissioning'], 'commissioned');
1670
1319
  if (commissioned === true) {
1671
1320
  return true;
1672
1321
  }
1673
- // Fallback to checking fabric count if commissioned flag not found
1674
1322
  const fabrics = this.getFabricInfo();
1675
1323
  return fabrics.length > 0;
1676
1324
  }
@@ -1679,15 +1327,9 @@ export class MatterServer extends EventEmitter {
1679
1327
  return false;
1680
1328
  }
1681
1329
  }
1682
- /**
1683
- * Get the number of commissioned fabrics
1684
- */
1685
1330
  getCommissionedFabricCount() {
1686
1331
  return this.getFabricInfo().length;
1687
1332
  }
1688
- /**
1689
- * Get server status information
1690
- */
1691
1333
  getServerInfo() {
1692
1334
  return {
1693
1335
  running: this.isRunning,
@@ -1698,9 +1340,6 @@ export class MatterServer extends EventEmitter {
1698
1340
  serialNumber: this.serialNumber,
1699
1341
  };
1700
1342
  }
1701
- /**
1702
- * Get commissioning information
1703
- */
1704
1343
  getCommissioningInfo() {
1705
1344
  return {
1706
1345
  ...this.commissioningInfo,
@@ -1710,40 +1349,21 @@ export class MatterServer extends EventEmitter {
1710
1349
  commissioned: this.isCommissioned(),
1711
1350
  };
1712
1351
  }
1713
- /**
1714
- * Get storage statistics
1715
- */
1716
1352
  getStorageStats() {
1717
1353
  if (!this.storageManager) {
1718
1354
  return null;
1719
1355
  }
1720
1356
  return this.storageManager.getAllStats();
1721
1357
  }
1722
- /**
1723
- * Check if server is running
1724
- */
1725
1358
  isServerRunning() {
1726
1359
  return this.isRunning;
1727
1360
  }
1728
- /**
1729
- * Get Matter device types available for plugin use
1730
- */
1731
1361
  getDeviceTypes() {
1732
1362
  return deviceTypes;
1733
1363
  }
1734
- /**
1735
- * Get Matter clusters available for plugin use
1736
- */
1737
1364
  getClusters() {
1738
1365
  return clusters;
1739
1366
  }
1740
- /**
1741
- * Remove a specific fabric (controller) from the bridge
1742
- * This decommissions a single controller while leaving others intact
1743
- *
1744
- * @param fabricIndex - The fabric index to remove
1745
- * @returns Promise that resolves when the fabric is removed
1746
- */
1747
1367
  async removeFabric(fabricIndex) {
1748
1368
  if (!this.serverNode) {
1749
1369
  throw new MatterDeviceError('Matter server not started');
@@ -1755,10 +1375,8 @@ export class MatterServer extends EventEmitter {
1755
1375
  if (typeof removeFabric !== 'function') {
1756
1376
  throw new MatterDeviceError('Fabric removal not supported by Matter.js version');
1757
1377
  }
1758
- // Remove the fabric
1759
1378
  await removeFabric(fabricIndex);
1760
1379
  log.info(`Fabric ${fabricIndex} removed successfully`);
1761
- // The fabric monitoring will detect this change and emit the appropriate events
1762
1380
  }
1763
1381
  catch (error) {
1764
1382
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1768,36 +1386,108 @@ export class MatterServer extends EventEmitter {
1768
1386
  });
1769
1387
  }
1770
1388
  }
1771
- /**
1772
- * Check if a specific fabric exists
1773
- */
1774
1389
  hasFabric(fabricIndex) {
1775
1390
  const fabrics = this.getFabricInfo();
1776
1391
  return fabrics.some(f => f.fabricIndex === fabricIndex);
1777
1392
  }
1778
- /**
1779
- * Notify controllers that the parts list has changed
1780
- * This triggers controllers (like Home app) to re-read the device list
1781
- * and discover new or removed accessories without needing to re-pair
1782
- */
1393
+ enableStateMonitoring() {
1394
+ this.monitoringEnabled = true;
1395
+ log.debug('Matter state monitoring enabled');
1396
+ }
1397
+ disableStateMonitoring() {
1398
+ this.monitoringEnabled = false;
1399
+ log.debug('Matter state monitoring disabled');
1400
+ }
1401
+ isMonitoringEnabled() {
1402
+ return this.monitoringEnabled;
1403
+ }
1404
+ notifyStateChange(uuid, cluster, state, partId) {
1405
+ if (!this.monitoringEnabled) {
1406
+ return;
1407
+ }
1408
+ if (process.send) {
1409
+ const event = {
1410
+ type: 'accessoryUpdate',
1411
+ data: {
1412
+ uuid,
1413
+ cluster,
1414
+ state,
1415
+ partId,
1416
+ },
1417
+ };
1418
+ process.send({
1419
+ id: "matterEvent",
1420
+ data: event,
1421
+ });
1422
+ }
1423
+ }
1424
+ getDeviceTypeName(deviceType) {
1425
+ return deviceType.name || 'Unknown';
1426
+ }
1427
+ collectAccessories(bridgeUsername, bridgeType, bridgeName) {
1428
+ const accessories = [];
1429
+ for (const [uuid, accessory] of this.accessories.entries()) {
1430
+ const transformed = {
1431
+ uuid,
1432
+ displayName: accessory.displayName,
1433
+ deviceType: this.getDeviceTypeName(accessory.deviceType),
1434
+ clusters: this.getCurrentState(uuid),
1435
+ parts: accessory._parts?.map(part => ({
1436
+ id: part.id,
1437
+ displayName: part.displayName,
1438
+ deviceType: this.getDeviceTypeName(part.deviceType),
1439
+ clusters: this.getCurrentState(uuid, part.id),
1440
+ })),
1441
+ bridge: {
1442
+ username: bridgeUsername,
1443
+ type: bridgeType,
1444
+ name: bridgeName,
1445
+ },
1446
+ };
1447
+ accessories.push(transformed);
1448
+ }
1449
+ return accessories;
1450
+ }
1451
+ getAccessoryInfo(uuid) {
1452
+ const accessory = this.accessories.get(uuid);
1453
+ if (!accessory) {
1454
+ return undefined;
1455
+ }
1456
+ return {
1457
+ uuid,
1458
+ displayName: accessory.displayName,
1459
+ deviceType: this.getDeviceTypeName(accessory.deviceType),
1460
+ clusters: this.getCurrentState(uuid),
1461
+ parts: accessory._parts?.map(part => ({
1462
+ id: part.id,
1463
+ displayName: part.displayName,
1464
+ deviceType: this.getDeviceTypeName(part.deviceType),
1465
+ clusters: this.getCurrentState(uuid, part.id),
1466
+ })),
1467
+ };
1468
+ }
1469
+ getCurrentState(uuid, partId) {
1470
+ const accessory = this.accessories.get(uuid);
1471
+ if (!accessory) {
1472
+ return {};
1473
+ }
1474
+ if (partId) {
1475
+ const part = accessory._parts?.find(p => p.id === partId);
1476
+ return part?.clusters || {};
1477
+ }
1478
+ return accessory.clusters || {};
1479
+ }
1783
1480
  async notifyPartsListChanged() {
1784
1481
  if (!this.aggregator || !this.isCommissioned()) {
1785
- // No controllers connected, skip notification
1786
1482
  return;
1787
1483
  }
1788
1484
  try {
1789
- // Access the aggregator's descriptor cluster state
1790
- // The partsList is automatically updated by Matter.js when endpoints are added/removed
1791
- // We just need to ensure controllers are notified of the change
1792
1485
  const aggregatorState = this.aggregator;
1793
1486
  if (aggregatorState.state?.descriptor) {
1794
- // Get current parts list (endpoint numbers of all children)
1795
1487
  const partsList = aggregatorState.state.descriptor.partsList || [];
1796
1488
  if (this.config.debugModeEnabled) {
1797
1489
  log.debug(`Parts list changed: ${partsList.length} devices (endpoints: ${partsList.join(', ')})`);
1798
1490
  }
1799
- // Trigger a state update event to notify subscribed controllers
1800
- // By setting the partsList to itself, we trigger the change notification
1801
1491
  await this.aggregator.set({
1802
1492
  descriptor: {
1803
1493
  partsList,
@@ -1807,7 +1497,6 @@ export class MatterServer extends EventEmitter {
1807
1497
  }
1808
1498
  }
1809
1499
  catch (error) {
1810
- // Non-fatal error - log but don't throw
1811
1500
  const errorMessage = error instanceof Error ? error.message : String(error);
1812
1501
  log.warn(`Failed to notify controllers of parts list change: ${errorMessage}`);
1813
1502
  }