homebridge 2.0.0-beta.35 → 2.0.0-beta.36

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 (280) hide show
  1. package/dist/api.d.ts +74 -32
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +48 -46
  4. package/dist/api.js.map +1 -1
  5. package/dist/api.spec.d.ts +2 -0
  6. package/dist/api.spec.d.ts.map +1 -0
  7. package/dist/api.spec.js +413 -0
  8. package/dist/api.spec.js.map +1 -0
  9. package/dist/childBridgeFork.d.ts +1 -18
  10. package/dist/childBridgeFork.d.ts.map +1 -1
  11. package/dist/childBridgeFork.js +21 -258
  12. package/dist/childBridgeFork.js.map +1 -1
  13. package/dist/index.d.ts +10 -10
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -3
  16. package/dist/index.js.map +1 -1
  17. package/dist/logger.spec.d.ts +2 -0
  18. package/dist/logger.spec.d.ts.map +1 -0
  19. package/dist/logger.spec.js +95 -0
  20. package/dist/logger.spec.js.map +1 -0
  21. package/dist/matter/ChildBridgeMatterManager.d.ts +96 -0
  22. package/dist/matter/ChildBridgeMatterManager.d.ts.map +1 -0
  23. package/dist/matter/ChildBridgeMatterManager.js +397 -0
  24. package/dist/matter/ChildBridgeMatterManager.js.map +1 -0
  25. package/dist/matter/ExternalMatterAccessoryPublisher.d.ts +48 -0
  26. package/dist/matter/ExternalMatterAccessoryPublisher.d.ts.map +1 -0
  27. package/dist/matter/ExternalMatterAccessoryPublisher.js +73 -0
  28. package/dist/matter/ExternalMatterAccessoryPublisher.js.map +1 -0
  29. package/dist/matter/ExternalMatterAccessoryPublisher.spec.d.ts +2 -0
  30. package/dist/matter/ExternalMatterAccessoryPublisher.spec.d.ts.map +1 -0
  31. package/dist/matter/ExternalMatterAccessoryPublisher.spec.js +293 -0
  32. package/dist/matter/ExternalMatterAccessoryPublisher.spec.js.map +1 -0
  33. package/dist/matter/{matterServer.d.ts → MatterAPIImpl.d.ts} +203 -367
  34. package/dist/matter/MatterAPIImpl.d.ts.map +1 -0
  35. package/dist/matter/MatterAPIImpl.js +305 -0
  36. package/dist/matter/MatterAPIImpl.js.map +1 -0
  37. package/dist/matter/MatterBridgeManager.d.ts +91 -0
  38. package/dist/matter/MatterBridgeManager.d.ts.map +1 -0
  39. package/dist/matter/MatterBridgeManager.js +426 -0
  40. package/dist/matter/MatterBridgeManager.js.map +1 -0
  41. package/dist/matter/MatterConfigCollector.d.ts +26 -0
  42. package/dist/matter/MatterConfigCollector.d.ts.map +1 -0
  43. package/dist/matter/MatterConfigCollector.js +78 -0
  44. package/dist/matter/MatterConfigCollector.js.map +1 -0
  45. package/dist/matter/{matterAccessoryCache.d.ts → accessoryCache.d.ts} +12 -3
  46. package/dist/matter/accessoryCache.d.ts.map +1 -0
  47. package/dist/matter/{matterAccessoryCache.js → accessoryCache.js} +32 -10
  48. package/dist/matter/accessoryCache.js.map +1 -0
  49. package/dist/matter/accessoryCache.spec.d.ts +2 -0
  50. package/dist/matter/accessoryCache.spec.d.ts.map +1 -0
  51. package/dist/matter/accessoryCache.spec.js +452 -0
  52. package/dist/matter/accessoryCache.spec.js.map +1 -0
  53. package/dist/matter/behaviors/BehaviorRegistry.d.ts +65 -0
  54. package/dist/matter/behaviors/BehaviorRegistry.d.ts.map +1 -0
  55. package/dist/matter/behaviors/BehaviorRegistry.js +139 -0
  56. package/dist/matter/behaviors/BehaviorRegistry.js.map +1 -0
  57. package/dist/matter/behaviors/BehaviorRegistry.spec.d.ts +2 -0
  58. package/dist/matter/behaviors/BehaviorRegistry.spec.d.ts.map +1 -0
  59. package/dist/matter/behaviors/BehaviorRegistry.spec.js +307 -0
  60. package/dist/matter/behaviors/BehaviorRegistry.spec.js.map +1 -0
  61. package/dist/matter/behaviors/ColorControlBehavior.d.ts +64 -0
  62. package/dist/matter/behaviors/ColorControlBehavior.d.ts.map +1 -0
  63. package/dist/matter/behaviors/ColorControlBehavior.js +160 -0
  64. package/dist/matter/behaviors/ColorControlBehavior.js.map +1 -0
  65. package/dist/matter/behaviors/ColorControlBehavior.spec.d.ts +2 -0
  66. package/dist/matter/behaviors/ColorControlBehavior.spec.d.ts.map +1 -0
  67. package/dist/matter/behaviors/ColorControlBehavior.spec.js +29 -0
  68. package/dist/matter/behaviors/ColorControlBehavior.spec.js.map +1 -0
  69. package/dist/matter/behaviors/DoorLockBehavior.d.ts +21 -0
  70. package/dist/matter/behaviors/DoorLockBehavior.d.ts.map +1 -0
  71. package/dist/matter/behaviors/DoorLockBehavior.js +49 -0
  72. package/dist/matter/behaviors/DoorLockBehavior.js.map +1 -0
  73. package/dist/matter/behaviors/DoorLockBehavior.spec.d.ts +2 -0
  74. package/dist/matter/behaviors/DoorLockBehavior.spec.d.ts.map +1 -0
  75. package/dist/matter/behaviors/DoorLockBehavior.spec.js +108 -0
  76. package/dist/matter/behaviors/DoorLockBehavior.spec.js.map +1 -0
  77. package/dist/matter/behaviors/FanControlBehavior.d.ts +20 -0
  78. package/dist/matter/behaviors/FanControlBehavior.d.ts.map +1 -0
  79. package/dist/matter/behaviors/FanControlBehavior.js +45 -0
  80. package/dist/matter/behaviors/FanControlBehavior.js.map +1 -0
  81. package/dist/matter/behaviors/FanControlBehavior.spec.d.ts +2 -0
  82. package/dist/matter/behaviors/FanControlBehavior.spec.d.ts.map +1 -0
  83. package/dist/matter/behaviors/FanControlBehavior.spec.js +23 -0
  84. package/dist/matter/behaviors/FanControlBehavior.spec.js.map +1 -0
  85. package/dist/matter/behaviors/IdentifyBehavior.d.ts +21 -0
  86. package/dist/matter/behaviors/IdentifyBehavior.d.ts.map +1 -0
  87. package/dist/matter/behaviors/IdentifyBehavior.js +27 -0
  88. package/dist/matter/behaviors/IdentifyBehavior.js.map +1 -0
  89. package/dist/matter/behaviors/IdentifyBehavior.spec.d.ts +2 -0
  90. package/dist/matter/behaviors/IdentifyBehavior.spec.d.ts.map +1 -0
  91. package/dist/matter/behaviors/IdentifyBehavior.spec.js +64 -0
  92. package/dist/matter/behaviors/IdentifyBehavior.spec.js.map +1 -0
  93. package/dist/matter/behaviors/LevelControlBehavior.d.ts +34 -0
  94. package/dist/matter/behaviors/LevelControlBehavior.d.ts.map +1 -0
  95. package/dist/matter/behaviors/LevelControlBehavior.js +75 -0
  96. package/dist/matter/behaviors/LevelControlBehavior.js.map +1 -0
  97. package/dist/matter/behaviors/LevelControlBehavior.spec.d.ts +2 -0
  98. package/dist/matter/behaviors/LevelControlBehavior.spec.d.ts.map +1 -0
  99. package/dist/matter/behaviors/LevelControlBehavior.spec.js +140 -0
  100. package/dist/matter/behaviors/LevelControlBehavior.spec.js.map +1 -0
  101. package/dist/matter/behaviors/OnOffBehavior.d.ts +28 -0
  102. package/dist/matter/behaviors/OnOffBehavior.d.ts.map +1 -0
  103. package/dist/matter/behaviors/OnOffBehavior.js +63 -0
  104. package/dist/matter/behaviors/OnOffBehavior.js.map +1 -0
  105. package/dist/matter/behaviors/OnOffBehavior.spec.d.ts +2 -0
  106. package/dist/matter/behaviors/OnOffBehavior.spec.d.ts.map +1 -0
  107. package/dist/matter/behaviors/OnOffBehavior.spec.js +116 -0
  108. package/dist/matter/behaviors/OnOffBehavior.spec.js.map +1 -0
  109. package/dist/matter/behaviors/RvcCleanModeBehavior.d.ts +19 -0
  110. package/dist/matter/behaviors/RvcCleanModeBehavior.d.ts.map +1 -0
  111. package/dist/matter/behaviors/RvcCleanModeBehavior.js +27 -0
  112. package/dist/matter/behaviors/RvcCleanModeBehavior.js.map +1 -0
  113. package/dist/matter/behaviors/RvcCleanModeBehavior.spec.d.ts +2 -0
  114. package/dist/matter/behaviors/RvcCleanModeBehavior.spec.d.ts.map +1 -0
  115. package/dist/matter/behaviors/RvcCleanModeBehavior.spec.js +56 -0
  116. package/dist/matter/behaviors/RvcCleanModeBehavior.spec.js.map +1 -0
  117. package/dist/matter/behaviors/RvcOperationalStateBehavior.d.ts +23 -0
  118. package/dist/matter/behaviors/RvcOperationalStateBehavior.d.ts.map +1 -0
  119. package/dist/matter/behaviors/RvcOperationalStateBehavior.js +49 -0
  120. package/dist/matter/behaviors/RvcOperationalStateBehavior.js.map +1 -0
  121. package/dist/matter/behaviors/RvcOperationalStateBehavior.spec.d.ts +2 -0
  122. package/dist/matter/behaviors/RvcOperationalStateBehavior.spec.d.ts.map +1 -0
  123. package/dist/matter/behaviors/RvcOperationalStateBehavior.spec.js +55 -0
  124. package/dist/matter/behaviors/RvcOperationalStateBehavior.spec.js.map +1 -0
  125. package/dist/matter/behaviors/RvcRunModeBehavior.d.ts +19 -0
  126. package/dist/matter/behaviors/RvcRunModeBehavior.d.ts.map +1 -0
  127. package/dist/matter/behaviors/RvcRunModeBehavior.js +27 -0
  128. package/dist/matter/behaviors/RvcRunModeBehavior.js.map +1 -0
  129. package/dist/matter/behaviors/RvcRunModeBehavior.spec.d.ts +2 -0
  130. package/dist/matter/behaviors/RvcRunModeBehavior.spec.d.ts.map +1 -0
  131. package/dist/matter/behaviors/RvcRunModeBehavior.spec.js +56 -0
  132. package/dist/matter/behaviors/RvcRunModeBehavior.spec.js.map +1 -0
  133. package/dist/matter/behaviors/ServiceAreaBehavior.d.ts +22 -0
  134. package/dist/matter/behaviors/ServiceAreaBehavior.d.ts.map +1 -0
  135. package/dist/matter/behaviors/ServiceAreaBehavior.js +35 -0
  136. package/dist/matter/behaviors/ServiceAreaBehavior.js.map +1 -0
  137. package/dist/matter/behaviors/ServiceAreaBehavior.spec.d.ts +2 -0
  138. package/dist/matter/behaviors/ServiceAreaBehavior.spec.d.ts.map +1 -0
  139. package/dist/matter/behaviors/ServiceAreaBehavior.spec.js +52 -0
  140. package/dist/matter/behaviors/ServiceAreaBehavior.spec.js.map +1 -0
  141. package/dist/matter/behaviors/ThermostatBehavior.d.ts +23 -0
  142. package/dist/matter/behaviors/ThermostatBehavior.d.ts.map +1 -0
  143. package/dist/matter/behaviors/ThermostatBehavior.js +79 -0
  144. package/dist/matter/behaviors/ThermostatBehavior.js.map +1 -0
  145. package/dist/matter/behaviors/ThermostatBehavior.spec.d.ts +2 -0
  146. package/dist/matter/behaviors/ThermostatBehavior.spec.d.ts.map +1 -0
  147. package/dist/matter/behaviors/ThermostatBehavior.spec.js +23 -0
  148. package/dist/matter/behaviors/ThermostatBehavior.spec.js.map +1 -0
  149. package/dist/matter/behaviors/WindowCoveringBehavior.d.ts +32 -0
  150. package/dist/matter/behaviors/WindowCoveringBehavior.d.ts.map +1 -0
  151. package/dist/matter/behaviors/WindowCoveringBehavior.js +106 -0
  152. package/dist/matter/behaviors/WindowCoveringBehavior.js.map +1 -0
  153. package/dist/matter/behaviors/WindowCoveringBehavior.spec.d.ts +2 -0
  154. package/dist/matter/behaviors/WindowCoveringBehavior.spec.d.ts.map +1 -0
  155. package/dist/matter/behaviors/WindowCoveringBehavior.spec.js +27 -0
  156. package/dist/matter/behaviors/WindowCoveringBehavior.spec.js.map +1 -0
  157. package/dist/matter/behaviors/index.d.ts +20 -0
  158. package/dist/matter/behaviors/index.d.ts.map +1 -0
  159. package/dist/matter/behaviors/index.js +21 -0
  160. package/dist/matter/behaviors/index.js.map +1 -0
  161. package/dist/matter/{matterConfigValidator.d.ts → configValidator.d.ts} +1 -1
  162. package/dist/matter/configValidator.d.ts.map +1 -0
  163. package/dist/matter/{matterConfigValidator.js → configValidator.js} +1 -1
  164. package/dist/matter/configValidator.js.map +1 -0
  165. package/dist/matter/configValidator.spec.d.ts +2 -0
  166. package/dist/matter/configValidator.spec.d.ts.map +1 -0
  167. package/dist/matter/configValidator.spec.js +390 -0
  168. package/dist/matter/configValidator.spec.js.map +1 -0
  169. package/dist/matter/errorHandler.d.ts +33 -0
  170. package/dist/matter/errorHandler.d.ts.map +1 -0
  171. package/dist/matter/errorHandler.js +113 -0
  172. package/dist/matter/errorHandler.js.map +1 -0
  173. package/dist/matter/errorHandler.spec.d.ts +2 -0
  174. package/dist/matter/errorHandler.spec.d.ts.map +1 -0
  175. package/dist/matter/errorHandler.spec.js +159 -0
  176. package/dist/matter/errorHandler.spec.js.map +1 -0
  177. package/dist/matter/index.d.ts +7 -4
  178. package/dist/matter/index.d.ts.map +1 -1
  179. package/dist/matter/index.js +7 -4
  180. package/dist/matter/index.js.map +1 -1
  181. package/dist/matter/{matterLogFormatter.d.ts → logFormatter.d.ts} +1 -1
  182. package/dist/matter/logFormatter.d.ts.map +1 -0
  183. package/dist/matter/{matterLogFormatter.js → logFormatter.js} +33 -6
  184. package/dist/matter/logFormatter.js.map +1 -0
  185. package/dist/matter/logFormatter.spec.d.ts +2 -0
  186. package/dist/matter/logFormatter.spec.d.ts.map +1 -0
  187. package/dist/matter/logFormatter.spec.js +284 -0
  188. package/dist/matter/logFormatter.spec.js.map +1 -0
  189. package/dist/matter/server.d.ts +350 -0
  190. package/dist/matter/server.d.ts.map +1 -0
  191. package/dist/matter/{matterServer.js → server.js} +533 -352
  192. package/dist/matter/server.js.map +1 -0
  193. package/dist/matter/{matterServerHelpers.d.ts → serverHelpers.d.ts} +2 -2
  194. package/dist/matter/serverHelpers.d.ts.map +1 -0
  195. package/dist/matter/{matterServerHelpers.js → serverHelpers.js} +11 -8
  196. package/dist/matter/serverHelpers.js.map +1 -0
  197. package/dist/matter/serverHelpers.spec.d.ts +2 -0
  198. package/dist/matter/serverHelpers.spec.d.ts.map +1 -0
  199. package/dist/matter/serverHelpers.spec.js +521 -0
  200. package/dist/matter/serverHelpers.spec.js.map +1 -0
  201. package/dist/matter/{matterSharedTypes.d.ts → sharedTypes.d.ts} +4 -10
  202. package/dist/matter/sharedTypes.d.ts.map +1 -0
  203. package/dist/matter/{matterSharedTypes.js → sharedTypes.js} +4 -10
  204. package/dist/matter/sharedTypes.js.map +1 -0
  205. package/dist/matter/{matterStorage.d.ts → storage.d.ts} +9 -2
  206. package/dist/matter/storage.d.ts.map +1 -0
  207. package/dist/matter/{matterStorage.js → storage.js} +14 -5
  208. package/dist/matter/{matterStorage.js.map → storage.js.map} +1 -1
  209. package/dist/matter/storage.spec.d.ts +2 -0
  210. package/dist/matter/storage.spec.d.ts.map +1 -0
  211. package/dist/matter/storage.spec.js +570 -0
  212. package/dist/matter/storage.spec.js.map +1 -0
  213. package/dist/matter/typeHelpers.d.ts +45 -0
  214. package/dist/matter/typeHelpers.d.ts.map +1 -0
  215. package/dist/matter/typeHelpers.js +57 -0
  216. package/dist/matter/typeHelpers.js.map +1 -0
  217. package/dist/matter/typeHelpers.spec.d.ts +2 -0
  218. package/dist/matter/typeHelpers.spec.d.ts.map +1 -0
  219. package/dist/matter/typeHelpers.spec.js +127 -0
  220. package/dist/matter/typeHelpers.spec.js.map +1 -0
  221. package/dist/matter/{matterTypes.d.ts → types.d.ts} +2 -2
  222. package/dist/matter/types.d.ts.map +1 -0
  223. package/dist/matter/{matterTypes.js → types.js} +1 -2
  224. package/dist/matter/types.js.map +1 -0
  225. package/dist/platformAccessory.spec.d.ts +2 -0
  226. package/dist/platformAccessory.spec.d.ts.map +1 -0
  227. package/dist/platformAccessory.spec.js +126 -0
  228. package/dist/platformAccessory.spec.js.map +1 -0
  229. package/dist/pluginManager.spec.d.ts +2 -0
  230. package/dist/pluginManager.spec.d.ts.map +1 -0
  231. package/dist/pluginManager.spec.js +43 -0
  232. package/dist/pluginManager.spec.js.map +1 -0
  233. package/dist/server.d.ts +4 -13
  234. package/dist/server.d.ts.map +1 -1
  235. package/dist/server.js +43 -346
  236. package/dist/server.js.map +1 -1
  237. package/dist/server.spec.d.ts +2 -0
  238. package/dist/server.spec.d.ts.map +1 -0
  239. package/dist/server.spec.js +57 -0
  240. package/dist/server.spec.js.map +1 -0
  241. package/dist/user.spec.d.ts +2 -0
  242. package/dist/user.spec.d.ts.map +1 -0
  243. package/dist/user.spec.js +31 -0
  244. package/dist/user.spec.js.map +1 -0
  245. package/dist/util/mac.spec.d.ts +2 -0
  246. package/dist/util/mac.spec.d.ts.map +1 -0
  247. package/dist/util/mac.spec.js +36 -0
  248. package/dist/util/mac.spec.js.map +1 -0
  249. package/dist/version.spec.d.ts +2 -0
  250. package/dist/version.spec.d.ts.map +1 -0
  251. package/dist/version.spec.js +20 -0
  252. package/dist/version.spec.js.map +1 -0
  253. package/package.json +4 -4
  254. package/dist/matter/matterAccessoryCache.d.ts.map +0 -1
  255. package/dist/matter/matterAccessoryCache.js.map +0 -1
  256. package/dist/matter/matterBehaviors.d.ts +0 -194
  257. package/dist/matter/matterBehaviors.d.ts.map +0 -1
  258. package/dist/matter/matterBehaviors.js +0 -665
  259. package/dist/matter/matterBehaviors.js.map +0 -1
  260. package/dist/matter/matterConfigValidator.d.ts.map +0 -1
  261. package/dist/matter/matterConfigValidator.js.map +0 -1
  262. package/dist/matter/matterErrorHandler.d.ts +0 -106
  263. package/dist/matter/matterErrorHandler.d.ts.map +0 -1
  264. package/dist/matter/matterErrorHandler.js +0 -495
  265. package/dist/matter/matterErrorHandler.js.map +0 -1
  266. package/dist/matter/matterLogFormatter.d.ts.map +0 -1
  267. package/dist/matter/matterLogFormatter.js.map +0 -1
  268. package/dist/matter/matterNetworkMonitor.d.ts +0 -68
  269. package/dist/matter/matterNetworkMonitor.d.ts.map +0 -1
  270. package/dist/matter/matterNetworkMonitor.js +0 -249
  271. package/dist/matter/matterNetworkMonitor.js.map +0 -1
  272. package/dist/matter/matterServer.d.ts.map +0 -1
  273. package/dist/matter/matterServer.js.map +0 -1
  274. package/dist/matter/matterServerHelpers.d.ts.map +0 -1
  275. package/dist/matter/matterServerHelpers.js.map +0 -1
  276. package/dist/matter/matterSharedTypes.d.ts.map +0 -1
  277. package/dist/matter/matterSharedTypes.js.map +0 -1
  278. package/dist/matter/matterStorage.d.ts.map +0 -1
  279. package/dist/matter/matterTypes.d.ts.map +0 -1
  280. package/dist/matter/matterTypes.js.map +0 -1
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { randomBytes } from 'node:crypto';
8
8
  import { EventEmitter } from 'node:events';
