@matter/protocol 0.16.0-alpha.0-20251213-e83db3732 → 0.16.0-alpha.0-20251217-038f88085

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 (309) hide show
  1. package/LICENSE +1 -1
  2. package/dist/cjs/action/client/ClientInteraction.d.ts +12 -5
  3. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
  4. package/dist/cjs/action/client/ClientInteraction.js +39 -15
  5. package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
  6. package/dist/cjs/action/client/ClientRead.d.ts +10 -0
  7. package/dist/cjs/action/client/ClientRead.d.ts.map +1 -0
  8. package/dist/cjs/action/client/ClientRead.js +22 -0
  9. package/dist/cjs/action/client/ClientRead.js.map +6 -0
  10. package/dist/cjs/action/client/ClientRequest.d.ts +20 -0
  11. package/dist/cjs/action/client/ClientRequest.d.ts.map +1 -0
  12. package/dist/cjs/action/client/ClientRequest.js +22 -0
  13. package/dist/cjs/action/client/ClientRequest.js.map +6 -0
  14. package/dist/cjs/action/client/ClientWrite.d.ts +10 -0
  15. package/dist/cjs/action/client/ClientWrite.d.ts.map +1 -0
  16. package/dist/cjs/action/client/ClientWrite.js +22 -0
  17. package/dist/cjs/action/client/ClientWrite.js.map +6 -0
  18. package/dist/cjs/action/client/QueuedClientInteraction.d.ts +49 -0
  19. package/dist/cjs/action/client/QueuedClientInteraction.d.ts.map +1 -0
  20. package/dist/cjs/action/client/QueuedClientInteraction.js +160 -0
  21. package/dist/cjs/action/client/QueuedClientInteraction.js.map +6 -0
  22. package/dist/cjs/action/client/index.d.ts +4 -0
  23. package/dist/cjs/action/client/index.d.ts.map +1 -1
  24. package/dist/cjs/action/client/index.js +4 -0
  25. package/dist/cjs/action/client/index.js.map +1 -1
  26. package/dist/cjs/action/client/subscription/ClientSubscribe.d.ts +2 -1
  27. package/dist/cjs/action/client/subscription/ClientSubscribe.d.ts.map +1 -1
  28. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
  29. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js +14 -3
  30. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
  31. package/dist/cjs/action/client/subscription/SustainedSubscription.d.ts +1 -1
  32. package/dist/cjs/action/client/subscription/SustainedSubscription.d.ts.map +1 -1
  33. package/dist/cjs/action/client/subscription/SustainedSubscription.js +1 -4
  34. package/dist/cjs/action/client/subscription/SustainedSubscription.js.map +1 -1
  35. package/dist/cjs/action/request/Invoke.d.ts +7 -1
  36. package/dist/cjs/action/request/Invoke.d.ts.map +1 -1
  37. package/dist/cjs/action/request/Invoke.js +0 -3
  38. package/dist/cjs/action/request/Invoke.js.map +1 -1
  39. package/dist/cjs/action/request/Read.d.ts.map +1 -1
  40. package/dist/cjs/action/request/Read.js +3 -2
  41. package/dist/cjs/action/request/Read.js.map +1 -1
  42. package/dist/cjs/action/request/Specifier.d.ts +1 -1
  43. package/dist/cjs/action/request/Specifier.d.ts.map +1 -1
  44. package/dist/cjs/action/request/Specifier.js +3 -0
  45. package/dist/cjs/action/request/Specifier.js.map +1 -1
  46. package/dist/cjs/action/request/Write.d.ts +1 -0
  47. package/dist/cjs/action/request/Write.d.ts.map +1 -1
  48. package/dist/cjs/action/request/Write.js +10 -2
  49. package/dist/cjs/action/request/Write.js.map +1 -1
  50. package/dist/cjs/action/response/ReadResult.d.ts +1 -1
  51. package/dist/cjs/action/response/ReadResult.d.ts.map +1 -1
  52. package/dist/cjs/cluster/client/ClusterClientTypes.d.ts +37 -8
  53. package/dist/cjs/cluster/client/ClusterClientTypes.d.ts.map +1 -1
  54. package/dist/cjs/cluster/client/index.d.ts +0 -3
  55. package/dist/cjs/cluster/client/index.d.ts.map +1 -1
  56. package/dist/cjs/cluster/client/index.js +0 -3
  57. package/dist/cjs/cluster/client/index.js.map +1 -1
  58. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  59. package/dist/cjs/interaction/InteractionMessenger.js +3 -2
  60. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  61. package/dist/cjs/interaction/SubscriptionClient.d.ts.map +1 -1
  62. package/dist/cjs/interaction/SubscriptionClient.js +2 -1
  63. package/dist/cjs/interaction/SubscriptionClient.js.map +1 -1
  64. package/dist/cjs/interaction/index.d.ts +1 -1
  65. package/dist/cjs/interaction/index.d.ts.map +1 -1
  66. package/dist/cjs/interaction/index.js +1 -1
  67. package/dist/cjs/interaction/index.js.map +1 -1
  68. package/dist/cjs/peer/CommissioningError.d.ts +13 -0
  69. package/dist/cjs/peer/CommissioningError.d.ts.map +1 -0
  70. package/dist/cjs/peer/CommissioningError.js +32 -0
  71. package/dist/cjs/peer/CommissioningError.js.map +6 -0
  72. package/dist/cjs/peer/ControllerCommissioner.d.ts +2 -3
  73. package/dist/cjs/peer/ControllerCommissioner.d.ts.map +1 -1
  74. package/dist/cjs/peer/ControllerCommissioner.js +20 -13
  75. package/dist/cjs/peer/ControllerCommissioner.js.map +2 -2
  76. package/dist/cjs/peer/ControllerCommissioningFlow.d.ts +7 -16
  77. package/dist/cjs/peer/ControllerCommissioningFlow.d.ts.map +1 -1
  78. package/dist/cjs/peer/ControllerCommissioningFlow.js +395 -178
  79. package/dist/cjs/peer/ControllerCommissioningFlow.js.map +1 -1
  80. package/dist/cjs/peer/ControllerDiscovery.d.ts +4 -0
  81. package/dist/cjs/peer/ControllerDiscovery.d.ts.map +1 -1
  82. package/dist/cjs/peer/ControllerDiscovery.js +6 -3
  83. package/dist/cjs/peer/ControllerDiscovery.js.map +1 -1
  84. package/dist/cjs/peer/InteractionQueue.d.ts +2 -2
  85. package/dist/cjs/peer/InteractionQueue.d.ts.map +1 -1
  86. package/dist/cjs/peer/InteractionQueue.js +1 -1
  87. package/dist/cjs/peer/InteractionQueue.js.map +1 -1
  88. package/dist/cjs/peer/PeerAddressStore.d.ts +0 -9
  89. package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
  90. package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
  91. package/dist/cjs/peer/PeerSet.d.ts +0 -2
  92. package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
  93. package/dist/cjs/peer/PeerSet.js +32 -18
  94. package/dist/cjs/peer/PeerSet.js.map +1 -1
  95. package/dist/cjs/peer/PhysicalDeviceProperties.js +1 -1
  96. package/dist/cjs/peer/PhysicalDeviceProperties.js.map +1 -1
  97. package/dist/cjs/peer/index.d.ts +1 -0
  98. package/dist/cjs/peer/index.d.ts.map +1 -1
  99. package/dist/cjs/peer/index.js +1 -0
  100. package/dist/cjs/peer/index.js.map +1 -1
  101. package/dist/cjs/protocol/DeviceCommissioner.d.ts.map +1 -1
  102. package/dist/cjs/protocol/DeviceCommissioner.js.map +1 -1
  103. package/dist/cjs/protocol/ExchangeManager.js +2 -2
  104. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  105. package/dist/cjs/protocol/ExchangeProvider.d.ts +13 -4
  106. package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
  107. package/dist/cjs/protocol/ExchangeProvider.js +5 -3
  108. package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
  109. package/dist/cjs/session/NodeSession.d.ts +5 -2
  110. package/dist/cjs/session/NodeSession.d.ts.map +1 -1
  111. package/dist/cjs/session/NodeSession.js +5 -4
  112. package/dist/cjs/session/NodeSession.js.map +1 -1
  113. package/dist/cjs/session/Session.d.ts +5 -3
  114. package/dist/cjs/session/Session.d.ts.map +1 -1
  115. package/dist/cjs/session/Session.js +8 -4
  116. package/dist/cjs/session/Session.js.map +1 -1
  117. package/dist/cjs/session/SessionManager.d.ts +8 -0
  118. package/dist/cjs/session/SessionManager.d.ts.map +1 -1
  119. package/dist/cjs/session/SessionManager.js +16 -2
  120. package/dist/cjs/session/SessionManager.js.map +1 -1
  121. package/dist/esm/action/client/ClientInteraction.d.ts +12 -5
  122. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
  123. package/dist/esm/action/client/ClientInteraction.js +42 -16
  124. package/dist/esm/action/client/ClientInteraction.js.map +1 -1
  125. package/dist/esm/action/client/ClientRead.d.ts +10 -0
  126. package/dist/esm/action/client/ClientRead.d.ts.map +1 -0
  127. package/dist/esm/action/client/ClientRead.js +6 -0
  128. package/dist/esm/action/client/ClientRead.js.map +6 -0
  129. package/dist/esm/action/client/ClientRequest.d.ts +20 -0
  130. package/dist/esm/action/client/ClientRequest.d.ts.map +1 -0
  131. package/dist/esm/action/client/ClientRequest.js +6 -0
  132. package/dist/esm/action/client/ClientRequest.js.map +6 -0
  133. package/dist/esm/action/client/ClientWrite.d.ts +10 -0
  134. package/dist/esm/action/client/ClientWrite.d.ts.map +1 -0
  135. package/dist/esm/action/client/ClientWrite.js +6 -0
  136. package/dist/esm/action/client/ClientWrite.js.map +6 -0
  137. package/dist/esm/action/client/QueuedClientInteraction.d.ts +49 -0
  138. package/dist/esm/action/client/QueuedClientInteraction.d.ts.map +1 -0
  139. package/dist/esm/action/client/QueuedClientInteraction.js +140 -0
  140. package/dist/esm/action/client/QueuedClientInteraction.js.map +6 -0
  141. package/dist/esm/action/client/index.d.ts +4 -0
  142. package/dist/esm/action/client/index.d.ts.map +1 -1
  143. package/dist/esm/action/client/index.js +4 -0
  144. package/dist/esm/action/client/index.js.map +1 -1
  145. package/dist/esm/action/client/subscription/ClientSubscribe.d.ts +2 -1
  146. package/dist/esm/action/client/subscription/ClientSubscribe.d.ts.map +1 -1
  147. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
  148. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js +14 -3
  149. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
  150. package/dist/esm/action/client/subscription/SustainedSubscription.d.ts +1 -1
  151. package/dist/esm/action/client/subscription/SustainedSubscription.d.ts.map +1 -1
  152. package/dist/esm/action/client/subscription/SustainedSubscription.js +1 -4
  153. package/dist/esm/action/client/subscription/SustainedSubscription.js.map +1 -1
  154. package/dist/esm/action/request/Invoke.d.ts +7 -1
  155. package/dist/esm/action/request/Invoke.d.ts.map +1 -1
  156. package/dist/esm/action/request/Invoke.js +0 -3
  157. package/dist/esm/action/request/Invoke.js.map +1 -1
  158. package/dist/esm/action/request/Read.d.ts.map +1 -1
  159. package/dist/esm/action/request/Read.js +3 -2
  160. package/dist/esm/action/request/Read.js.map +1 -1
  161. package/dist/esm/action/request/Specifier.d.ts +1 -1
  162. package/dist/esm/action/request/Specifier.d.ts.map +1 -1
  163. package/dist/esm/action/request/Specifier.js +3 -0
  164. package/dist/esm/action/request/Specifier.js.map +1 -1
  165. package/dist/esm/action/request/Write.d.ts +1 -0
  166. package/dist/esm/action/request/Write.d.ts.map +1 -1
  167. package/dist/esm/action/request/Write.js +10 -2
  168. package/dist/esm/action/request/Write.js.map +1 -1
  169. package/dist/esm/action/response/ReadResult.d.ts +1 -1
  170. package/dist/esm/action/response/ReadResult.d.ts.map +1 -1
  171. package/dist/esm/cluster/client/ClusterClientTypes.d.ts +37 -8
  172. package/dist/esm/cluster/client/ClusterClientTypes.d.ts.map +1 -1
  173. package/dist/esm/cluster/client/index.d.ts +0 -3
  174. package/dist/esm/cluster/client/index.d.ts.map +1 -1
  175. package/dist/esm/cluster/client/index.js +0 -3
  176. package/dist/esm/cluster/client/index.js.map +1 -1
  177. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  178. package/dist/esm/interaction/InteractionMessenger.js +4 -3
  179. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  180. package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -1
  181. package/dist/esm/interaction/SubscriptionClient.js +2 -1
  182. package/dist/esm/interaction/SubscriptionClient.js.map +1 -1
  183. package/dist/esm/interaction/index.d.ts +1 -1
  184. package/dist/esm/interaction/index.d.ts.map +1 -1
  185. package/dist/esm/interaction/index.js +1 -1
  186. package/dist/esm/peer/CommissioningError.d.ts +13 -0
  187. package/dist/esm/peer/CommissioningError.d.ts.map +1 -0
  188. package/dist/esm/peer/CommissioningError.js +12 -0
  189. package/dist/esm/peer/CommissioningError.js.map +6 -0
  190. package/dist/esm/peer/ControllerCommissioner.d.ts +2 -3
  191. package/dist/esm/peer/ControllerCommissioner.d.ts.map +1 -1
  192. package/dist/esm/peer/ControllerCommissioner.js +19 -13
  193. package/dist/esm/peer/ControllerCommissioner.js.map +2 -2
  194. package/dist/esm/peer/ControllerCommissioningFlow.d.ts +7 -16
  195. package/dist/esm/peer/ControllerCommissioningFlow.d.ts.map +1 -1
  196. package/dist/esm/peer/ControllerCommissioningFlow.js +380 -162
  197. package/dist/esm/peer/ControllerCommissioningFlow.js.map +1 -1
  198. package/dist/esm/peer/ControllerDiscovery.d.ts +4 -0
  199. package/dist/esm/peer/ControllerDiscovery.d.ts.map +1 -1
  200. package/dist/esm/peer/ControllerDiscovery.js +4 -1
  201. package/dist/esm/peer/ControllerDiscovery.js.map +1 -1
  202. package/dist/esm/peer/InteractionQueue.d.ts +2 -2
  203. package/dist/esm/peer/InteractionQueue.d.ts.map +1 -1
  204. package/dist/esm/peer/InteractionQueue.js +2 -2
  205. package/dist/esm/peer/InteractionQueue.js.map +1 -1
  206. package/dist/esm/peer/PeerAddressStore.d.ts +0 -9
  207. package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
  208. package/dist/esm/peer/PeerAddressStore.js.map +1 -1
  209. package/dist/esm/peer/PeerSet.d.ts +0 -2
  210. package/dist/esm/peer/PeerSet.d.ts.map +1 -1
  211. package/dist/esm/peer/PeerSet.js +32 -18
  212. package/dist/esm/peer/PeerSet.js.map +1 -1
  213. package/dist/esm/peer/PhysicalDeviceProperties.js +1 -1
  214. package/dist/esm/peer/PhysicalDeviceProperties.js.map +1 -1
  215. package/dist/esm/peer/index.d.ts +1 -0
  216. package/dist/esm/peer/index.d.ts.map +1 -1
  217. package/dist/esm/peer/index.js +1 -0
  218. package/dist/esm/peer/index.js.map +1 -1
  219. package/dist/esm/protocol/DeviceCommissioner.d.ts.map +1 -1
  220. package/dist/esm/protocol/DeviceCommissioner.js.map +1 -1
  221. package/dist/esm/protocol/ExchangeManager.js +2 -2
  222. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  223. package/dist/esm/protocol/ExchangeProvider.d.ts +13 -4
  224. package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
  225. package/dist/esm/protocol/ExchangeProvider.js +5 -3
  226. package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
  227. package/dist/esm/session/NodeSession.d.ts +5 -2
  228. package/dist/esm/session/NodeSession.d.ts.map +1 -1
  229. package/dist/esm/session/NodeSession.js +5 -4
  230. package/dist/esm/session/NodeSession.js.map +1 -1
  231. package/dist/esm/session/Session.d.ts +5 -3
  232. package/dist/esm/session/Session.d.ts.map +1 -1
  233. package/dist/esm/session/Session.js +8 -4
  234. package/dist/esm/session/Session.js.map +1 -1
  235. package/dist/esm/session/SessionManager.d.ts +8 -0
  236. package/dist/esm/session/SessionManager.d.ts.map +1 -1
  237. package/dist/esm/session/SessionManager.js +17 -2
  238. package/dist/esm/session/SessionManager.js.map +1 -1
  239. package/package.json +6 -6
  240. package/src/action/client/ClientInteraction.ts +58 -19
  241. package/src/action/client/ClientRead.ts +10 -0
  242. package/src/action/client/ClientRequest.ts +20 -0
  243. package/src/action/client/ClientWrite.ts +10 -0
  244. package/src/action/client/QueuedClientInteraction.ts +91 -0
  245. package/src/action/client/index.ts +4 -0
  246. package/src/action/client/subscription/ClientSubscribe.ts +2 -1
  247. package/src/action/client/subscription/ClientSubscriptionHandler.ts +14 -3
  248. package/src/action/client/subscription/SustainedSubscription.ts +6 -9
  249. package/src/action/request/Invoke.ts +11 -4
  250. package/src/action/request/Read.ts +3 -2
  251. package/src/action/request/Specifier.ts +4 -1
  252. package/src/action/request/Write.ts +11 -2
  253. package/src/action/response/ReadResult.ts +1 -1
  254. package/src/cluster/client/ClusterClientTypes.ts +47 -7
  255. package/src/cluster/client/index.ts +0 -3
  256. package/src/interaction/InteractionMessenger.ts +5 -4
  257. package/src/interaction/SubscriptionClient.ts +2 -1
  258. package/src/interaction/index.ts +1 -1
  259. package/src/peer/CommissioningError.ts +13 -0
  260. package/src/peer/ControllerCommissioner.ts +21 -13
  261. package/src/peer/ControllerCommissioningFlow.ts +418 -186
  262. package/src/peer/ControllerDiscovery.ts +4 -1
  263. package/src/peer/InteractionQueue.ts +2 -2
  264. package/src/peer/PeerAddressStore.ts +0 -9
  265. package/src/peer/PeerSet.ts +56 -23
  266. package/src/peer/PhysicalDeviceProperties.ts +1 -1
  267. package/src/peer/index.ts +1 -0
  268. package/src/protocol/DeviceCommissioner.ts +0 -1
  269. package/src/protocol/ExchangeManager.ts +2 -2
  270. package/src/protocol/ExchangeProvider.ts +9 -7
  271. package/src/session/NodeSession.ts +5 -4
  272. package/src/session/Session.ts +8 -4
  273. package/src/session/SessionManager.ts +19 -2
  274. package/dist/cjs/cluster/client/AttributeClient.d.ts +0 -75
  275. package/dist/cjs/cluster/client/AttributeClient.d.ts.map +0 -1
  276. package/dist/cjs/cluster/client/AttributeClient.js +0 -209
  277. package/dist/cjs/cluster/client/AttributeClient.js.map +0 -6
  278. package/dist/cjs/cluster/client/ClusterClient.d.ts +0 -11
  279. package/dist/cjs/cluster/client/ClusterClient.d.ts.map +0 -1
  280. package/dist/cjs/cluster/client/ClusterClient.js +0 -335
  281. package/dist/cjs/cluster/client/ClusterClient.js.map +0 -6
  282. package/dist/cjs/cluster/client/EventClient.d.ts +0 -33
  283. package/dist/cjs/cluster/client/EventClient.d.ts.map +0 -1
  284. package/dist/cjs/cluster/client/EventClient.js +0 -89
  285. package/dist/cjs/cluster/client/EventClient.js.map +0 -6
  286. package/dist/cjs/interaction/InteractionClient.d.ts +0 -375
  287. package/dist/cjs/interaction/InteractionClient.d.ts.map +0 -1
  288. package/dist/cjs/interaction/InteractionClient.js +0 -1046
  289. package/dist/cjs/interaction/InteractionClient.js.map +0 -6
  290. package/dist/esm/cluster/client/AttributeClient.d.ts +0 -75
  291. package/dist/esm/cluster/client/AttributeClient.d.ts.map +0 -1
  292. package/dist/esm/cluster/client/AttributeClient.js +0 -189
  293. package/dist/esm/cluster/client/AttributeClient.js.map +0 -6
  294. package/dist/esm/cluster/client/ClusterClient.d.ts +0 -11
  295. package/dist/esm/cluster/client/ClusterClient.d.ts.map +0 -1
  296. package/dist/esm/cluster/client/ClusterClient.js +0 -320
  297. package/dist/esm/cluster/client/ClusterClient.js.map +0 -6
  298. package/dist/esm/cluster/client/EventClient.d.ts +0 -33
  299. package/dist/esm/cluster/client/EventClient.d.ts.map +0 -1
  300. package/dist/esm/cluster/client/EventClient.js +0 -69
  301. package/dist/esm/cluster/client/EventClient.js.map +0 -6
  302. package/dist/esm/interaction/InteractionClient.d.ts +0 -375
  303. package/dist/esm/interaction/InteractionClient.d.ts.map +0 -1
  304. package/dist/esm/interaction/InteractionClient.js +0 -1047
  305. package/dist/esm/interaction/InteractionClient.js.map +0 -6
  306. package/src/cluster/client/AttributeClient.ts +0 -230
  307. package/src/cluster/client/ClusterClient.ts +0 -433
  308. package/src/cluster/client/EventClient.ts +0 -99
  309. package/src/interaction/InteractionClient.ts +0 -1614