9
- import { constants, existsSync } from 'node:fs';
9
+ import { constants, watch as fsWatch } from 'node:fs';
10
10
  import { access, writeFile } from 'node:fs/promises';
11
11
  import { homedir, release } from 'node:os';
12
12
  import { join, normalize, resolve } from 'node:path';
@@ -21,15 +21,15 @@ import fse from 'fs-extra';
21
21
  import QRCode from 'qrcode-terminal';
22
22
  import { Logger } from '../logger.js';
23
23
  import getVersion from '../version.js';
24
- import { MatterAccessoryCache } from './matterAccessoryCache.js';
25
- import { HomebridgeColorControlServer, HomebridgeDoorLockServer, HomebridgeFanControlServer, HomebridgeIdentifyServer, HomebridgeLevelControlServer, HomebridgeOnOffServer, HomebridgeRvcCleanModeServer, HomebridgeRvcOperationalStateServer, HomebridgeRvcRunModeServer, HomebridgeServiceAreaServer, HomebridgeThermostatServer, HomebridgeWindowCoveringServer, registerHandler, registerPartEndpoint, setAccessoriesMap, } from './matterBehaviors.js';
26
- import { sanitizeUniqueId, truncateString, validatePort } from './matterConfigValidator.js';
27
- import { errorHandler } from './matterErrorHandler.js';
28
- import { createHomebridgeLogFormatter } from './matterLogFormatter.js';
29
- import { networkMonitor } from './matterNetworkMonitor.js';
30
- import { applyWindowCoveringFeatures, CLUSTER_IDS, detectBehaviorFeatures, detectWindowCoveringFeatures, determineColorControlFeaturesFromHandlers, extractColorControlFeatures, extractThermostatFeatures, validateAccessoryRequiredFields, } from './matterServerHelpers.js';
31
- import { MatterStorageManager } from './matterStorage.js';
32
- import { deviceTypes, MatterDeviceError, } from './matterTypes.js';
24
+ import { MatterAccessoryCache } from './accessoryCache.js';
25
+ import { BehaviorRegistry, HomebridgeColorControlServer, HomebridgeDoorLockServer, HomebridgeFanControlServer, HomebridgeIdentifyServer, HomebridgeLevelControlServer, HomebridgeOnOffServer, HomebridgeRvcCleanModeServer, HomebridgeRvcOperationalStateServer, HomebridgeRvcRunModeServer, HomebridgeServiceAreaServer, HomebridgeThermostatServer, HomebridgeWindowCoveringServer, } from './behaviors/index.js';
26
+ import { sanitizeUniqueId, truncateString, validatePort } from './configValidator.js';
27
+ import { errorHandler } from './errorHandler.js';
28
+ import { createHomebridgeLogFormatter } from './logFormatter.js';
29
+ import { applyWindowCoveringFeatures, CLUSTER_IDS, detectBehaviorFeatures, detectWindowCoveringFeatures, determineColorControlFeaturesFromHandlers, extractColorControlFeatures, extractThermostatFeatures, validateAccessoryRequiredFields, } from './serverHelpers.js';
30
+ import { MatterStorageManager } from './storage.js';
31
+ import { isDeviceType, withBehaviors, withFeatures } from './typeHelpers.js';
32
+ import { deviceTypes, MatterDeviceError, } from './types.js';
33
33
  const log = Logger.withPrefix('Matter/Server');
34
34
  /**
35
35
  * Constants for Matter server configuration
@@ -54,9 +54,31 @@ export class MatterServer extends EventEmitter {
54
54
  serverNode = null;
55
55
  aggregator = null;
56
56
  accessories = new Map();
57
+ behaviorRegistry;
57
58
  isRunning = false;
58
59
  MAX_DEVICES = MAX_DEVICES_PER_BRIDGE;
59
60
  shutdownHandler = null;
61
+ // Map cluster names to custom behavior classes
62
+ // Only clusters with user-triggered commands need custom behaviors
63
+ static CLUSTER_BEHAVIOR_MAP = {
64
+ // Core controls
65
+ onOff: HomebridgeOnOffServer,
66
+ levelControl: HomebridgeLevelControlServer,
67
+ colorControl: HomebridgeColorControlServer,
68
+ // Coverings & locks
69
+ windowCovering: HomebridgeWindowCoveringServer,
70
+ doorLock: HomebridgeDoorLockServer,
71
+ // Climate control
72
+ fanControl: HomebridgeFanControlServer,
73
+ thermostat: HomebridgeThermostatServer,
74
+ // Robotic vacuum cleaners
75
+ rvcOperationalState: HomebridgeRvcOperationalStateServer,
76
+ rvcRunMode: HomebridgeRvcRunModeServer,
77
+ rvcCleanMode: HomebridgeRvcCleanModeServer,
78
+ serviceArea: HomebridgeServiceAreaServer,
79
+ // Identification
80
+ identify: HomebridgeIdentifyServer,
81
+ };
60
82
  // Internal commissioning values (generated, not user-configurable)
61
83
  passcode = 0;
62
84
  discriminator = 0;
@@ -70,6 +92,7 @@ export class MatterServer extends EventEmitter {
70
92
  accessoryCache = null;
71
93
  // Fabric monitoring
72
94
  fabricMonitorInterval = null;
95
+ fabricCheckTimeout = null;
73
96
  previousFabrics = new Map();
74
97
  constructor(config) {
75
98
  super();
@@ -95,8 +118,21 @@ export class MatterServer extends EventEmitter {
95
118
  // Initialize commissioning values (will be loaded from storage in start())
96
119
  this.vendorId = DEFAULT_VENDOR_ID;
97
120
  this.productId = DEFAULT_PRODUCT_ID;
98
- // Provide accessories map reference to behaviors for cache syncing
99
- setAccessoriesMap(this.accessories);
121
+ // Create behavior registry and set it on all behavior classes
122
+ this.behaviorRegistry = new BehaviorRegistry(this.accessories);
123
+ // Set the registry on all custom behavior classes
124
+ HomebridgeOnOffServer.setRegistry(this.behaviorRegistry);
125
+ HomebridgeLevelControlServer.setRegistry(this.behaviorRegistry);
126
+ HomebridgeColorControlServer.setRegistry(this.behaviorRegistry);
127
+ HomebridgeWindowCoveringServer.setRegistry(this.behaviorRegistry);
128
+ HomebridgeDoorLockServer.setRegistry(this.behaviorRegistry);
129
+ HomebridgeFanControlServer.setRegistry(this.behaviorRegistry);
130
+ HomebridgeThermostatServer.setRegistry(this.behaviorRegistry);
131
+ HomebridgeIdentifyServer.setRegistry(this.behaviorRegistry);
132
+ HomebridgeRvcOperationalStateServer.setRegistry(this.behaviorRegistry);
133
+ HomebridgeRvcRunModeServer.setRegistry(this.behaviorRegistry);
134
+ HomebridgeRvcCleanModeServer.setRegistry(this.behaviorRegistry);
135
+ HomebridgeServiceAreaServer.setRegistry(this.behaviorRegistry);
100
136
  }
101
137
  /**
102
138
  * Validate and sanitize Matter server configuration
@@ -239,7 +275,8 @@ export class MatterServer extends EventEmitter {
239
275
  const digitCounts = new Map();
240
276
  for (const digit of passcodeStr) {
241
277
  digitCounts.set(digit, (digitCounts.get(digit) || 0) + 1);
242
- if (digitCounts.get(digit) > 3) {
278
+ const count = digitCounts.get(digit);
279
+ if (count !== undefined && count > 3) {
243
280
  return false;
244
281
  }
245
282
  }
@@ -285,10 +322,12 @@ export class MatterServer extends EventEmitter {
285
322
  }
286
323
  catch (error) {
287
324
  // Check if this is a storage corruption error
288
- const isStorageError = error?.message?.includes('Invalid public key encoding')
289
- || error?.message?.includes('FabricManager unavailable')
290
- || error?.message?.includes('key-input')
291
- || error?.cause?.message?.includes('Invalid public key encoding');
325
+ const errorMessage = error instanceof Error ? error.message : '';
326
+ const causeMessage = error instanceof Error && error.cause instanceof Error ? error.cause.message : '';
327
+ const isStorageError = errorMessage.includes('Invalid public key encoding')
328
+ || errorMessage.includes('FabricManager unavailable')
329
+ || errorMessage.includes('key-input')
330
+ || causeMessage.includes('Invalid public key encoding');
292
331
  if (!isStorageError) {
293
332
  // Not a storage error, rethrow
294
333
  throw error;
@@ -306,18 +345,32 @@ export class MatterServer extends EventEmitter {
306
345
  const serverNodeStoreJsonFile = `${serverNodeStorePath}.json`;
307
346
  try {
308
347
  let removedSomething = false;
309
- // Delete the ServerNodeStore subdirectory
310
- if (existsSync(serverNodeStorePath)) {
348
+ // Delete the ServerNodeStore subdirectory (async check and removal)
349
+ try {
350
+ await fse.stat(serverNodeStorePath);
311
351
  log.info(`Removing corrupted ServerNodeStore directory: ${serverNodeStorePath}`);
312
352
  await fse.remove(serverNodeStorePath);
313
353
  removedSomething = true;
314
354
  }
355
+ catch (err) {
356
+ const code = err instanceof Error && 'code' in err ? err.code : undefined;
357
+ if (code !== 'ENOENT') {
358
+ throw err;
359
+ }
360
+ }
315
361
  // Delete the ServerNodeStore JSON file (contains fabric data)
316
- if (existsSync(serverNodeStoreJsonFile)) {
362
+ try {
363
+ await fse.stat(serverNodeStoreJsonFile);
317
364
  log.info(`Removing corrupted ServerNodeStore JSON file: ${serverNodeStoreJsonFile}`);
318
365
  await fse.remove(serverNodeStoreJsonFile);
319
366
  removedSomething = true;
320
367
  }
368
+ catch (err) {
369
+ const code = err instanceof Error && 'code' in err ? err.code : undefined;
370
+ if (code !== 'ENOENT') {
371
+ throw err;
372
+ }
373
+ }
321
374
  if (removedSomething) {
322
375
  log.info('Corrupted storage removed, retrying ServerNode creation...');
323
376
  }
@@ -353,9 +406,6 @@ export class MatterServer extends EventEmitter {
353
406
  // Load or generate commissioning credentials
354
407
  await this.loadOrGenerateCredentials();
355
408
  log.info(`Configuration: Port=${this.config.port}, Passcode=${this.passcode}, Discriminator=${this.discriminator}`);
356
- // Start network monitoring
357
- networkMonitor.startMonitoring();
358
- this.cleanupHandlers.push(() => networkMonitor.stopMonitoring());
359
409
  // Configure network interfaces if specified in the config
360
410
  if (this.config.networkInterfaces && this.config.networkInterfaces.length > 0) {
361
411
  const environment = Environment.default;
@@ -474,7 +524,17 @@ export class MatterServer extends EventEmitter {
474
524
  }
475
525
  /**
476
526
  * Run the server after devices have been added (for external accessory mode)
477
- * This must be called after registerPlatformAccessories() when using externalAccessory mode
527
+ *
528
+ * This must be called after registerPlatformAccessories() when using externalAccessory mode.
529
+ * In bridge mode, the server starts automatically when accessories are registered.
530
+ *
531
+ * @throws {MatterDeviceError} If server node is not initialized or server is already running
532
+ * @example
533
+ * ```typescript
534
+ * await matterServer.start()
535
+ * await matterServer.registerPlatformAccessories('plugin', 'platform', accessories)
536
+ * await matterServer.runServer() // External accessory mode only
537
+ * ```
478
538
  */