@@ -1,1614 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2022-2025 Matter.js Authors
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- import { ReadScope } from "#action/client/ReadScope.js";
8
- import { AccessControl } from "#clusters/access-control";
9
- import { Mark } from "#common/Mark.js";
10
- import {
11
- Diagnostic,
12
- Duration,
13
- Environment,
14
- Environmental,
15
- ImplementationError,
16
- Logger,
17
- MatterFlowError,
18
- MaybePromise,
19
- PromiseQueue,
20
- Seconds,
21
- ServerAddressUdp,
22
- Timer,
23
- UnexpectedDataError,
24
- isDeepEqual,
25
- serialize,
26
- } from "#general";
27
- import { Specification } from "#model";
28
- import { PeerAddress, PeerAddressMap } from "#peer/PeerAddress.js";
29
- import { PeerDataStore } from "#peer/PeerAddressStore.js";
30
- import { PeerConnectionOptions, PeerSet } from "#peer/PeerSet.js";
31
- import { SecureSession } from "#session/SecureSession.js";
32
- import {
33
- ArraySchema,
34
- Attribute,
35
- AttributeId,
36
- AttributeJsType,
37
- ClusterId,
38
- Command,
39
- EndpointNumber,
40
- Event,
41
- EventId,
42
- EventNumber,
43
- FabricIndex,
44
- NodeId,
45
- ObjectSchema,
46
- RequestType,
47
- ResponseType,
48
- StatusCode,
49
- StatusResponseError,
50
- SubscribeRequest,
51
- TlvEventFilter,
52
- TlvInvokeResponse,
53
- TlvNoResponse,
54
- TlvSubscribeResponse,
55
- TlvWriteResponse,
56
- TypeFromSchema,
57
- resolveAttributeName,
58
- resolveCommandName,
59
- resolveEventName,
60
- } from "#types";
61
- import { ExchangeProvider, ReconnectableExchangeProvider } from "../protocol/ExchangeProvider.js";
62
- import { DecodedAttributeReportStatus, DecodedAttributeReportValue } from "./AttributeDataDecoder.js";
63
- import { DecodedDataReport } from "./DecodedDataReport.js";
64
- import { DecodedEventData, DecodedEventReportStatus, DecodedEventReportValue } from "./EventDataDecoder.js";
65
- import { InteractionClientMessenger, ReadRequest } from "./InteractionMessenger.js";
66
- import { Subscription } from "./Subscription.js";
67
- import { RegisteredSubscription, SubscriptionClient } from "./SubscriptionClient.js";
68
-
69
- const logger = Logger.get("InteractionClient");
70
-
71
- const REQUEST_ALL = [{}];
72
- const DEFAULT_TIMED_REQUEST_TIMEOUT = Seconds(10);
73
- const DEFAULT_MINIMUM_RESPONSE_TIMEOUT_WITH_FAILSAFE = Seconds(30);
74
-
75
- const AclClusterId = AccessControl.Complete.id;
76
- const AclAttributeId = AccessControl.Complete.attributes.acl.id;
77
- const AclExtensionAttributeId = AccessControl.Complete.attributes.extension.id;
78
-
79
- function isAclOrExtensionPath(path: { clusterId: ClusterId; attributeId: AttributeId }) {
80
- const { clusterId, attributeId } = path;
81
- return clusterId === AclClusterId && (attributeId === AclAttributeId || attributeId === AclExtensionAttributeId);
82
- }
83
-
84
- export interface AttributeStatus {
85
- path: {
86
- nodeId?: NodeId;
87
- endpointId?: EndpointNumber;
88
- clusterId?: ClusterId;
89
- attributeId?: AttributeId;
90
- };
91
- status: StatusCode;
92
- }
93
-
94
- export class InteractionClientProvider {
95
- readonly #peers: PeerSet;
96
- readonly #clients = new PeerAddressMap<InteractionClient>();
97
- readonly #subscriptionClient = new SubscriptionClient();
98
-
99
- constructor(peers: PeerSet) {
100
- this.#peers = peers;
101
- this.#peers.deleted.on(peer => this.#onPeerLoss(peer.address));
102
- this.#peers.disconnected.on(peer => this.#onPeerLoss(peer.address));
103
- }
104
-
105
- static [Environmental.create](env: Environment) {
106
- const instance = new InteractionClientProvider(env.get(PeerSet));
107
- env.set(InteractionClientProvider, instance);
108
- return instance;
109
- }
110
-
111
- get peers() {
112
- return this.#peers;
113
- }
114
-
115
- get subscriptionClient() {
116
- return this.#subscriptionClient;
117
- }
118
-
119
- async connect(
120
- address: PeerAddress,
121
- options: PeerConnectionOptions & {
122
- allowUnknownPeer?: boolean;
123
- operationalAddress?: ServerAddressUdp;
124
- },
125
- ): Promise<InteractionClient> {
126
- await this.#peers.connect(address, options);
127
-
128
- return this.getInteractionClient(address, options);
129
- }
130
-
131
- async interactionClientFor(session: SecureSession): Promise<InteractionClient> {
132
- const exchangeProvider = await this.#peers.exchangeProviderFor(session);
133
-
134
- return new InteractionClient(
135
- exchangeProvider,
136
- this.#subscriptionClient,
137
- undefined,
138
- this.#peers.interactionQueue,
139
- );
140
- }
141
-
142
- async getInteractionClient(address: PeerAddress, options: PeerConnectionOptions = {}) {
143
- let client = this.#clients.get(address);
144
- if (client !== undefined) {
145
- return client;
146
- }
147
-
148
- const isGroupAddress = PeerAddress.isGroup(address);
149
- const nodeStore = isGroupAddress ? undefined : this.#peers.get(address)?.descriptor.dataStore;
150
- await nodeStore?.construction; // Lazy initialize the data if not already done
151
-
152
- const exchangeProvider = await this.#peers.exchangeProviderFor(address, options);
153
-
154
- client = new InteractionClient(
155
- exchangeProvider,
156
- this.#subscriptionClient,
157
- address,
158
- this.#peers.interactionQueue,
159
- nodeStore,
160
- );
161
- this.#clients.set(address, client);
162
-
163
- return client;
164
- }
165
-
166
- #onPeerLoss(address: PeerAddress) {
167
- const client = this.#clients.get(address);
168
- if (client !== undefined) {
169
- client.close();
170
- this.#clients.delete(address);
171
- }
172
- }
173
- }
174
-
175
- export class InteractionClient {
176
- readonly #exchangeProvider: ExchangeProvider;
177
- readonly #nodeStore?: PeerDataStore;
178
- readonly #ownSubscriptionIds = new Set<number>();
179
- readonly #queue?: PromiseQueue;
180
- readonly #address?: PeerAddress;
181
- readonly isGroupAddress: boolean;
182
-
183
- // TODO - SubscriptionClient is used by CommissioningController but not ClientNode. However InteractionClient *is*
184
- // used by ClientNode to perform commissioning, during which time SubscriptionClient is unnecessary. So this should
185
- // be set after commissioning
186
- //
187
- // If we remove CommissioningController then this entire class goes away; if we first move commissioning to
188
- // ClientInteraction then this should become required
189
- readonly #subscriptionClient?: SubscriptionClient;
190
-
191
- constructor(
192
- exchangeProvider: ExchangeProvider,
193
- subscriptionClient?: SubscriptionClient,
194
- address?: PeerAddress,
195
- queue?: PromiseQueue,
196
- nodeStore?: PeerDataStore,
197
- ) {
198
- this.#exchangeProvider = exchangeProvider;
199
- this.#nodeStore = nodeStore;
200
- this.#subscriptionClient = subscriptionClient;
201
- this.#queue = queue;
202
- this.#address = address;
203
- this.isGroupAddress = address !== undefined ? PeerAddress.isGroup(address) : false;
204
- }
205
-
206
- get address() {
207
- if (this.#address === undefined) {
208
- throw new ImplementationError("This InteractionClient is not bound to a specific peer.");
209
- }
210
- return this.#address;
211
- }
212
-
213
- get isReconnectable() {
214
- return this.#exchangeProvider instanceof ReconnectableExchangeProvider;
215
- }
216
-
217
- get channelUpdated() {
218
- if (this.#exchangeProvider instanceof ReconnectableExchangeProvider) {
219
- return this.#exchangeProvider.channelUpdated;
220
- }
221
- throw new ImplementationError("ExchangeProvider does not support channelUpdated");
222
- }
223
-
224
- /** Calculates the current maximum response time for a message use in additional logic like timers. */
225
- maximumPeerResponseTime(expectedProcessingTime?: Duration) {
226
- return this.#exchangeProvider.maximumPeerResponseTime(expectedProcessingTime);
227
- }
228
-
229
- removeSubscription(subscriptionId: number) {
230
- this.#ownSubscriptionIds.delete(subscriptionId);
231
- this.#subscriptionClient?.delete(subscriptionId);
232
- }
233
-
234
- async getAllAttributes(
235
- options: {
236
- dataVersionFilters?: { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[];
237
- enrichCachedAttributeData?: boolean;
238
- isFabricFiltered?: boolean;
239
- executeQueued?: boolean;
240
- attributeChangeListener?: (
241
- data: DecodedAttributeReportValue<any>,
242
- valueChanged?: boolean,
243
- oldValue?: any,
244
- ) => void;
245
- } = {},
246
- ): Promise<DecodedAttributeReportValue<any>[]> {
247
- return (
248
- await this.getMultipleAttributesAndEvents({
249
- attributes: REQUEST_ALL,
250
- ...options,
251
- })
252
- ).attributeReports;
253
- }
254
-
255
- async getAllEvents(
256
- options: {
257
- eventFilters?: TypeFromSchema<typeof TlvEventFilter>[];
258
- isFabricFiltered?: boolean;
259
- executeQueued?: boolean;
260
- } = {},
261
- ): Promise<DecodedEventReportValue<any>[]> {
262
- return (
263
- await this.getMultipleAttributesAndEvents({
264
- events: REQUEST_ALL,
265
- ...options,
266
- })
267
- ).eventReports;
268
- }
269
-
270
- async getAllAttributesAndEvents(
271
- options: {
272
- dataVersionFilters?: {
273
- endpointId: EndpointNumber;
274
- clusterId: ClusterId;
275
- dataVersion: number;
276
- }[];
277
- enrichCachedAttributeData?: boolean;
278
- eventFilters?: TypeFromSchema<typeof TlvEventFilter>[];
279
- isFabricFiltered?: boolean;
280
- executeQueued?: boolean;
281
- attributeChangeListener?: (
282
- data: DecodedAttributeReportValue<any>,
283
- valueChanged?: boolean,
284
- oldValue?: any,
285
- ) => void;
286
- } = {},
287
- ): Promise<{
288
- attributeReports: DecodedAttributeReportValue<any>[];
289
- eventReports: DecodedEventReportValue<any>[];
290
- }> {
291
- return this.getMultipleAttributesAndEvents({
292
- attributes: REQUEST_ALL,
293
- events: REQUEST_ALL,
294
- ...options,
295
- });
296
- }
297
-
298
- async getMultipleAttributes(
299
- options: {
300
- attributes?: { endpointId?: EndpointNumber; clusterId?: ClusterId; attributeId?: AttributeId }[];
301
- dataVersionFilters?: { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[];
302
- enrichCachedAttributeData?: boolean;
303
- isFabricFiltered?: boolean;
304
- executeQueued?: boolean;
305
- attributeChangeListener?: (
306
- data: DecodedAttributeReportValue<any>,
307
- valueChanged?: boolean,
308
- oldValue?: any,
309
- ) => void;
310
- } = {},
311
- ): Promise<DecodedAttributeReportValue<any>[]> {
312
- return (await this.getMultipleAttributesAndEvents(options)).attributeReports;
313
- }
314
-
315
- async getMultipleAttributesAndStatus(
316
- options: {
317
- attributes?: { endpointId?: EndpointNumber; clusterId?: ClusterId; attributeId?: AttributeId }[];
318
- dataVersionFilters?: { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[];
319
- enrichCachedAttributeData?: boolean;
320
- isFabricFiltered?: boolean;
321
- executeQueued?: boolean;
322
- attributeChangeListener?: (
323
- data: DecodedAttributeReportValue<any>,
324
- valueChanged?: boolean,
325
- oldValue?: any,
326
- ) => void;
327
- } = {},
328
- ): Promise<{
329
- attributeData: DecodedAttributeReportValue<any>[];
330
- attributeStatus?: DecodedAttributeReportStatus[];
331
- }> {
332
- const { attributeReports, attributeStatus } = await this.getMultipleAttributesAndEvents(options);
333
- return { attributeData: attributeReports, attributeStatus };
334
- }
335
-
336
- async getMultipleEvents(
337
- options: {
338
- events?: { endpointId?: EndpointNumber; clusterId?: ClusterId; eventId?: EventId }[];
339
- eventFilters?: TypeFromSchema<typeof TlvEventFilter>[];
340
- isFabricFiltered?: boolean;
341
- executeQueued?: boolean;
342
- } = {},
343
- ): Promise<DecodedEventReportValue<any>[]> {
344
- return (await this.getMultipleAttributesAndEvents(options)).eventReports;
345
- }
346
-
347
- async getMultipleEventsAndStatus(
348
- options: {
349
- events?: { endpointId?: EndpointNumber; clusterId?: ClusterId; eventId?: EventId }[];
350
- eventFilters?: TypeFromSchema<typeof TlvEventFilter>[];
351
- isFabricFiltered?: boolean;
352
- executeQueued?: boolean;
353
- } = {},
354
- ): Promise<{ eventData: DecodedEventReportValue<any>[]; eventStatus?: DecodedEventReportStatus[] }> {
355
- const { eventReports, eventStatus } = await this.getMultipleAttributesAndEvents(options);
356
- return { eventData: eventReports, eventStatus };
357
- }
358
-
359
- async getMultipleAttributesAndEvents(
360
- options: {
361
- attributes?: { endpointId?: EndpointNumber; clusterId?: ClusterId; attributeId?: AttributeId }[];
362
- dataVersionFilters?: { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[];
363
- enrichCachedAttributeData?: boolean;
364
- events?: { endpointId?: EndpointNumber; clusterId?: ClusterId; eventId?: EventId }[];
365
- eventFilters?: TypeFromSchema<typeof TlvEventFilter>[];
366
- isFabricFiltered?: boolean;
367
- executeQueued?: boolean;
368
- attributeChangeListener?: (
369
- data: DecodedAttributeReportValue<any>,
370
- valueChanged?: boolean,
371
- oldValue?: any,
372
- ) => void;
373
- } = {},
374
- ): Promise<DecodedDataReport> {
375
- if (this.isGroupAddress) {
376
- throw new ImplementationError("Reading data from group addresses is not supported.");
377
- }
378
-
379
- const {
380
- attributes: attributeRequests,
381
- dataVersionFilters,
382
- executeQueued,
383
- events: eventRequests,
384
- enrichCachedAttributeData,
385
- eventFilters,
386
- isFabricFiltered = true,
387
- attributeChangeListener,
388
- } = options;
389
- if (attributeRequests === undefined && eventRequests === undefined) {
390
- throw new ImplementationError("When reading attributes and events, at least one must be specified.");
391
- }
392
-
393
- const readPathsCount = (attributeRequests?.length ?? 0) + (eventRequests?.length ?? 0);
394
- if (readPathsCount > 9) {
395
- logger.debug(
396
- "Read interactions with more then 9 paths might be not allowed by the device. Consider splitting then into several read requests.",
397
- );
398
- }
399
-
400
- const result = await this.withMessenger(async messenger => {
401
- return await this.processReadRequest(
402
- messenger,
403
- {
404
- attributeRequests,
405
- dataVersionFilters: dataVersionFilters?.map(({ endpointId, clusterId, dataVersion }) => ({
406
- path: { endpointId, clusterId },
407
- dataVersion,
408
- })),
409
- eventRequests,
410
- eventFilters,
411
- isFabricFiltered,
412
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
413
- },
414
- attributeChangeListener,
415
- );
416
- }, executeQueued);
417
-
418
- if (dataVersionFilters !== undefined && dataVersionFilters.length > 0 && enrichCachedAttributeData) {
419
- this.#enrichCachedAttributeData(result.attributeReports, dataVersionFilters);
420
- }
421
-
422
- return result;
423
- }
424
-
425
- async getAttribute<A extends Attribute<any, any>>(options: {
426
- endpointId: EndpointNumber;
427
- clusterId: ClusterId;
428
- attribute: A;
429
- isFabricFiltered?: boolean;
430
- requestFromRemote?: boolean;
431
- executeQueued?: boolean;
432
- attributeChangeListener?: (
433
- data: DecodedAttributeReportValue<any>,
434
- valueChanged?: boolean,
435
- oldValue?: any,
436
- ) => void;
437
- }): Promise<AttributeJsType<A> | undefined> {
438
- const { requestFromRemote } = options;
439
- const response = await this.getAttributeWithVersion({
440
- ...options,
441
- requestFromRemote,
442
- });
443
- return response?.value;
444
- }
445
-
446
- getStoredAttribute<A extends Attribute<any, any>>(options: {
447
- endpointId: EndpointNumber;
448
- clusterId: ClusterId;
449
- attribute: A;
450
- }): AttributeJsType<A> | undefined {
451
- return this.getStoredAttributeWithVersion(options)?.value;
452
- }
453
-
454
- getStoredAttributeWithVersion<A extends Attribute<any, any>>(options: {
455
- endpointId: EndpointNumber;
456
- clusterId: ClusterId;
457
- attribute: A;
458
- }): { value: AttributeJsType<A>; version: number } | undefined {
459
- if (this.isGroupAddress) {
460
- throw new ImplementationError("Reading data from group addresses is not supported.");
461
- }
462
-
463
- const { endpointId, clusterId, attribute } = options;
464
- const { id: attributeId } = attribute;
465
- if (this.#nodeStore !== undefined) {
466
- const { value, version } = this.#nodeStore.retrieveAttribute(endpointId, clusterId, attributeId) ?? {};
467
- if (value !== undefined && version !== undefined) {
468
- return { value, version } as { value: AttributeJsType<A>; version: number };
469
- }
470
- }
471
- return undefined;
472
- }
473
-
474
- async getAttributeWithVersion<A extends Attribute<any, any>>(options: {
475
- endpointId: EndpointNumber;
476
- clusterId: ClusterId;
477
- attribute: A;
478
- isFabricFiltered?: boolean;
479
- requestFromRemote?: boolean;
480
- executeQueued?: boolean;
481
- attributeChangeListener?: (
482
- data: DecodedAttributeReportValue<any>,
483
- valueChanged?: boolean,
484
- oldValue?: any,
485
- ) => void;
486
- }): Promise<{ value: AttributeJsType<A>; version: number } | undefined> {
487
- if (this.isGroupAddress) {
488
- throw new ImplementationError("Reading data from group addresses is not supported.");
489
- }
490
-
491
- const {
492
- endpointId,
493
- clusterId,
494
- attribute,
495
- requestFromRemote,
496
- isFabricFiltered,
497
- executeQueued,
498
- attributeChangeListener,
499
- } = options;
500
- const { id: attributeId } = attribute;
501
- if (this.#nodeStore !== undefined) {
502
- if (!requestFromRemote) {
503
- const { value, version } = this.#nodeStore.retrieveAttribute(endpointId, clusterId, attributeId) ?? {};
504
- if (value !== undefined && version !== undefined) {
505
- return { value, version } as { value: AttributeJsType<A>; version: number };
506
- }
507
- }
508
- if (requestFromRemote === false) {
509
- return undefined;
510
- }
511
- }
512
-
513
- const { attributeReports } = await this.getMultipleAttributesAndEvents({
514
- attributes: [{ endpointId, clusterId, attributeId }],
515
- isFabricFiltered,
516
- executeQueued,
517
- attributeChangeListener,
518
- });
519
-
520
- if (attributeReports.length === 0) {
521
- return undefined;
522
- }
523
- if (attributeReports.length > 1) {
524
- throw new UnexpectedDataError("Unexpected response with more then one attribute");
525
- }
526
- return { value: attributeReports[0].value, version: attributeReports[0].version };
527
- }
528
-
529
- async getEvent<T, E extends Event<T, any>>(options: {
530
- endpointId: EndpointNumber;
531
- clusterId: ClusterId;
532
- event: E;
533
- minimumEventNumber?: EventNumber;
534
- isFabricFiltered?: boolean;
535
- executeQueued?: boolean;
536
- }): Promise<DecodedEventData<T>[] | undefined> {
537
- const { endpointId, clusterId, event, minimumEventNumber, isFabricFiltered = true, executeQueued } = options;
538
- const { id: eventId } = event;
539
- const response = await this.getMultipleAttributesAndEvents({
540
- events: [{ endpointId, clusterId, eventId }],
541
- eventFilters: minimumEventNumber !== undefined ? [{ eventMin: minimumEventNumber }] : undefined,
542
- isFabricFiltered,
543
- executeQueued,
544
- });
545
- return response?.eventReports[0]?.events;
546
- }
547
-
548
- private async processReadRequest(
549
- messenger: InteractionClientMessenger,
550
- request: ReadRequest,
551
- attributeChangeListener?: (
552
- data: DecodedAttributeReportValue<any>,
553
- valueChanged?: boolean,
554
- oldValue?: any,
555
- ) => void,
556
- ): Promise<DecodedDataReport> {
557
- const { attributeRequests, eventRequests, dataVersionFilters, eventFilters, isFabricFiltered } = request;
558
- logger.debug(() => [
559
- "Read",
560
- Mark.OUTBOUND,
561
- messenger.exchange.via,
562
- Diagnostic.dict({
563
- attributes: attributeRequests?.length
564
- ? attributeRequests?.map(path => resolveAttributeName(path)).join(", ")
565
- : undefined,
566
- events: eventRequests?.length
567
- ? eventRequests?.map(path => resolveEventName(path)).join(", ")
568
- : undefined,
569
- dataVersionFilters: dataVersionFilters?.length
570
- ? dataVersionFilters
571
- .map(
572
- ({ path: { endpointId, clusterId }, dataVersion }) =>
573
- `${endpointId}/${clusterId}=${dataVersion}`,
574
- )
575
- .join(", ")
576
- : undefined,
577
- eventFilters: eventFilters?.length
578
- ? eventFilters.map(({ nodeId, eventMin }) => `${nodeId}=${eventMin}`).join(", ")
579
- : undefined,
580
- fabricFiltered: isFabricFiltered,
581
- }),
582
- ]);
583
-
584
- // Send read request and combine all (potentially chunked) responses
585
- await messenger.sendReadRequest(request);
586
- const scope = ReadScope(request);
587
- const response = await messenger.readAggregateDataReport(chunk =>
588
- this.processAttributeUpdates(scope, chunk, attributeChangeListener),
589
- );
590
-
591
- // Normalize and decode the response
592
- const { attributeReports, attributeStatus, eventReports, eventStatus } = response;
593
-
594
- if (attributeReports.length || eventReports.length || attributeStatus?.length || eventStatus?.length) {
595
- logger.debug(() => [
596
- "Read",
597
- Mark.INBOUND,
598
- messenger.exchange.via,
599
- Diagnostic.dict({
600
- attributes: attributeReports.length
601
- ? attributeReports
602
- .map(({ path, value }) => `${resolveAttributeName(path)}=${serialize(value)}`)
603
- .join(", ")
604
- : undefined,
605
- events: eventReports.length
606
- ? eventReports.map(({ path }) => resolveEventName(path)).join(", ")
607
- : undefined,
608
- attributeStatus: attributeStatus?.length
609
- ? attributeStatus.map(({ path }) => resolveAttributeName(path)).join(", ")
610
- : undefined,
611
- eventStatus: eventStatus?.length
612
- ? eventStatus.map(({ path }) => resolveEventName(path)).join(", ")
613
- : undefined,
614
- fabricFiltered: isFabricFiltered,
615
- }),
616
- ]);
617
- } else {
618
- logger.debug("Read", Mark.INBOUND, messenger.exchange.via, "empty response");
619
- }
620
-
621
- return response;
622
- }
623
-
624
- async setAttribute<T>(options: {
625
- attributeData: {
626
- endpointId?: EndpointNumber;
627
- clusterId: ClusterId;
628
- attribute: Attribute<T, any>;
629
- value: T;
630
- dataVersion?: number;
631
- };
632
- asTimedRequest?: boolean;
633
- timedRequestTimeout?: Duration;
634
- suppressResponse?: boolean;
635
- executeQueued?: boolean;
636
- chunkLists?: boolean;
637
- }): Promise<void> {
638
- const { attributeData, asTimedRequest, timedRequestTimeout, suppressResponse, executeQueued, chunkLists } =
639
- options;
640
- const { endpointId, clusterId, attribute, value, dataVersion } = attributeData;
641
- const response = await this.setMultipleAttributes({
642
- attributes: [{ endpointId, clusterId, attribute, value, dataVersion }],
643
- asTimedRequest,
644
- timedRequestTimeout,
645
- suppressResponse,
646
- executeQueued,
647
- chunkLists,
648
- });
649
-
650
- // Response contains Status error if there was an error on write
651
- if (response.length) {
652
- const {
653
- path: { endpointId, clusterId, attributeId },
654
- status,
655
- } = response[0];
656
- if (status !== undefined && status !== StatusCode.Success) {
657
- throw new StatusResponseError(
658
- `Error setting attribute ${endpointId}/${clusterId}/${attributeId}`,
659
- status,
660
- );
661
- }
662
- }
663
- }
664
-
665
- async setMultipleAttributes(options: {
666
- attributes: {
667
- endpointId?: EndpointNumber;
668
- clusterId: ClusterId;
669
- attribute: Attribute<any, any>;
670
- value: any;
671
- dataVersion?: number;
672
- }[];
673
- asTimedRequest?: boolean;
674
- timedRequestTimeout?: Duration;
675
- suppressResponse?: boolean;
676
- executeQueued?: boolean;
677
- chunkLists?: boolean;
678
- }): Promise<AttributeStatus[]> {
679
- const { executeQueued } = options;
680
-
681
- const {
682
- attributes,
683
- asTimedRequest,
684
- timedRequestTimeout = DEFAULT_TIMED_REQUEST_TIMEOUT,
685
- suppressResponse = this.isGroupAddress,
686
- chunkLists = true, // Should be true currently to stay in sync with chip sdk
687
- } = options;
688
- if (this.isGroupAddress) {
689
- if (!suppressResponse) {
690
- throw new ImplementationError("Writing attributes on a group address can not return a response.");
691
- }
692
- if (
693
- attributes.some(
694
- ({ endpointId, clusterId, attribute }) =>
695
- endpointId !== undefined || clusterId === undefined || attribute.id === undefined,
696
- )
697
- ) {
698
- throw new ImplementationError("Not all attribute write paths are valid for group address writes.");
699
- }
700
- }
701
-
702
- // TODO Add multi message write handling with streamed encoding
703
- const writeRequests = attributes.flatMap(
704
- ({ endpointId, clusterId, attribute: { id, schema }, value, dataVersion }) => {
705
- if (
706
- chunkLists &&
707
- Array.isArray(value) &&
708
- schema instanceof ArraySchema &&
709
- // As implemented for Matter 1.4.2 in https://github.com/project-chip/connectedhomeip/pull/38263
710
- // Acl writes will no longer be chunked by default, all others still
711
- // Will be streamlined later ... see https://github.com/project-chip/connectedhomeip/issues/38270
712
- !isAclOrExtensionPath({ clusterId, attributeId: id })
713
- ) {
714
- return schema
715
- .encodeAsChunkedArray(value, { forWriteInteraction: true })
716
- .map(({ element: data, listIndex }) => ({
717
- path: { endpointId, clusterId, attributeId: id, listIndex },
718
- data,
719
- dataVersion,
720
- }));
721
- }
722
- return [
723
- {
724
- path: { endpointId, clusterId, attributeId: id },
725
- data: schema.encodeTlv(value, { forWriteInteraction: true }),
726
- dataVersion,
727
- },
728
- ];
729
- },
730
- );
731
- const timedRequest =
732
- attributes.some(({ attribute: { timed } }) => timed) ||
733
- asTimedRequest === true ||
734
- options.timedRequestTimeout !== undefined;
735
- if (this.isGroupAddress && timedRequest) {
736
- throw new ImplementationError("Timed requests are not supported for group address writes.");
737
- }
738
-
739
- const response = await this.withMessenger<TypeFromSchema<typeof TlvWriteResponse> | undefined>(
740
- async messenger => {
741
- if (timedRequest) {
742
- await messenger.sendTimedRequest(timedRequestTimeout);
743
- }
744
-
745
- logger.debug(() => [
746
- "Write",
747
- Mark.OUTBOUND,
748
- messenger.exchange.via,
749
- Diagnostic.dict({
750
- attributes: attributes
751
- .map(
752
- ({ endpointId, clusterId, attribute: { id }, value, dataVersion }) =>
753
- `${resolveAttributeName({ endpointId, clusterId, attributeId: id })} = ${Diagnostic.json(
754
- value,
755
- )} (version=${dataVersion})`,
756
- )
757
- .join(", "),
758
- }),
759
- ]);
760
-
761
- return await messenger.sendWriteCommand({
762
- suppressResponse,
763
- timedRequest,
764
- writeRequests,
765
- moreChunkedMessages: false,
766
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
767
- });
768
- },
769
- executeQueued,
770
- );
771
-
772
- if (response === undefined) {
773
- if (!suppressResponse) {
774
- throw new MatterFlowError(`No response received from write interaction but expected.`);
775
- }
776
- return [];
777
- }
778
- return response.writeResponses
779
- .flatMap(({ status: { status, clusterStatus }, path: { nodeId, endpointId, clusterId, attributeId } }) => {
780
- return {
781
- path: { nodeId, endpointId, clusterId, attributeId },
782
- status: status ?? clusterStatus ?? StatusCode.Failure,
783
- };
784
- })
785
- .filter(({ status }) => status !== StatusCode.Success);
786
- }
787
-
788
- async subscribeAttribute<A extends Attribute<any, any>>(options: {
789
- endpointId: EndpointNumber;
790
- clusterId: ClusterId;
791
- attribute: A;
792
- minIntervalFloorSeconds: number;
793
- maxIntervalCeilingSeconds: number;
794
- isFabricFiltered?: boolean;
795
- knownDataVersion?: number;
796
- keepSubscriptions?: boolean;
797
- listener?: (value: AttributeJsType<A>, version: number) => void;
798
- updateTimeoutHandler?: Timer.Callback;
799
- updateReceived?: () => void;
800
- executeQueued?: boolean;
801
- }): Promise<{
802
- maxInterval: number;
803
- }> {
804
- if (this.isGroupAddress) {
805
- throw new ImplementationError("Subscribing to attributes on a group address is not supported.");
806
- }
807
- const {
808
- endpointId,
809
- clusterId,
810
- attribute,
811
- minIntervalFloorSeconds,
812
- maxIntervalCeilingSeconds,
813
- isFabricFiltered = true,
814
- listener,
815
- knownDataVersion,
816
- keepSubscriptions = true,
817
- updateTimeoutHandler,
818
- updateReceived,
819
- executeQueued,
820
- } = options;
821
- const { id: attributeId } = attribute;
822
-
823
- if (!keepSubscriptions) {
824
- for (const subscriptionId of this.#ownSubscriptionIds) {
825
- logger.debug(
826
- `Removing subscription ${Subscription.idStrOf(subscriptionId)} from InteractionClient because new subscription replaces it`,
827
- );
828
- this.removeSubscription(subscriptionId);
829
- }
830
- }
831
-
832
- const request: SubscribeRequest = {
833
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
834
- attributeRequests: [{ endpointId, clusterId, attributeId }],
835
- dataVersionFilters:
836
- knownDataVersion !== undefined
837
- ? [{ path: { endpointId, clusterId }, dataVersion: knownDataVersion }]
838
- : undefined,
839
- keepSubscriptions,
840
- minIntervalFloorSeconds,
841
- maxIntervalCeilingSeconds,
842
- isFabricFiltered,
843
- };
844
- const scope = ReadScope(request);
845
-
846
- const {
847
- subscribeResponse: { subscriptionId, maxInterval },
848
- report,
849
- maximumPeerResponseTime,
850
- } = await this.withMessenger<{
851
- subscribeResponse: TypeFromSchema<typeof TlvSubscribeResponse>;
852
- report: DecodedDataReport;
853
- maximumPeerResponseTime: Duration;
854
- }>(async messenger => {
855
- logger.debug(() => [
856
- "Subscribe",
857
- Mark.OUTBOUND,
858
- messenger.exchange.via,
859
- Diagnostic.dict({
860
- attributes: resolveAttributeName({ endpointId, clusterId, attributeId }),
861
- dataVersionFilter: knownDataVersion,
862
- fabricFiltered: isFabricFiltered,
863
- minInterval: Duration.format(Seconds(minIntervalFloorSeconds)),
864
- maxInterval: Duration.format(Seconds(maxIntervalCeilingSeconds)),
865
- }),
866
- ]);
867
-
868
- await messenger.sendSubscribeRequest(request);
869
- const { subscribeResponse, report } = await messenger.readAggregateSubscribeResponse();
870
- return {
871
- subscribeResponse,
872
- report,
873
- maximumPeerResponseTime: this.maximumPeerResponseTime(),
874
- };
875
- }, executeQueued);
876
-
877
- const subscriptionListener = async (dataReport: DecodedDataReport) => {
878
- const { attributeReports } = dataReport;
879
-
880
- if (attributeReports.length === 0) {
881
- throw new MatterFlowError("Subscription result reporting undefined/no value not specified");
882
- }
883
- if (attributeReports.length > 1) {
884
- throw new UnexpectedDataError("Unexpected response with more then one attribute");
885
- }
886
- const { value, version } = attributeReports[0];
887
- if (value === undefined)
888
- throw new MatterFlowError("Subscription result reporting undefined value not specified.");
889
-
890
- await this.#nodeStore?.persistAttributes(attributeReports, scope);
891
-
892
- listener?.(value, version);
893
-
894
- updateReceived?.();
895
- };
896
-
897
- await this.#registerSubscription(
898
- {
899
- id: subscriptionId,
900
- maximumPeerResponseTime,
901
- maxInterval: Seconds(maxInterval),
902
- onData: subscriptionListener,
903
- onTimeout: updateTimeoutHandler,
904
- },
905
- report,
906
- );
907
-
908
- return { maxInterval };
909
- }
910
-
911
- async #registerSubscription(subscription: RegisteredSubscription, initialReport: DecodedDataReport) {
912
- this.#ownSubscriptionIds.add(subscription.id);
913
- this.#subscriptionClient?.add(subscription);
914
- await subscription.onData(initialReport);
915
- }
916
-
917
- async subscribeEvent<T, E extends Event<T, any>>(options: {
918
- endpointId: EndpointNumber;
919
- clusterId: ClusterId;
920
- event: E;
921
- minIntervalFloor: Duration;
922
- maxIntervalCeiling: Duration;
923
- isUrgent?: boolean;
924
- minimumEventNumber?: EventNumber;
925
- isFabricFiltered?: boolean;
926
- listener?: (value: DecodedEventData<T>) => void;
927
- updateTimeoutHandler?: Timer.Callback;
928
- updateReceived?: () => void;
929
- executeQueued?: boolean;
930
- }): Promise<{
931
- maxInterval: number;
932
- }> {
933
- if (this.isGroupAddress) {
934
- throw new ImplementationError("Subscribing to events on a group address is not supported.");
935
- }
936
- const {
937
- endpointId,
938
- clusterId,
939
- event,
940
- minIntervalFloor,
941
- maxIntervalCeiling,
942
- isUrgent,
943
- minimumEventNumber,
944
- isFabricFiltered = true,
945
- listener,
946
- updateTimeoutHandler,
947
- updateReceived,
948
- executeQueued,
949
- } = options;
950
- const { id: eventId } = event;
951
-
952
- const {
953
- report,
954
- subscribeResponse: { subscriptionId, maxInterval },
955
- maximumPeerResponseTime,
956
- } = await this.withMessenger<{
957
- subscribeResponse: TypeFromSchema<typeof TlvSubscribeResponse>;
958
- report: DecodedDataReport;
959
- maximumPeerResponseTime: Duration;
960
- }>(async messenger => {
961
- logger.debug(() => [
962
- "Subscribe",
963
- Mark.OUTBOUND,
964
- messenger.exchange.via,
965
- Diagnostic.dict({
966
- events: resolveEventName({ endpointId, clusterId, eventId }),
967
- fabricFiltered: isFabricFiltered,
968
- minInterval: Duration.format(minIntervalFloor),
969
- maxInterval: Duration.format(maxIntervalCeiling),
970
- }),
971
- ]);
972
-
973
- await messenger.sendSubscribeRequest({
974
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
975
- eventRequests: [{ endpointId, clusterId, eventId, isUrgent }],
976
- eventFilters: minimumEventNumber !== undefined ? [{ eventMin: minimumEventNumber }] : undefined,
977
- keepSubscriptions: true,
978
- minIntervalFloorSeconds: Seconds.of(minIntervalFloor),
979
- maxIntervalCeilingSeconds: Seconds.of(maxIntervalCeiling),
980
- isFabricFiltered,
981
- });
982
- const { subscribeResponse, report } = await messenger.readAggregateSubscribeResponse();
983
- return {
984
- subscribeResponse,
985
- report,
986
- maximumPeerResponseTime: this.maximumPeerResponseTime(),
987
- };
988
- }, executeQueued);
989
-
990
- const subscriptionListener = (dataReport: DecodedDataReport) => {
991
- const { eventReports } = dataReport;
992
-
993
- if (eventReports.length === 0) {
994
- throw new MatterFlowError("Received empty subscription result value.");
995
- }
996
- if (eventReports.length > 1) {
997
- throw new UnexpectedDataError("Unexpected response with more then one attribute.");
998
- }
999
- const { events } = eventReports[0];
1000
- if (events === undefined)
1001
- throw new MatterFlowError("Subscription result reporting undefined value not specified.");
1002
-
1003
- events.forEach(event => listener?.(event));
1004
-
1005
- updateReceived?.();
1006
- };
1007
-
1008
- await this.#registerSubscription(
1009
- {
1010
- id: subscriptionId,
1011
- maximumPeerResponseTime,
1012
- maxInterval: Seconds(maxInterval),
1013
- onData: subscriptionListener,
1014
- onTimeout: updateTimeoutHandler,
1015
- },
1016
- report,
1017
- );
1018
-
1019
- return { maxInterval };
1020
- }
1021
-
1022
- async subscribeAllAttributesAndEvents(options: {
1023
- minIntervalFloorSeconds: number;
1024
- maxIntervalCeilingSeconds: number;
1025
- attributeListener?: (
1026
- data: DecodedAttributeReportValue<any>,
1027
- valueChanged?: boolean,
1028
- oldValue?: unknown,
1029
- ) => void;
1030
- eventListener?: (data: DecodedEventReportValue<any>) => void;
1031
- isUrgent?: boolean;
1032
- keepSubscriptions?: boolean;
1033
- isFabricFiltered?: boolean;
1034
- eventFilters?: TypeFromSchema<typeof TlvEventFilter>[];
1035
- dataVersionFilters?: { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[];
1036
- enrichCachedAttributeData?: boolean;
1037
- updateTimeoutHandler?: Timer.Callback;
1038
- updateReceived?: () => void;
1039
- executeQueued?: boolean;
1040
- }): Promise<{
1041
- attributeReports?: DecodedAttributeReportValue<any>[];
1042
- eventReports?: DecodedEventReportValue<any>[];
1043
- maxInterval: number;
1044
- }> {
1045
- const { isUrgent } = options;
1046
- return this.subscribeMultipleAttributesAndEvents({
1047
- ...options,
1048
- attributes: REQUEST_ALL,
1049
- events: [{ isUrgent }],
1050
- });
1051
- }
1052
-
1053
- async subscribeMultipleAttributesAndEvents(options: {
1054
- attributes?: { endpointId?: EndpointNumber; clusterId?: ClusterId; attributeId?: AttributeId }[];
1055
- events?: { endpointId?: EndpointNumber; clusterId?: ClusterId; eventId?: EventId; isUrgent?: boolean }[];
1056
- minIntervalFloorSeconds: number;
1057
- maxIntervalCeilingSeconds: number;
1058
- keepSubscriptions?: boolean;
1059
- isFabricFiltered?: boolean;
1060
- attributeListener?: (data: DecodedAttributeReportValue<any>, valueChanged?: boolean, oldValue?: any) => void;
1061
- eventListener?: (data: DecodedEventReportValue<any>) => void;
1062
- eventFilters?: TypeFromSchema<typeof TlvEventFilter>[];
1063
- dataVersionFilters?: { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[];
1064
- enrichCachedAttributeData?: boolean;
1065
- updateTimeoutHandler?: Timer.Callback;
1066
- updateReceived?: () => void;
1067
- executeQueued?: boolean;
1068
- }): Promise<{
1069
- attributeReports?: DecodedAttributeReportValue<any>[];
1070
- eventReports?: DecodedEventReportValue<any>[];
1071
- maxInterval: number;
1072
- }> {
1073
- if (this.isGroupAddress) {
1074
- throw new ImplementationError("Subscribing to attributes or events on a group address is not supported.");
1075
- }
1076
- const {
1077
- attributes: attributeRequests = [],
1078
- events: eventRequests = [],
1079
- executeQueued,
1080
- minIntervalFloorSeconds,
1081
- maxIntervalCeilingSeconds,
1082
- keepSubscriptions = true,
1083
- isFabricFiltered = true,
1084
- attributeListener,
1085
- eventListener,
1086
- eventFilters,
1087
- dataVersionFilters,
1088
- updateTimeoutHandler,
1089
- updateReceived,
1090
- enrichCachedAttributeData,
1091
- } = options;
1092
-
1093
- const subscriptionPathsCount = (attributeRequests?.length ?? 0) + (eventRequests?.length ?? 0);
1094
- if (subscriptionPathsCount > 3) {
1095
- logger.debug("Subscribe interactions with more then 3 paths might be not allowed by the device.");
1096
- }
1097
-
1098
- if (!keepSubscriptions) {
1099
- for (const subscriptionId of this.#ownSubscriptionIds) {
1100
- logger.debug(
1101
- `Removing subscription with ID ${Subscription.idStrOf(subscriptionId)} from InteractionClient because new subscription replaces it`,
1102
- );
1103
- this.removeSubscription(subscriptionId);
1104
- }
1105
- }
1106
-
1107
- const request: SubscribeRequest = {
1108
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
1109
- attributeRequests,
1110
- eventRequests,
1111
- keepSubscriptions,
1112
- minIntervalFloorSeconds,
1113
- maxIntervalCeilingSeconds,
1114
- isFabricFiltered,
1115
- eventFilters,
1116
- dataVersionFilters: dataVersionFilters?.map(({ endpointId, clusterId, dataVersion }) => ({
1117
- path: { endpointId, clusterId },
1118
- dataVersion,
1119
- })),
1120
- };
1121
- const scope = ReadScope(request);
1122
-
1123
- let processNewAttributeChangesInListener = false;
1124
- const {
1125
- report,
1126
- subscribeResponse: { subscriptionId, maxInterval },
1127
- maximumPeerResponseTime,
1128
- } = await this.withMessenger<{
1129
- subscribeResponse: TypeFromSchema<typeof TlvSubscribeResponse>;
1130
- report: DecodedDataReport;
1131
- maximumPeerResponseTime: Duration;
1132
- }>(async messenger => {
1133
- logger.debug(() => [
1134
- "Subscribe",
1135
- Mark.OUTBOUND,
1136
- messenger.exchange.via,
1137
- Diagnostic.dict({
1138
- attributes: attributeRequests.length
1139
- ? attributeRequests.map(path => resolveAttributeName(path)).join(", ")
1140
- : undefined,
1141
- events: eventRequests.length
1142
- ? eventRequests.map(path => resolveEventName(path)).join(", ")
1143
- : undefined,
1144
- dataVersionFilter: dataVersionFilters?.length
1145
- ? dataVersionFilters
1146
- .map(
1147
- ({ endpointId, clusterId, dataVersion }) =>
1148
- `${endpointId}/${clusterId}=${dataVersion}`,
1149
- )
1150
- .join(", ")
1151
- : undefined,
1152
- eventFilters: eventFilters?.length
1153
- ? eventFilters.map(({ nodeId, eventMin }) => `${nodeId}=${eventMin}`).join(", ")
1154
- : undefined,
1155
- fabricFiltered: isFabricFiltered,
1156
- keepSubscriptions,
1157
- minInterval: Duration.format(Seconds(minIntervalFloorSeconds)),
1158
- maxInterval: Duration.format(Seconds(maxIntervalCeilingSeconds)),
1159
- }),
1160
- ]);
1161
-
1162
- await messenger.sendSubscribeRequest(request);
1163
- const { subscribeResponse, report } = await messenger.readAggregateSubscribeResponse(attributeReports =>
1164
- this.processAttributeUpdates(scope, attributeReports, attributeListener),
1165
- );
1166
-
1167
- logger.info(
1168
- "Subscription successful",
1169
- Mark.INBOUND,
1170
- messenger.exchange.via,
1171
- Diagnostic.dict({
1172
- ...Subscription.diagnosticOf(subscribeResponse.subscriptionId),
1173
- maxInterval: Duration.format(Seconds(subscribeResponse.maxInterval)),
1174
- }),
1175
- );
1176
-
1177
- return {
1178
- subscribeResponse,
1179
- report,
1180
- maximumPeerResponseTime: this.maximumPeerResponseTime(),
1181
- };
1182
- }, executeQueued);
1183
-
1184
- const subscriptionListener = async (dataReport: {
1185
- attributeReports?: DecodedAttributeReportValue<any>[];
1186
- eventReports?: DecodedEventReportValue<any>[];
1187
- subscriptionId?: number;
1188
- }) => {
1189
- if (
1190
- (!Array.isArray(dataReport.attributeReports) || !dataReport.attributeReports.length) &&
1191
- (!Array.isArray(dataReport.eventReports) || !dataReport.eventReports.length)
1192
- ) {
1193
- updateReceived?.();
1194
- return;
1195
- }
1196
- const { attributeReports, eventReports } = dataReport;
1197
-
1198
- // We emit events first because events usually happened and lead to a new final attribute value
1199
- if (eventReports?.length) {
1200
- let maxEventNumber = this.#nodeStore?.maxEventNumber ?? eventReports[0].events[0].eventNumber;
1201
- eventReports.forEach(data => {
1202
- logger.debug(
1203
- `Event update ${Mark.INBOUND} ${resolveEventName(data.path)}: ${Diagnostic.json(data.events)}`,
1204
- );
1205
- const { events } = data;
1206
-
1207
- maxEventNumber =
1208
- events.length === 1
1209
- ? events[0].eventNumber
1210
- : events.reduce(
1211
- (max, { eventNumber }) => (max < eventNumber ? eventNumber : max),
1212
- maxEventNumber,
1213
- );
1214
- eventListener?.(data);
1215
- });
1216
- await this.#nodeStore?.updateLastEventNumber(maxEventNumber);
1217
- }
1218
-
1219
- // Initial Data reports during seeding are handled directly
1220
- if (processNewAttributeChangesInListener && attributeReports !== undefined) {
1221
- await this.processAttributeUpdates(scope, attributeReports, attributeListener);
1222
- }
1223
- updateReceived?.();
1224
- };
1225
-
1226
- await this.#registerSubscription(
1227
- {
1228
- id: subscriptionId,
1229
- maximumPeerResponseTime,
1230
- maxInterval: Seconds(maxInterval),
1231
-
1232
- onData: dataReport => subscriptionListener(dataReport),
1233
-
1234
- onTimeout: updateTimeoutHandler,
1235
- },
1236
- report,
1237
- );
1238
- processNewAttributeChangesInListener = true;
1239
-
1240
- if (dataVersionFilters !== undefined && dataVersionFilters.length > 0 && enrichCachedAttributeData) {
1241
- this.#enrichCachedAttributeData(report.attributeReports, dataVersionFilters);
1242
- }
1243
-
1244
- return {
1245
- ...report,
1246
- maxInterval,
1247
- };
1248
- }
1249
-
1250
- /**
1251
- * Process changed attributes, detect changes and persist them to the node store
1252
- */
1253
- async processAttributeUpdates(
1254
- scope: ReadScope,
1255
- attributeReports: DecodedAttributeReportValue<any>[],
1256
- attributeListener?: (data: DecodedAttributeReportValue<any>, valueChanged?: boolean, oldValue?: any) => void,
1257
- ) {
1258
- for (const data of attributeReports) {
1259
- const {
1260
- path: { endpointId, clusterId, attributeId },
1261
- value,
1262
- version,
1263
- } = data;
1264
-
1265
- if (value === undefined) {
1266
- throw new MatterFlowError("Received empty subscription result value.");
1267
- }
1268
- const { value: oldValue, version: oldVersion } =
1269
- this.#nodeStore?.retrieveAttribute(endpointId, clusterId, attributeId) ?? {};
1270
- const changed = oldValue !== undefined ? !isDeepEqual(oldValue, value) : undefined;
1271
- if (changed !== false || version !== oldVersion) {
1272
- await this.#nodeStore?.persistAttributes([data], scope);
1273
- }
1274
- logger.debug(
1275
- `Attribute update ${Mark.INBOUND}${changed ? " (value changed)" : ""}: ${resolveAttributeName({
1276
- endpointId,
1277
- clusterId,
1278
- attributeId,
1279
- })} = ${serialize(value)} (version=${version})`,
1280
- );
1281
-
1282
- attributeListener?.(data, changed, oldValue);
1283
- }
1284
- }
1285
-
1286
- async invoke<C extends Command<any, any, any>>(options: {
1287
- endpointId?: EndpointNumber;
1288
- clusterId: ClusterId;
1289
- request: RequestType<C>;
1290
- command: C;
1291
-
1292
- /** Send as timed request. If no timedRequestTimeoutMs is provided the default of 10s will be used. */
1293
- asTimedRequest?: boolean;
1294
-
1295
- /** Use this timeout and send the request as Timed Request. If this is specified the above parameter is implied. */
1296
- timedRequestTimeout?: Duration;
1297
-
1298
- /**
1299
- * Expected processing time on the device side for this command.
1300
- * useExtendedFailSafeMessageResponseTimeout is ignored if this value is set.
1301
- */
1302
- expectedProcessingTime?: Duration;
1303
-
1304
- /** Use an extended Message Response Timeout as defined for FailSafe cases which is 30s. */
1305
- useExtendedFailSafeMessageResponseTimeout?: boolean;
1306
-
1307
- /** Execute this request queued - mainly used to execute invokes sequentially for thread devices. */
1308
- executeQueued?: boolean;
1309
-
1310
- /** Skip request data validation. Use this only when you know that your data is correct and validation would return an error. */
1311
- skipValidation?: boolean;
1312
- }): Promise<ResponseType<C>> {
1313
- const { executeQueued } = options;
1314
-
1315
- const {
1316
- endpointId,
1317
- clusterId,
1318
- command: { requestId, requestSchema, responseId, responseSchema, optional, timed },
1319
- asTimedRequest,
1320
- timedRequestTimeout: timedRequestTimeoutMs = DEFAULT_TIMED_REQUEST_TIMEOUT,
1321
- expectedProcessingTime,
1322
- useExtendedFailSafeMessageResponseTimeout = false,
1323
- skipValidation,
1324
- } = options;
1325
- let { request } = options;
1326
- const timedRequest =
1327
- (timed && !skipValidation) || asTimedRequest === true || options.timedRequestTimeout !== undefined;
1328
-
1329
- if (this.isGroupAddress) {
1330
- if (endpointId !== undefined) {
1331
- throw new ImplementationError("Invoking a concrete command on a group address is not supported.");
1332
- }
1333
- if (timedRequest) {
1334
- throw new ImplementationError("Timed requests are not supported for group address invokes.");
1335
- }
1336
- }
1337
-
1338
- if (requestSchema instanceof ObjectSchema) {
1339
- if (request === undefined) {
1340
- // If developer did not provide a request object, create an empty one if it needs to be an object
1341
- // This can happen when all object properties are optional
1342
- request = {} as RequestType<C>;
1343
- }
1344
- if (requestSchema.isFabricScoped && request.fabricIndex === undefined) {
1345
- request.fabricIndex = FabricIndex.NO_FABRIC;
1346
- }
1347
- }
1348
-
1349
- logger.debug(
1350
- `Invoking command: ${resolveCommandName({
1351
- endpointId,
1352
- clusterId,
1353
- commandId: requestId,
1354
- })} with ${Diagnostic.json(request)}`,
1355
- );
1356
-
1357
- if (!skipValidation) {
1358
- requestSchema.validate(request);
1359
- }
1360
-
1361
- const commandFields = requestSchema.encodeTlv(request);
1362
-
1363
- const invokeResponse = await this.withMessenger<TypeFromSchema<typeof TlvInvokeResponse>>(async messenger => {
1364
- if (timedRequest) {
1365
- await messenger.sendTimedRequest(timedRequestTimeoutMs);
1366
- }
1367
-
1368
- const response = await messenger.sendInvokeCommand(
1369
- {
1370
- invokeRequests: [{ commandPath: { endpointId, clusterId, commandId: requestId }, commandFields }],
1371
- timedRequest,
1372
- suppressResponse: false,
1373
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
1374
- },
1375
- expectedProcessingTime ??
1376
- (useExtendedFailSafeMessageResponseTimeout
1377
- ? DEFAULT_MINIMUM_RESPONSE_TIMEOUT_WITH_FAILSAFE
1378
- : undefined),
1379
- );
1380
- if (response === undefined) {
1381
- throw new MatterFlowError("No response received from invoke interaction but expected.");
1382
- }
1383
- return response;
1384
- }, executeQueued);
1385
-
1386
- const { invokeResponses } = invokeResponse;
1387
- if (invokeResponses.length === 0) {
1388
- throw new MatterFlowError("Received invoke response with no invoke results.");
1389
- }
1390
- const { command, status } = invokeResponses[0];
1391
- if (status !== undefined) {
1392
- const resultCode = status.status.status;
1393
- if (resultCode !== StatusCode.Success)
1394
- throw new StatusResponseError(
1395
- `Received non-success result: ${resultCode}`,
1396
- resultCode ?? StatusCode.Failure,
1397
- status.status.clusterStatus,
1398
- );
1399
- if ((responseSchema as any) !== TlvNoResponse)
1400
- throw new MatterFlowError("A response was expected for this command.");
1401
- return undefined as unknown as ResponseType<C>; // ResponseType is void, force casting the empty result
1402
- }
1403
- if (command !== undefined) {
1404
- const {
1405
- commandPath: { commandId },
1406
- commandFields,
1407
- } = command;
1408
- if (commandId !== responseId) {
1409
- throw new MatterFlowError(
1410
- `Received invoke response with unexpected command ID ${commandId}, expected ${responseId}.`,
1411
- );
1412
- }
1413
- if (commandFields === undefined) {
1414
- if ((responseSchema as any) !== TlvNoResponse)
1415
- throw new MatterFlowError(`A response was expected for command ${requestId}.`);
1416
- return undefined as unknown as ResponseType<C>; // ResponseType is void, force casting the empty result
1417
- }
1418
- const response = responseSchema.decodeTlv(commandFields);
1419
- logger.debug(
1420
- "Invoke",
1421
- Mark.INBOUND,
1422
- resolveCommandName({
1423
- endpointId,
1424
- clusterId,
1425
- commandId: requestId,
1426
- }),
1427
- "with",
1428
- Diagnostic.json(response),
1429
- );
1430
- return response;
1431
- }
1432
- if (optional) {
1433
- return undefined as ResponseType<C>; // ResponseType allows undefined for optional commands
1434
- }
1435
- throw new MatterFlowError("Received invoke response with no result nor response.");
1436
- }
1437
-
1438
- // TODO Add to ClusterClient when needed/when Group communication is implemented
1439
- // TODO Additionally support it without endpoint
1440
- async invokeWithSuppressedResponse<C extends Command<any, any, any>>(options: {
1441
- endpointId?: EndpointNumber;
1442
- clusterId: ClusterId;
1443
- request: RequestType<C>;
1444
- command: C;
1445
- asTimedRequest?: boolean;
1446
- timedRequestTimeout?: Duration;
1447
- executeQueued?: boolean;
1448
- }): Promise<void> {
1449
- const { executeQueued } = options;
1450
-
1451
- const {
1452
- endpointId,
1453
- clusterId,
1454
- request,
1455
- command: { requestId, requestSchema, timed },
1456
- asTimedRequest,
1457
- timedRequestTimeout = DEFAULT_TIMED_REQUEST_TIMEOUT,
1458
- } = options;
1459
- const timedRequest = timed || asTimedRequest === true || options.timedRequestTimeout !== undefined;
1460
-
1461
- if (this.isGroupAddress) {
1462
- if (timed) {
1463
- throw new ImplementationError("Timed requests are not supported for group address invokes.");
1464
- }
1465
- if (endpointId !== undefined) {
1466
- throw new ImplementationError("Invoking a concrete command on a group address is not supported.");
1467
- }
1468
- }
1469
-
1470
- logger.debug(
1471
- `Invoking command with suppressedResponse: ${resolveCommandName({
1472
- endpointId,
1473
- clusterId,
1474
- commandId: requestId,
1475
- })} with ${Diagnostic.json(request)}`,
1476
- );
1477
- const commandFields = requestSchema.encodeTlv(request);
1478
-
1479
- await this.withMessenger<void>(async messenger => {
1480
- if (timedRequest) {
1481
- await messenger.sendTimedRequest(timedRequestTimeout);
1482
- }
1483
-
1484
- const response = await messenger.sendInvokeCommand({
1485
- invokeRequests: [{ commandPath: { endpointId, clusterId, commandId: requestId }, commandFields }],
1486
- timedRequest,
1487
- suppressResponse: true,
1488
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
1489
- });
1490
- if (response !== undefined) {
1491
- throw new MatterFlowError(
1492
- "Response received from invoke interaction but none expected because response is suppressed.",
1493
- );
1494
- }
1495
- }, executeQueued);
1496
-
1497
- logger.debug(
1498
- "Invoke successful",
1499
- Mark.INBOUND,
1500
- resolveCommandName({
1501
- endpointId,
1502
- clusterId,
1503
- commandId: requestId,
1504
- }),
1505
- );
1506
- }
1507
-
1508
- private async withMessenger<T>(
1509
- invoke: (messenger: InteractionClientMessenger) => Promise<T>,
1510
- executeQueued = false,
1511
- ): Promise<T> {
1512
- const messenger = await InteractionClientMessenger.create(this.#exchangeProvider);
1513
- let result: T;
1514
- try {
1515
- if (executeQueued) {
1516
- if (this.#queue === undefined) {
1517
- throw new ImplementationError("Cannot execute queued operation without a queue.");
1518
- }
1519
- return await this.#queue.add(() => invoke(messenger));
1520
- }
1521
- result = await invoke(messenger);
1522
- } finally {
1523
- // No need to wait for closing and final ack message here, for us all is done
1524
- messenger.close().catch(error => logger.info(`Error closing messenger: ${error}`));
1525
- }
1526
- return result;
1527
- }
1528
-
1529
- removeAllSubscriptions() {
1530
- for (const subscriptionId of this.#ownSubscriptionIds) {
1531
- this.removeSubscription(subscriptionId);
1532
- }
1533
- }
1534
-
1535
- close() {
1536
- this.removeAllSubscriptions();
1537
- }
1538
-
1539
- get session() {
1540
- return this.#exchangeProvider.session;
1541
- }
1542
-
1543
- get channelType() {
1544
- return this.#exchangeProvider.channelType;
1545
- }
1546
-
1547
- /** Enrich cached data to get complete responses when data version filters were used. */
1548
- #enrichCachedAttributeData(
1549
- attributeReports: DecodedAttributeReportValue<any>[],
1550
- dataVersionFilters: { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[],
1551
- ) {
1552
- if (this.#nodeStore === undefined) {
1553
- return;
1554
- }
1555
-
1556
- // Collect the Endpoints and clusters to potentially enrich data from the cache
1557
- const candidates = new Map<EndpointNumber, Map<ClusterId, number>>();
1558
- for (const { endpointId, clusterId, dataVersion } of dataVersionFilters) {
1559
- if (!candidates.has(endpointId)) {
1560
- candidates.set(endpointId, new Map());
1561
- }
1562
- candidates
1563
- .get(endpointId)
1564
- ?.set(clusterId, this.#nodeStore.getClusterDataVersion(endpointId, clusterId) ?? dataVersion);
1565
- }
1566
-
1567
- // Remove all where data were returned because there the versions did not match
1568
- attributeReports.forEach(({ path: { endpointId, clusterId } }) => {
1569
- if (candidates.has(endpointId)) {
1570
- candidates.get(endpointId)?.delete(clusterId);
1571
- }
1572
- });
1573
-
1574
- // Enrich the data from the cache for all Endpoints and clusters that are left
1575
- for (const [endpointId, clusters] of candidates) {
1576
- for (const [clusterId, version] of clusters) {
1577
- const clusterValues = this.#nodeStore.retrieveAttributes(endpointId, clusterId);
1578
- logger.debug(
1579
- `Enriching cached data (${clusterValues.length} attributes) for ${endpointId}/${clusterId} with version=${version}`,
1580
- );
1581
- attributeReports.push(...clusterValues);
1582
- }
1583
- }
1584
- }
1585
-
1586
- /**
1587
- * Returns the list (optionally filtered by endpointId and/or clusterId) of the dataVersions of the currently cached
1588
- * values to use them as knownDataVersion for read or subscription requests.
1589
- */
1590
- getCachedClusterDataVersions(filter?: {
1591
- endpointId?: EndpointNumber;
1592
- clusterId?: ClusterId;
1593
- }): { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[] {
1594
- if (this.#nodeStore === undefined) {
1595
- return [];
1596
- }
1597
- const { endpointId, clusterId } = filter ?? {};
1598
- return this.#nodeStore.getClusterDataVersions(endpointId, clusterId);
1599
- }
1600
-
1601
- get maxKnownEventNumber() {
1602
- return this.#nodeStore?.maxEventNumber;
1603
- }
1604
-
1605
- cleanupAttributeData(endpointId: EndpointNumber, clusterIds?: ClusterId[]): MaybePromise<void> {
1606
- return this.#nodeStore?.cleanupAttributeData(endpointId, clusterIds);
1607
- }
1608
-
1609
- getAllCachedClusterData() {
1610
- const result = new Array<DecodedAttributeReportValue<any>>();
1611
- this.#enrichCachedAttributeData(result, this.getCachedClusterDataVersions());
1612
- return result;
1613
- }
1614
- }