479
539
  async runServer() {
480
540
  if (!this.serverNode) {
@@ -655,7 +715,8 @@ export class MatterServer extends EventEmitter {
655
715
  log.debug(`Saved commissioning info to ${commissioningFilePath}`);
656
716
  }
657
717
  catch (error) {
658
- log.warn(`Failed to save commissioning info to disk: ${error.message}`);
718
+ const errorMessage = error instanceof Error ? error.message : String(error);
719
+ log.warn(`Failed to save commissioning info to disk: ${errorMessage}`);
659
720
  }
660
721
  // Display commissioning information
661
722
  log.info(`\n${'='.repeat(60)}`);
@@ -690,6 +751,7 @@ export class MatterServer extends EventEmitter {
690
751
  }
691
752
  /**
692
753
  * Start monitoring fabric changes to emit commissioning events
754
+ * Uses file watching instead of polling for better performance
693
755
  */
694
756
  startFabricMonitoring() {
695
757
  // Stop any existing monitor
@@ -700,22 +762,63 @@ export class MatterServer extends EventEmitter {
700
762
  this.previousFabrics.set(fabric.fabricIndex, fabric);
701
763
  }
702
764
  log.debug('Starting fabric monitoring for commissioning events');
703
- // Set up periodic monitoring
704
- this.fabricMonitorInterval = setInterval(() => {
705
- this.checkFabricChanges();
706
- }, FABRIC_MONITOR_INTERVAL_MS);
765
+ // Watch the storage file for fabric changes instead of polling
766
+ // This is much more efficient - only checks when the file actually changes
767
+ if (this.storageManager && this.matterStoragePath) {
768
+ const fabricStorageFile = join(this.matterStoragePath, `${this.config.uniqueId}.json`);
769
+ try {
770
+ // Use fs.watch for efficient file monitoring
771
+ this.fabricMonitorInterval = fsWatch(fabricStorageFile, (eventType) => {
772
+ if (eventType === 'change') {
773
+ // Debounce rapid changes (file writes may trigger multiple events)
774
+ if (this.fabricCheckTimeout) {
775
+ clearTimeout(this.fabricCheckTimeout);
776
+ }
777
+ this.fabricCheckTimeout = setTimeout(() => {
778
+ this.checkFabricChanges();
779
+ }, 100); // 100ms debounce
780
+ }
781
+ });
782
+ log.debug(`Watching fabric storage file for changes: ${fabricStorageFile}`);
783
+ }
784
+ catch (error) {
785
+ // Fall back to polling if file watching fails
786
+ log.warn('Failed to set up file watcher for fabric monitoring, falling back to polling:', error);
787
+ this.fabricMonitorInterval = setInterval(() => {
788
+ this.checkFabricChanges();
789
+ }, FABRIC_MONITOR_INTERVAL_MS);
790
+ }
791
+ }
792
+ else {
793
+ // Fallback to polling if storage not available
794
+ this.fabricMonitorInterval = setInterval(() => {
795
+ this.checkFabricChanges();
796
+ }, FABRIC_MONITOR_INTERVAL_MS);
797
+ }
707
798
  // Add to clean up handlers
708
799
  this.cleanupHandlers.push(() => this.stopFabricMonitoring());
709
800
  }
710
801
  /**
711
- * Stop fabric monitoring
802
+ * Stop fabric monitoring and clean up watchers/timers
712
803
  */
713
804
  stopFabricMonitoring() {
714
805
  if (this.fabricMonitorInterval) {
715
- clearInterval(this.fabricMonitorInterval);
806
+ // Could be either a setInterval or a file watcher
807
+ if (typeof this.fabricMonitorInterval === 'object' && 'close' in this.fabricMonitorInterval) {
808
+ // It's a file watcher
809
+ this.fabricMonitorInterval.close();
810
+ }
811
+ else {
812
+ // It's a setInterval
813
+ clearInterval(this.fabricMonitorInterval);
814
+ }
716
815
  this.fabricMonitorInterval = null;
717
816
  log.debug('Stopped fabric monitoring');
718
817
  }
818
+ if (this.fabricCheckTimeout) {
819
+ clearTimeout(this.fabricCheckTimeout);
820
+ this.fabricCheckTimeout = null;
821
+ }
719
822
  }
720
823
  /**
721
824
  * Check for fabric changes and emit appropriate events
@@ -794,11 +897,21 @@ export class MatterServer extends EventEmitter {
794
897
  log.debug('Updated commissioning info file');
795
898
  }
796
899
  catch (error) {
797
- log.debug(`Failed to update commissioning info file: ${error.message}`);
900
+ const errorMessage = error instanceof Error ? error.message : String(error);
901
+ log.debug(`Failed to update commissioning info file: ${errorMessage}`);
798
902
  }
799
903
  }
800
904
  /**
801
905
  * Register Matter platform accessories (Plugin API - matches HAP pattern)
906
+ *
907
+ * Registers Matter accessories from a dynamic platform plugin. Accessories are stored
908
+ * and automatically restored on server restart.
909
+ *
910
+ * @param pluginIdentifier - The plugin identifier (e.g., 'homebridge-example')
911
+ * @param platformName - The platform name from config.json
912
+ * @param accessories - Array of Matter accessories to register
913
+ * @throws {MatterDeviceError} If maximum device limit is reached or accessory is invalid
914
+ * @see {@link MatterAccessory} for accessory structure
802
915
  */
803
916
  async registerPlatformAccessories(pluginIdentifier, platformName, accessories) {
804
917
  for (const accessory of accessories) {
@@ -813,6 +926,36 @@ export class MatterServer extends EventEmitter {
813
926
  await this.unregisterAccessory(accessory.uuid);
814
927
  }
815
928
  }
929
+ /**
930
+ * Update Matter platform accessories in the cache
931
+ * Similar to api.updatePlatformAccessories() for HAP accessories
932
+ *
933
+ * This updates the cached accessory information without unregistering and re-registering.
934
+ * Useful when device metadata changes (name, manufacturer, firmware version, etc.)
935
+ */
936
+ async updatePlatformAccessories(accessories) {
937
+ if (!this.accessoryCache) {
938
+ log.warn('Cannot update Matter platform accessories - cache not initialized');
939
+ return;
940
+ }
941
+ for (const accessory of accessories) {
942
+ const internal = accessory;
943
+ // Verify accessory exists in current session and cache
944
+ if (!this.accessories.has(accessory.uuid)) {
945
+ log.warn(`Cannot update Matter accessory ${accessory.uuid} - not registered in current session`);
946
+ continue;
947
+ }
948
+ if (!this.accessoryCache.hasCached(accessory.uuid)) {
949
+ log.warn(`Cannot update Matter accessory ${accessory.uuid} - not found in cache`);
950
+ continue;
951
+ }
952
+ // Update the in-memory accessory
953
+ this.accessories.set(accessory.uuid, internal);
954
+ log.debug(`Updated Matter accessory ${accessory.uuid} (${accessory.displayName})`);
955
+ }
956
+ // Save updated accessories to cache
957
+ this.accessoryCache.requestSave(this.accessories);
958
+ }
816
959
  /**
817
960
  * Register a single Matter accessory (internal method)
818
961
  */
@@ -832,6 +975,69 @@ export class MatterServer extends EventEmitter {
832
975
  + `New accessory: "${accessory.displayName}"\n`
833
976
  + 'Each accessory must have a unique UUID. Use api.hap.uuid.generate() with a unique string.');
834
977
  }
978
+ // Restore cached state if available
979
+ this.restoreCachedState(accessory);
980
+ // Check device limit
981
+ if (this.accessories.size >= this.MAX_DEVICES) {
982
+ throw new MatterDeviceError(`Cannot register Matter accessory "${accessory.displayName}": `
983
+ + `Maximum device limit reached (${this.MAX_DEVICES} devices).\n`
984
+ + `Current registered devices: ${this.accessories.size}`);
985
+ }
986
+ try {
987
+ // Prepare device type with WindowCovering features
988
+ let deviceType = accessory.deviceType;
989
+ const windowCoveringFeatures = detectWindowCoveringFeatures(accessory);
990
+ if (windowCoveringFeatures.length > 0) {
991
+ deviceType = applyWindowCoveringFeatures(deviceType, accessory, windowCoveringFeatures);
992
+ }
993
+ // Detect cluster features for behavior configuration
994
+ const features = this.detectClusterFeatures(accessory, deviceType);
995
+ // Build and apply custom behaviors based on handlers
996
+ const customBehaviors = this.buildCustomBehaviors(accessory, deviceType, features);
997
+ if (customBehaviors.length > 0) {
998
+ deviceType = withBehaviors(deviceType, customBehaviors);
999
+ log.info(`Applied ${customBehaviors.length} custom behavior(s) to device type`);
1000
+ }
1001
+ // Add BridgedDeviceBasicInformationServer for bridged devices only
1002
+ // This is required by the Matter spec for devices behind an aggregator
1003
+ // External accessories should NOT have this cluster
1004
+ if (!this.config.externalAccessory) {
1005
+ deviceType = withBehaviors(deviceType, [BridgedDeviceBasicInformationServer]);
1006
+ log.debug(`Added BridgedDeviceBasicInformationServer to ${accessory.displayName}`);
1007
+ }
1008
+ // Create endpoint with cluster states
1009
+ const endpointOptions = this.createEndpointOptions(accessory);
1010
+ const endpoint = new Endpoint(deviceType, endpointOptions);
1011
+ if (this.config.debugModeEnabled) {
1012
+ log.debug(`Created endpoint for ${accessory.displayName} with initial cluster states`);
1013
+ }
1014
+ // Add endpoint to aggregator or serverNode depending on mode
1015
+ if (this.config.externalAccessory) {
1016
+ await this.serverNode.add(endpoint);
1017
+ log.debug(`Added ${accessory.displayName} as external accessory to ServerNode`);
1018
+ }
1019
+ else {
1020
+ await this.aggregator.add(endpoint);
1021
+ if (this.config.debugModeEnabled) {
1022
+ log.debug(`Added endpoint for ${accessory.displayName} to aggregator`);
1023
+ }
1024
+ }
1025
+ // Register command handlers
1026
+ this.registerAccessoryHandlers(accessory);
1027
+ // Create and register child endpoints (parts)
1028
+ const internalParts = await this.createAccessoryParts(accessory);
1029
+ // Finalize registration (store, emit events, save cache)
1030
+ await this.finalizeAccessoryRegistration(accessory, endpoint, internalParts);
1031
+ }
1032
+ catch (error) {
1033
+ log.error(`Failed to register Matter accessory ${accessory.displayName}:`, error);
1034
+ throw new MatterDeviceError(`Failed to register accessory: ${error}`);
1035
+ }
1036
+ }
1037
+ /**
1038
+ * Restore cached state for an accessory
1039
+ */
1040
+ restoreCachedState(accessory) {
835
1041
  // Check if there's a cached version - merge cached cluster states with new registration.
836
1042
  // This ensures state persistence across Homebridge restarts.
837
1043
  if (this.accessoryCache && this.accessoryCache.hasCached(accessory.uuid)) {
@@ -858,345 +1064,304 @@ export class MatterServer extends EventEmitter {
858
1064
  log.info(`Restored cached state for Matter accessory: ${accessory.displayName}`);
859
1065
  }
860
1066
  }
861
- // Check device limit
862
- if (this.accessories.size >= this.MAX_DEVICES) {
863
- throw new MatterDeviceError(`Cannot register Matter accessory "${accessory.displayName}": `
864
- + `Maximum device limit reached (${this.MAX_DEVICES} devices).\n`
865
- + `Current registered devices: ${this.accessories.size}`);
1067
+ }
1068
+ /**
1069
+ * Detect cluster features for an accessory
1070
+ * Returns an object containing detected features for various clusters
1071
+ */
1072
+ detectClusterFeatures(accessory, deviceType) {
1073
+ // Detect WindowCovering features
1074
+ const windowCoveringFeatures = detectWindowCoveringFeatures(accessory);
1075
+ // Detect ServiceArea features
1076
+ let serviceAreaFeatures = null;
1077
+ if (accessory.clusters?.serviceArea) {
1078
+ const features = [];
1079
+ // Check if Maps feature should be enabled (when supportedMaps is defined)
1080
+ if (accessory.clusters.serviceArea.supportedMaps) {
1081
+ features.push('Maps');
1082
+ }
1083
+ // Check if ProgressReporting feature should be enabled (when progress is defined)
1084
+ if (accessory.clusters.serviceArea.progress !== undefined) {
1085
+ features.push('ProgressReporting');
1086
+ }
1087
+ if (features.length > 0) {
1088
+ serviceAreaFeatures = features;
1089
+ log.info(`ServiceArea features will be enabled for ${accessory.displayName}: ${features.join(', ')}`);
1090
+ }
866
1091
  }
867
- try {
868
- // Modify device type with custom behaviors if handlers are defined
869
- let deviceType = accessory.deviceType;
870
- // Detect WindowCovering features
871
- const windowCoveringFeatures = detectWindowCoveringFeatures(accessory);
872
- if (windowCoveringFeatures.length > 0) {
873
- deviceType = applyWindowCoveringFeatures(deviceType, accessory, windowCoveringFeatures);
1092
+ // Detect ColorControl features
1093
+ let colorControlFeatures = null;
1094
+ if (accessory.handlers?.colorControl) {
1095
+ colorControlFeatures = detectBehaviorFeatures(deviceType, CLUSTER_IDS.COLOR_CONTROL, extractColorControlFeatures);
1096
+ if (colorControlFeatures) {
1097
+ colorControlFeatures = determineColorControlFeaturesFromHandlers(accessory.handlers.colorControl);
874
1098
  }
875
- if (accessory.handlers) {
876
- log.warn(`📝 [${accessory.displayName}] Has handlers: ${Object.keys(accessory.handlers).join(', ')}`);
877
- // IMPORTANT: Detect ServiceArea features BEFORE modifying device type
878
- let serviceAreaFeatures = null;
879
- if (accessory.clusters?.serviceArea) {
880
- const features = [];
881
- // Check if Maps feature should be enabled (when supportedMaps is defined)
882
- if (accessory.clusters.serviceArea.supportedMaps) {
883
- features.push('Maps');
884
- }
885
- // Check if ProgressReporting feature should be enabled (when progress is defined)
886
- if (accessory.clusters.serviceArea.progress !== undefined) {
887
- features.push('ProgressReporting');
888
- }
889
- if (features.length > 0) {
890
- serviceAreaFeatures = features;
891
- log.info(`ServiceArea features will be enabled for ${accessory.displayName}: ${features.join(', ')}`);
1099
+ }
1100
+ // Detect Thermostat features
1101
+ let thermostatFeatures = null;
1102
+ if (accessory.handlers?.thermostat) {
1103
+ thermostatFeatures = detectBehaviorFeatures(deviceType, CLUSTER_IDS.THERMOSTAT, extractThermostatFeatures);
1104
+ }
1105
+ return {
1106
+ windowCoveringFeatures,
1107
+ serviceAreaFeatures,
1108
+ colorControlFeatures,
1109
+ thermostatFeatures,
1110
+ };
1111
+ }
1112
+ /**
1113
+ * Build custom behaviors for an accessory based on handlers
1114
+ */
1115
+ buildCustomBehaviors(accessory, deviceType, features) {
1116
+ const customBehaviors = [];
1117
+ if (!accessory.handlers) {
1118
+ return customBehaviors;
1119
+ }
1120
+ log.debug(`[${accessory.displayName}] Has handlers: ${Object.keys(accessory.handlers).join(', ')}`);
1121
+ // Use the static cluster behavior map
1122
+ const behaviorMap = MatterServer.CLUSTER_BEHAVIOR_MAP;
1123
+ // For RoboticVacuumCleaner, add optional clusters if they're defined in accessory.clusters
1124
+ // These clusters need to be added to the device type even if there are no handlers
1125
+ if (isDeviceType(deviceType, devices.RoboticVacuumCleanerDevice)) {
1126
+ // Import RVC requirements
1127
+ const { RvcCleanModeServer, ServiceAreaServer } = devices.RoboticVacuumCleanerRequirements;
1128
+ // Add RvcCleanMode if defined in clusters
1129
+ if (accessory.clusters?.rvcCleanMode) {
1130
+ // Check if there's a custom behavior with handlers
1131
+ if (accessory.handlers?.rvcCleanMode) {
1132
+ const behaviorClass = HomebridgeRvcCleanModeServer;
1133
+ customBehaviors.push(behaviorClass);
1134
+ log.info('Adding custom RvcCleanMode behavior with handlers');
1135
+ }
1136
+ else {
1137
+ // No handlers, use base server
1138
+ customBehaviors.push(RvcCleanModeServer);
1139
+ log.info('Adding base RvcCleanMode server');
1140
+ }
1141
+ }
1142
+ // Add ServiceArea if defined in clusters
1143
+ if (accessory.clusters?.serviceArea) {
1144
+ // Check if there's a custom behavior with handlers
1145
+ if (accessory.handlers?.serviceArea) {
1146
+ let behaviorClass = HomebridgeServiceAreaServer;
1147
+ // Apply features if detected
1148
+ if (features.serviceAreaFeatures && features.serviceAreaFeatures.length > 0) {
1149
+ behaviorClass = withFeatures(behaviorClass, features.serviceAreaFeatures);
1150
+ log.info(`ServiceArea custom behavior will have features: ${features.serviceAreaFeatures.join(', ')}`);
892
1151
  }
1152
+ customBehaviors.push(behaviorClass);
1153
+ log.info('Adding custom ServiceArea behavior with handlers');
893
1154
  }
894
- // Detect ColorControl features
895
- let colorControlFeatures = null;
896
- if (accessory.handlers.colorControl) {
897
- colorControlFeatures = detectBehaviorFeatures(deviceType, CLUSTER_IDS.COLOR_CONTROL, extractColorControlFeatures);
898
- if (colorControlFeatures) {
899
- colorControlFeatures = determineColorControlFeaturesFromHandlers(accessory.handlers.colorControl);
1155
+ else {
1156
+ // No handlers, use base server with features
1157
+ let behaviorClass = ServiceAreaServer;
1158
+ if (features.serviceAreaFeatures && features.serviceAreaFeatures.length > 0) {
1159
+ behaviorClass = withFeatures(behaviorClass, features.serviceAreaFeatures);
1160
+ log.info(`ServiceArea base server will have features: ${features.serviceAreaFeatures.join(', ')}`);
900
1161
  }
1162
+ customBehaviors.push(behaviorClass);
1163
+ log.info('Adding base ServiceArea server');
901
1164
  }
902
- // Detect Thermostat features
903
- let thermostatFeatures = null;
904
- if (accessory.handlers.thermostat) {
905
- thermostatFeatures = detectBehaviorFeatures(deviceType, CLUSTER_IDS.THERMOSTAT, extractThermostatFeatures);
1165
+ }
1166
+ }
1167
+ for (const clusterName of Object.keys(accessory.handlers || {})) {
1168
+ // Skip windowCovering if we already applied features via base WindowCoveringServer
1169
+ const skipWindowCoveringBehavior = accessory.context?._skipWindowCoveringBehavior;
1170
+ if (clusterName === 'windowCovering' && skipWindowCoveringBehavior) {
1171
+ log.debug('Skipping custom WindowCovering behavior (using base server with features instead)');
1172
+ continue;
1173
+ }
1174
+ // Skip RVC clusters - they're handled specially above for RoboticVacuumCleaner
1175
+ if (clusterName === 'rvcCleanMode' || clusterName === 'serviceArea') {
1176
+ continue;
1177
+ }
1178
+ let behaviorClass = behaviorMap[clusterName];
1179
+ // Apply ColorControl features if we detected them earlier
1180
+ if (clusterName === 'colorControl' && behaviorClass && features.colorControlFeatures && features.colorControlFeatures.length > 0) {
1181
+ behaviorClass = withFeatures(behaviorClass, features.colorControlFeatures);
1182
+ log.info(`ColorControl custom behavior will preserve features: ${features.colorControlFeatures.join(', ')}`);
1183
+ }
1184
+ // Apply Thermostat features if we detected them earlier
1185
+ if (clusterName === 'thermostat' && behaviorClass && features.thermostatFeatures && features.thermostatFeatures.length > 0) {
1186
+ behaviorClass = withFeatures(behaviorClass, features.thermostatFeatures);
1187
+ log.info(`Thermostat custom behavior will preserve features: ${features.thermostatFeatures.join(', ')}`);
1188
+ }
1189
+ // Apply ServiceArea features if we detected them earlier
1190
+ if (clusterName === 'serviceArea' && behaviorClass && features.serviceAreaFeatures && features.serviceAreaFeatures.length > 0) {
1191
+ behaviorClass = withFeatures(behaviorClass, features.serviceAreaFeatures);
1192
+ log.info(`ServiceArea custom behavior will preserve features: ${features.serviceAreaFeatures.join(', ')}`);
1193
+ }
1194
+ // Apply WindowCovering features to custom behavior as well
1195
+ // (features were already applied to base device type, but custom behavior needs them too)
1196
+ if (clusterName === 'windowCovering') {
1197
+ log.debug(`WindowCovering handler found: behaviorClass=${!!behaviorClass}, windowCoveringFeatures=${features.windowCoveringFeatures}, length=${features.windowCoveringFeatures?.length}`);
1198
+ if (behaviorClass && features.windowCoveringFeatures && features.windowCoveringFeatures.length > 0) {
1199
+ behaviorClass = withFeatures(behaviorClass, features.windowCoveringFeatures);
1200
+ log.debug(`WindowCovering custom behavior will have features: ${features.windowCoveringFeatures.join(', ')}`);
906
1201
  }
907
- // Map cluster names to custom behavior classes
908
- // Only clusters with user-triggered commands need custom behaviors
909
- const behaviorMap = {
910
- // Core controls
911
- onOff: HomebridgeOnOffServer,
912
- levelControl: HomebridgeLevelControlServer,
913
- colorControl: HomebridgeColorControlServer,
914
- // Coverings & locks
915
- windowCovering: HomebridgeWindowCoveringServer,
916
- doorLock: HomebridgeDoorLockServer,
917
- // Climate control
918
- fanControl: HomebridgeFanControlServer,
919
- thermostat: HomebridgeThermostatServer,
920
- // Robotic vacuum cleaners
921
- rvcOperationalState: HomebridgeRvcOperationalStateServer,
922
- rvcRunMode: HomebridgeRvcRunModeServer,
923
- rvcCleanMode: HomebridgeRvcCleanModeServer,
924
- serviceArea: HomebridgeServiceAreaServer,
925
- // Identification
926
- identify: HomebridgeIdentifyServer,
927
- };
928
- // Build array of custom behaviors to apply based on what handlers are defined
929
- const customBehaviors = [];
930
- // For RoboticVacuumCleaner, add optional clusters if they're defined in accessory.clusters
931
- // These clusters need to be added to the device type even if there are no handlers
932
- if (deviceType === devices.RoboticVacuumCleanerDevice || deviceType.name === 'RoboticVacuumCleaner') {
933
- // Import RVC requirements
934
- const { RvcCleanModeServer, ServiceAreaServer } = devices.RoboticVacuumCleanerRequirements;
935
- // Add RvcCleanMode if defined in clusters
936
- if (accessory.clusters?.rvcCleanMode) {
937
- // Check if there's a custom behavior with handlers
938
- if (accessory.handlers?.rvcCleanMode) {
939
- const behaviorClass = HomebridgeRvcCleanModeServer;
940
- customBehaviors.push(behaviorClass);
941
- log.info('Adding custom RvcCleanMode behavior with handlers');
942
- }
943
- else {
944
- // No handlers, use base server
945
- customBehaviors.push(RvcCleanModeServer);
946
- log.info('Adding base RvcCleanMode server');
947
- }
948
- }
949
- // Add ServiceArea if defined in clusters
950
- if (accessory.clusters?.serviceArea) {
951
- // Check if there's a custom behavior with handlers
952
- if (accessory.handlers?.serviceArea) {
953
- let behaviorClass = HomebridgeServiceAreaServer;
954
- // Apply features if detected
955
- if (serviceAreaFeatures && serviceAreaFeatures.length > 0) {
956
- behaviorClass = behaviorClass.with(...serviceAreaFeatures);
957
- log.info(`ServiceArea custom behavior will have features: ${serviceAreaFeatures.join(', ')}`);
958
- }
959
- customBehaviors.push(behaviorClass);
960
- log.info('Adding custom ServiceArea behavior with handlers');
961
- }
962
- else {
963
- // No handlers, use base server with features
964
- let behaviorClass = ServiceAreaServer;
965
- if (serviceAreaFeatures && serviceAreaFeatures.length > 0) {
966
- behaviorClass = behaviorClass.with(...serviceAreaFeatures);
967
- log.info(`ServiceArea base server will have features: ${serviceAreaFeatures.join(', ')}`);
968
- }
969
- customBehaviors.push(behaviorClass);
970
- log.info('Adding base ServiceArea server');
971
- }
972
- }
1202
+ else {
1203
+ log.debug(`Skipping WindowCovering feature application: behaviorClass=${!!behaviorClass}, features=${features.windowCoveringFeatures}`);
973
1204
  }
974
- for (const clusterName of Object.keys(accessory.handlers || {})) {
975
- // Skip windowCovering if we already applied features via base WindowCoveringServer
976
- if (clusterName === 'windowCovering' && accessory.context?._skipWindowCoveringBehavior) {
977
- log.debug('Skipping custom WindowCovering behavior (using base server with features instead)');
978
- continue;
979
- }
980
- // Skip RVC clusters - they're handled specially above for RoboticVacuumCleaner
981
- if (clusterName === 'rvcCleanMode' || clusterName === 'serviceArea') {
982
- continue;
983
- }
984
- let behaviorClass = behaviorMap[clusterName];
985
- // Apply ColorControl features if we detected them earlier
986
- if (clusterName === 'colorControl' && behaviorClass && colorControlFeatures && colorControlFeatures.length > 0) {
987
- behaviorClass = behaviorClass.with(...colorControlFeatures);
988
- log.info(`ColorControl custom behavior will preserve features: ${colorControlFeatures.join(', ')}`);
989
- }
990
- // Apply Thermostat features if we detected them earlier
991
- if (clusterName === 'thermostat' && behaviorClass && thermostatFeatures && thermostatFeatures.length > 0) {
992
- behaviorClass = behaviorClass.with(...thermostatFeatures);
993
- log.info(`Thermostat custom behavior will preserve features: ${thermostatFeatures.join(', ')}`);
994
- }
995
- // Apply ServiceArea features if we detected them earlier
996
- if (clusterName === 'serviceArea' && behaviorClass && serviceAreaFeatures && serviceAreaFeatures.length > 0) {
997
- behaviorClass = behaviorClass.with(...serviceAreaFeatures);
998
- log.info(`ServiceArea custom behavior will preserve features: ${serviceAreaFeatures.join(', ')}`);
999
- }
1000
- // Apply WindowCovering features to custom behavior as well
1001
- // (features were already applied to base device type, but custom behavior needs them too)
1002
- if (clusterName === 'windowCovering') {
1003
- log.warn(`🔍 WindowCovering handler found: behaviorClass=${!!behaviorClass}, windowCoveringFeatures=${windowCoveringFeatures}, length=${windowCoveringFeatures?.length}`);
1004
- if (behaviorClass && windowCoveringFeatures && windowCoveringFeatures.length > 0) {
1005
- behaviorClass = behaviorClass.with(...windowCoveringFeatures);
1006
- log.warn(`🔧 WindowCovering custom behavior will have features: ${windowCoveringFeatures.join(', ')}`);
1007
- }
1008
- else {
1009
- log.warn(`⚠️ Skipping WindowCovering feature application: behaviorClass=${!!behaviorClass}, features=${windowCoveringFeatures}`);
1010
- }
1011
- }
1205
+ }
1206
+ if (behaviorClass) {
1207
+ customBehaviors.push(behaviorClass);
1208
+ log.info(`Will use ${behaviorClass.name} for ${accessory.displayName}`);
1209
+ }
1210
+ else {
1211
+ log.warn(`No custom behavior class available for cluster '${clusterName}' - handlers will be registered but may not be called`);
1212
+ }
1213
+ }
1214
+ return customBehaviors;
1215
+ }
1216
+ /**
1217
+ * Create endpoint options for an accessory
1218
+ */
1219
+ createEndpointOptions(accessory) {
1220
+ const endpointOptions = {
1221
+ id: accessory.uuid,
1222
+ ...accessory.clusters, // Spread cluster states as initial values
1223
+ };
1224
+ // Add bridgedDeviceBasicInformation cluster only for bridged devices
1225
+ // For external accessories, use the root basicInformation instead
1226
+ if (!this.config.externalAccessory) {
1227
+ endpointOptions.bridgedDeviceBasicInformation = {
1228
+ vendorName: accessory.manufacturer,
1229
+ nodeLabel: accessory.displayName, // Main end user name for the device
1230
+ productName: accessory.model,
1231
+ productLabel: accessory.displayName,
1232
+ serialNumber: accessory.serialNumber,
1233
+ reachable: true,
1234
+ };
1235
+ }
1236
+ return endpointOptions;
1237
+ }
1238
+ /**
1239
+ * Register command handlers for an accessory
1240
+ */
1241
+ registerAccessoryHandlers(accessory) {
1242
+ if (!accessory.handlers) {
1243
+ return;
1244
+ }
1245
+ log.info(`Setting up handlers for accessory ${accessory.uuid}`);
1246
+ // Register handlers with the custom behavior classes
1247
+ for (const [clusterName, handlers] of Object.entries(accessory.handlers)) {
1248
+ log.info(` Processing cluster: ${clusterName}`);
1249
+ for (const [commandName, handler] of Object.entries(handlers)) {
1250
+ this.behaviorRegistry.registerHandler(accessory.uuid, clusterName, commandName, handler);
1251
+ }
1252
+ }
1253
+ }
1254
+ /**
1255
+ * Create and register child endpoints (parts) for an accessory
1256
+ */
1257
+ async createAccessoryParts(accessory) {
1258
+ const internalParts = [];
1259
+ if (!accessory.parts || accessory.parts.length === 0) {
1260
+ return internalParts;
1261
+ }
1262
+ log.info(`Creating ${accessory.parts.length} child endpoint(s) for ${accessory.displayName}`);
1263
+ for (const part of accessory.parts) {
1264
+ // Create unique endpoint ID for this part
1265
+ const partEndpointId = `${accessory.uuid}-part-${part.id}`;
1266
+ // Register the part endpoint mapping for handler context
1267
+ this.behaviorRegistry.registerPartEndpoint(partEndpointId, accessory.uuid, part.id);
1268
+ // Apply custom behaviors to part based on its handlers (same logic as main accessory)
1269
+ let partDeviceType = part.deviceType;
1270
+ const partCustomBehaviors = [];
1271
+ if (part.handlers) {
1272
+ // Use the static cluster behavior map for parts as well
1273
+ const partBehaviorMap = MatterServer.CLUSTER_BEHAVIOR_MAP;
1274
+ for (const clusterName of Object.keys(part.handlers)) {
1275
+ const behaviorClass = partBehaviorMap[clusterName];
1012
1276
  if (behaviorClass) {
1013
- customBehaviors.push(behaviorClass);
1014
- log.info(`Will use ${behaviorClass.name} for ${accessory.displayName}`);
1277
+ partCustomBehaviors.push(behaviorClass);
1278
+ log.info(` Will use ${behaviorClass.name} for part ${part.id}`);
1015
1279
  }
1016
1280
  else {
1017
- log.warn(`No custom behavior class available for cluster '${clusterName}' - handlers will be registered but may not be called`);
1281
+ log.warn(`No custom behavior class available for cluster '${clusterName}' on part ${part.id}`);
1018
1282
  }
1019
1283
  }
1020
- if (customBehaviors.length > 0) {
1021
- // Cast to any to bypass TypeScript limitations
1022
- deviceType = deviceType.with(...customBehaviors);
1023
- log.info(`Applied ${customBehaviors.length} custom behavior(s) to device type`);
1284
+ if (partCustomBehaviors.length > 0) {
1285
+ // Add custom behaviors to part device type
1286
+ partDeviceType = withBehaviors(partDeviceType, partCustomBehaviors);
1287
+ log.info(` Applied ${partCustomBehaviors.length} custom behavior(s) to part ${part.id}`);
1024
1288
  }
1025
1289
  }
1026
- // Add BridgedDeviceBasicInformationServer for bridged devices only
1027
- // This is required by the Matter spec for devices behind an aggregator
1028
- // External accessories should NOT have this cluster
1290
+ // Add BridgedDeviceBasicInformationServer for bridged parts
1029
1291
  if (!this.config.externalAccessory) {
1030
- deviceType = deviceType.with(BridgedDeviceBasicInformationServer);
1031
- log.debug(`Added BridgedDeviceBasicInformationServer to ${accessory.displayName}`);
1292
+ partDeviceType = withBehaviors(partDeviceType, [BridgedDeviceBasicInformationServer]);
1032
1293
  }
1033
- // Create endpoint with the modified device type AND initial cluster states
1034
- // IMPORTANT: Cluster states must be provided at creation time, before adding to aggregator
1035
- // This ensures mandatory attributes are set before Matter.js validates the endpoint
1036
- const endpointOptions = {
1037
- id: accessory.uuid,
1038
- ...accessory.clusters, // Spread cluster states as initial values
1294
+ // Create endpoint options with cluster states
1295
+ const partEndpointOptions = {
1296
+ id: partEndpointId,
1297
+ ...part.clusters,
1039
1298
  };
1040
- // Add bridgedDeviceBasicInformation cluster only for bridged devices
1041
- // For external accessories, use the root basicInformation instead
1299
+ // Add bridgedDeviceBasicInformation for the part
1042
1300
  if (!this.config.externalAccessory) {
1043
- endpointOptions.bridgedDeviceBasicInformation = {
1301
+ partEndpointOptions.bridgedDeviceBasicInformation = {
1044
1302
  vendorName: accessory.manufacturer,
1045
- nodeLabel: accessory.displayName, // Main end user name for the device
1303
+ nodeLabel: part.displayName || `${accessory.displayName} - ${part.id}`,
1046
1304
  productName: accessory.model,
1047
- productLabel: accessory.displayName,
1048
- serialNumber: accessory.serialNumber,
1305
+ productLabel: part.displayName || part.id,
1306
+ serialNumber: `${accessory.serialNumber}-${part.id}`,
1049
1307
  reachable: true,
1050
1308
  };
1051
1309
  }
1052
- const endpoint = new Endpoint(deviceType, endpointOptions);
1053
- if (this.config.debugModeEnabled) {
1054
- log.debug(`Created endpoint for ${accessory.displayName} with initial cluster states`);
1055
- }
1056
- // Add to aggregator or serverNode depending on mode
1057
- // (validation happens here - cluster states must already be set)
1310
+ // Create the part endpoint
1311
+ const partEndpoint = new Endpoint(partDeviceType, partEndpointOptions);
1312
+ // Add part endpoint to aggregator or serverNode
1058
1313
  if (this.config.externalAccessory) {
1059
- // In external accessory mode, add device directly to serverNode as root endpoint
1060
- await this.serverNode.add(endpoint);
1061
- log.debug(`Added ${accessory.displayName} as external accessory to ServerNode`);
1314
+ await this.serverNode.add(partEndpoint);
1062
1315
  }
1063
1316
  else {
1064
- // In bridge mode, add to aggregator
1065
- await this.aggregator.add(endpoint);
1066
- if (this.config.debugModeEnabled) {
1067
- log.debug(`Added endpoint for ${accessory.displayName} to aggregator`);
1068
- }
1317
+ await this.aggregator.add(partEndpoint);
1069
1318
  }
1070
- // Set up command handlers if provided
1071
- if (accessory.handlers) {
1072
- log.info(`Setting up handlers for accessory ${accessory.uuid}`);
1073
- // Register handlers with the custom behavior classes
1074
- for (const [clusterName, handlers] of Object.entries(accessory.handlers)) {
1075
- log.info(` Processing cluster: ${clusterName}`);
1319
+ log.info(` Created part endpoint: ${part.displayName || part.id} (${partEndpointId})`);
1320
+ // Set up handlers for this part
1321
+ if (part.handlers) {
1322
+ for (const [clusterName, handlers] of Object.entries(part.handlers)) {
1076
1323
  for (const [commandName, handler] of Object.entries(handlers)) {
1077
- registerHandler(accessory.uuid, clusterName, commandName, handler);
1324
+ // Register handler with the part's endpoint ID
1325
+ this.behaviorRegistry.registerHandler(partEndpointId, clusterName, commandName, handler);
1078
1326
  }
1079
1327
  }
1328
+ log.debug(` Registered ${Object.keys(part.handlers).length} handler(s) for part ${part.id}`);
1080
1329
  }
1081
- // Create and register parts (child endpoints) if provided
1082
- const internalParts = [];
1083
- if (accessory.parts && accessory.parts.length > 0) {
1084
- log.info(`Creating ${accessory.parts.length} child endpoint(s) for ${accessory.displayName}`);
1085
- for (const part of accessory.parts) {
1086
- // Create unique endpoint ID for this part
1087
- const partEndpointId = `${accessory.uuid}-part-${part.id}`;
1088
- // Register the part endpoint mapping for handler context
1089
- registerPartEndpoint(partEndpointId, accessory.uuid, part.id);
1090
- // Apply custom behaviors to part based on its handlers (same logic as main accessory)
1091
- let partDeviceType = part.deviceType;
1092
- const partCustomBehaviors = [];
1093
- if (part.handlers) {
1094
- // Map cluster names to custom behavior classes for parts
1095
- const partBehaviorMap = {
1096
- onOff: HomebridgeOnOffServer,
1097
- levelControl: HomebridgeLevelControlServer,
1098
- colorControl: HomebridgeColorControlServer,
1099
- windowCovering: HomebridgeWindowCoveringServer,
1100
- doorLock: HomebridgeDoorLockServer,
1101
- fanControl: HomebridgeFanControlServer,
1102
- thermostat: HomebridgeThermostatServer,
1103
- identify: HomebridgeIdentifyServer,
1104
- };
1105
- for (const clusterName of Object.keys(part.handlers)) {
1106
- const behaviorClass = partBehaviorMap[clusterName];
1107
- if (behaviorClass) {
1108
- partCustomBehaviors.push(behaviorClass);
1109
- log.info(` Will use ${behaviorClass.name} for part ${part.id}`);
1110
- }
1111
- else {
1112
- log.warn(`No custom behavior class available for cluster '${clusterName}' on part ${part.id}`);
1113
- }
1114
- }
1115
- if (partCustomBehaviors.length > 0) {
1116
- // Matter.js device types support .with() to add behaviors
1117
- partDeviceType = partDeviceType.with(...partCustomBehaviors);
1118
- log.info(` Applied ${partCustomBehaviors.length} custom behavior(s) to part ${part.id}`);
1119
- }
1120
- }
1121
- // Add BridgedDeviceBasicInformationServer for bridged parts
1122
- if (!this.config.externalAccessory) {
1123
- // Matter.js device types support .with() to add behaviors
1124
- partDeviceType = partDeviceType.with(BridgedDeviceBasicInformationServer);
1125
- }
1126
- // Create endpoint options with cluster states
1127
- const partEndpointOptions = {
1128
- id: partEndpointId,
1129
- ...part.clusters,
1130
- };
1131
- // Add bridgedDeviceBasicInformation for the part
1132
- if (!this.config.externalAccessory) {
1133
- partEndpointOptions.bridgedDeviceBasicInformation = {
1134
- vendorName: accessory.manufacturer,
1135
- nodeLabel: part.displayName || `${accessory.displayName} - ${part.id}`,
1136
- productName: accessory.model,
1137
- productLabel: part.displayName || part.id,
1138
- serialNumber: `${accessory.serialNumber}-${part.id}`,
1139
- reachable: true,
1140
- };
1141
- }
1142
- // Create the part endpoint
1143
- const partEndpoint = new Endpoint(partDeviceType, partEndpointOptions);
1144
- // Add part endpoint to aggregator or serverNode
1145
- if (this.config.externalAccessory) {
1146
- await this.serverNode.add(partEndpoint);
1147
- }
1148
- else {
1149
- await this.aggregator.add(partEndpoint);
1150
- }
1151
- log.info(` Created part endpoint: ${part.displayName || part.id} (${partEndpointId})`);
1152
- // Set up handlers for this part
1153
- if (part.handlers) {
1154
- for (const [clusterName, handlers] of Object.entries(part.handlers)) {
1155
- for (const [commandName, handler] of Object.entries(handlers)) {
1156
- // Register handler with the part's endpoint ID
1157
- registerHandler(partEndpointId, clusterName, commandName, handler);
1158
- }
1159
- }
1160
- log.debug(` Registered ${Object.keys(part.handlers).length} handler(s) for part ${part.id}`);
1161
- }
1162
- // Store the internal part
1163
- internalParts.push({
1164
- ...part,
1165
- endpoint: partEndpoint,
1166
- });
1167
- }
1168
- }
1169
- // Store accessory with internal metadata and event emitter
1170
- // The event emitter allows plugins to listen for lifecycle events (ready, commissioned, decommissioned)
1171
- const internalAccessory = {
1172
- ...accessory,
1173
- _associatedPlugin: pluginIdentifier,
1174
- _associatedPlatform: platformName,
1175
- endpoint,
1176
- registered: true,
1177
- _parts: internalParts.length > 0 ? internalParts : undefined,
1178
- _eventEmitter: new EventEmitter(),
1179
- };
1180
- this.accessories.set(accessory.uuid, internalAccessory);
1181
- log.info(`Registered Matter accessory: ${accessory.displayName} (${accessory.uuid})`);
1182
- if (this.config.debugModeEnabled) {
1183
- log.debug(`Total registered accessories: ${this.accessories.size}/${this.MAX_DEVICES}`);
1184
- }
1185
- // Emit accessory-registered event
1186
- this.emit('accessory-registered', accessory);
1187
- // Notify controllers about the new device (parts list changed)
1188
- // This allows the Home app to discover new devices without re-pairing
1189
- await this.notifyPartsListChanged();
1190
- // Save to cache asynchronously (don't block registration)
1191
- if (this.accessoryCache) {
1192
- this.accessoryCache.save(this.accessories).catch((error) => {
1193
- log.warn('Failed to save accessory cache:', error);
1194
- });
1195
- }
1330
+ // Store the internal part
1331
+ internalParts.push({
1332
+ ...part,
1333
+ endpoint: partEndpoint,
1334
+ });
1196
1335
  }
1197
- catch (error) {
1198
- log.error(`Failed to register Matter accessory ${accessory.displayName}:`, error);
1199
- throw new MatterDeviceError(`Failed to register accessory: ${error}`);
1336
+ return internalParts;
1337
+ }
1338
+ /**
1339
+ * Finalize accessory registration (store, emit events, save cache)
1340
+ */
1341
+ async finalizeAccessoryRegistration(accessory, endpoint, internalParts) {
1342
+ // Store accessory with internal metadata and event emitter
1343
+ // The event emitter allows plugins to listen for lifecycle events (ready, commissioned, decommissioned)
1344
+ // Note: _associatedPlugin and _associatedPlatform are already set by MatterAPIImpl
1345
+ const internalAccessory = {
1346
+ ...accessory,
1347
+ endpoint,
1348
+ registered: true,
1349
+ _parts: internalParts.length > 0 ? internalParts : undefined,
1350
+ _eventEmitter: new EventEmitter(),
1351
+ };
1352
+ this.accessories.set(accessory.uuid, internalAccessory);
1353
+ log.info(`Registered Matter accessory: ${accessory.displayName} (${accessory.uuid})`);
1354
+ if (this.config.debugModeEnabled) {
1355
+ log.debug(`Total registered accessories: ${this.accessories.size}/${this.MAX_DEVICES}`);
1356
+ }
1357
+ // Emit accessory-registered event
1358
+ this.emit('accessory-registered', accessory);
1359
+ // Notify controllers about the new device (parts list changed)
1360
+ // This allows the Home app to discover new devices without re-pairing
1361
+ await this.notifyPartsListChanged();
1362
+ // Request debounced save to cache (reduces disk I/O during rapid registration)
1363
+ if (this.accessoryCache) {
1364
+ this.accessoryCache.requestSave(this.accessories);
1200
1365
  }
1201
1366
  }
1202
1367
  /**
@@ -1211,9 +1376,7 @@ export class MatterServer extends EventEmitter {
1211
1376
  if (this.accessoryCache && this.accessoryCache.getCached(uuid)) {
1212
1377
  log.debug(`Removing ${uuid} from cache`);
1213
1378
  this.accessoryCache.removeCached(uuid);
1214
- this.accessoryCache.save(this.accessories).catch((error) => {
1215
- log.warn('Failed to save accessory cache:', error);
1216
- });
1379
+ this.accessoryCache.requestSave(this.accessories);
1217
1380
  }
1218
1381
  return;
1219
1382
  }
@@ -1231,9 +1394,7 @@ export class MatterServer extends EventEmitter {
1231
1394
  // Update cache (remove the accessory)
1232
1395
  if (this.accessoryCache) {
1233
1396
  this.accessoryCache.removeCached(uuid);
1234
- this.accessoryCache.save(this.accessories).catch((error) => {
1235
- log.warn('Failed to save accessory cache:', error);
1236
- });
1397
+ this.accessoryCache.requestSave(this.accessories);
1237
1398
  }
1238
1399
  }
1239
1400
  catch (error) {
@@ -1281,10 +1442,10 @@ export class MatterServer extends EventEmitter {
1281
1442
  displayName = accessory.displayName;
1282
1443
  }
1283
1444
  // Defer the update to avoid "read-only transaction" errors when called from handlers
1284
- // Matter.js uses transactions, and we need to wait until the transaction fully completes
1285
- // Use setTimeout with a delay to ensure we're completely outside the transaction
1445
+ // Matter.js uses transactions, and we need to escape the current call stack
1446
+ // setImmediate ensures we're in a new event loop tick without arbitrary delays
1286
1447
  return new Promise((resolve, reject) => {
1287
- setTimeout(async () => {
1448
+ setImmediate(async () => {
1288
1449
  try {
1289
1450
  // Construct the update object
1290
1451
  const updateObject = { [cluster]: attributes };
@@ -1313,7 +1474,7 @@ export class MatterServer extends EventEmitter {
1313
1474
  log.error(`Failed to update state for accessory ${uuid}${partInfo}:`, error);
1314
1475
  reject(new MatterDeviceError(`Failed to update accessory state: ${error}`));
1315
1476
  }
1316
- }, 50); // 50ms delay to ensure we're completely outside the transaction
1477
+ });
1317
1478
  });
1318
1479
  }
1319
1480
  /**
@@ -1452,7 +1613,6 @@ export class MatterServer extends EventEmitter {
1452
1613
  this.isRunning = false;
1453
1614
  // Stop monitoring
1454
1615
  this.stopFabricMonitoring();
1455
- networkMonitor.stopMonitoring();
1456
1616
  try {
1457
1617
  // Save accessory cache before shutting down (BEFORE clearing accessories!)
1458
1618
  if (this.accessoryCache && this.accessories.size > 0) {
@@ -1508,6 +1668,23 @@ export class MatterServer extends EventEmitter {
1508
1668
  }
1509
1669
  /**
1510
1670
  * Get fabric information for commissioned controllers
1671
+ *
1672
+ * Returns information about each paired controller (fabric) including:
1673
+ * - fabricIndex: Unique identifier for the fabric
1674
+ * - fabricId: 64-bit fabric identifier
1675
+ * - nodeId: Node identifier within the fabric
1676
+ * - rootVendorId: Vendor ID of the root node
1677
+ * - label: Optional human-readable label
1678
+ *
1679
+ * @returns Array of fabric information objects, or empty array if no fabrics are commissioned
1680
+ * @example
1681
+ * ```typescript
1682
+ * const fabrics = matterServer.getFabricInfo()
1683
+ * console.log(`Commissioned to ${fabrics.length} controller(s)`)
1684
+ * fabrics.forEach(fabric => {
1685
+ * console.log(` Fabric ${fabric.fabricIndex}: ${fabric.label || 'Unnamed'}`)
1686
+ * })
1687
+ * ```
1511
1688
  */
1512
1689
  getFabricInfo() {
1513
1690
  try {
@@ -1641,8 +1818,11 @@ export class MatterServer extends EventEmitter {
1641
1818
  // The fabric monitoring will detect this change and emit the appropriate events
1642
1819
  }
1643
1820
  catch (error) {
1821
+ const errorMessage = error instanceof Error ? error.message : String(error);
1644
1822
  log.error(`Failed to remove fabric ${fabricIndex}:`, error);
1645
- throw new MatterDeviceError(`Failed to remove fabric: ${error.message}`, error);
1823
+ throw new MatterDeviceError(`Failed to remove fabric: ${errorMessage}`, {
1824
+ originalError: error instanceof Error ? error : undefined,
1825
+ });
1646
1826
  }
1647
1827
  }
1648
1828
  /**
@@ -1685,8 +1865,9 @@ export class MatterServer extends EventEmitter {
1685
1865
  }
1686
1866
  catch (error) {
1687
1867
  // Non-fatal error - log but don't throw
1688
- log.warn(`Failed to notify controllers of parts list change: ${error.message}`);
1868
+ const errorMessage = error instanceof Error ? error.message : String(error);
1869
+ log.warn(`Failed to notify controllers of parts list change: ${errorMessage}`);
1689
1870
  }
1690
1871
  }
1691
1872
  }
1692
- //# sourceMappingURL=matterServer.js.map
1873
+ //# sourceMappingURL=server.js.map