@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.
- package/LICENSE +1 -1
- package/dist/cjs/action/client/ClientInteraction.d.ts +12 -5
- package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
- package/dist/cjs/action/client/ClientInteraction.js +39 -15
- package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
- package/dist/cjs/action/client/ClientRead.d.ts +10 -0
- package/dist/cjs/action/client/ClientRead.d.ts.map +1 -0
- package/dist/cjs/action/client/ClientRead.js +22 -0
- package/dist/cjs/action/client/ClientRead.js.map +6 -0
- package/dist/cjs/action/client/ClientRequest.d.ts +20 -0
- package/dist/cjs/action/client/ClientRequest.d.ts.map +1 -0
- package/dist/cjs/action/client/ClientRequest.js +22 -0
- package/dist/cjs/action/client/ClientRequest.js.map +6 -0
- package/dist/cjs/action/client/ClientWrite.d.ts +10 -0
- package/dist/cjs/action/client/ClientWrite.d.ts.map +1 -0
- package/dist/cjs/action/client/ClientWrite.js +22 -0
- package/dist/cjs/action/client/ClientWrite.js.map +6 -0
- package/dist/cjs/action/client/QueuedClientInteraction.d.ts +49 -0
- package/dist/cjs/action/client/QueuedClientInteraction.d.ts.map +1 -0
- package/dist/cjs/action/client/QueuedClientInteraction.js +160 -0
- package/dist/cjs/action/client/QueuedClientInteraction.js.map +6 -0
- package/dist/cjs/action/client/index.d.ts +4 -0
- package/dist/cjs/action/client/index.d.ts.map +1 -1
- package/dist/cjs/action/client/index.js +4 -0
- package/dist/cjs/action/client/index.js.map +1 -1
- package/dist/cjs/action/client/subscription/ClientSubscribe.d.ts +2 -1
- package/dist/cjs/action/client/subscription/ClientSubscribe.d.ts.map +1 -1
- package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
- package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js +14 -3
- package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
- package/dist/cjs/action/client/subscription/SustainedSubscription.d.ts +1 -1
- package/dist/cjs/action/client/subscription/SustainedSubscription.d.ts.map +1 -1
- package/dist/cjs/action/client/subscription/SustainedSubscription.js +1 -4
- package/dist/cjs/action/client/subscription/SustainedSubscription.js.map +1 -1
- package/dist/cjs/action/request/Invoke.d.ts +7 -1
- package/dist/cjs/action/request/Invoke.d.ts.map +1 -1
- package/dist/cjs/action/request/Invoke.js +0 -3
- package/dist/cjs/action/request/Invoke.js.map +1 -1
- package/dist/cjs/action/request/Read.d.ts.map +1 -1
- package/dist/cjs/action/request/Read.js +3 -2
- package/dist/cjs/action/request/Read.js.map +1 -1
- package/dist/cjs/action/request/Specifier.d.ts +1 -1
- package/dist/cjs/action/request/Specifier.d.ts.map +1 -1
- package/dist/cjs/action/request/Specifier.js +3 -0
- package/dist/cjs/action/request/Specifier.js.map +1 -1
- package/dist/cjs/action/request/Write.d.ts +1 -0
- package/dist/cjs/action/request/Write.d.ts.map +1 -1
- package/dist/cjs/action/request/Write.js +10 -2
- package/dist/cjs/action/request/Write.js.map +1 -1
- package/dist/cjs/action/response/ReadResult.d.ts +1 -1
- package/dist/cjs/action/response/ReadResult.d.ts.map +1 -1
- package/dist/cjs/cluster/client/ClusterClientTypes.d.ts +37 -8
- package/dist/cjs/cluster/client/ClusterClientTypes.d.ts.map +1 -1
- package/dist/cjs/cluster/client/index.d.ts +0 -3
- package/dist/cjs/cluster/client/index.d.ts.map +1 -1
- package/dist/cjs/cluster/client/index.js +0 -3
- package/dist/cjs/cluster/client/index.js.map +1 -1
- package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionMessenger.js +3 -2
- package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
- package/dist/cjs/interaction/SubscriptionClient.d.ts.map +1 -1
- package/dist/cjs/interaction/SubscriptionClient.js +2 -1
- package/dist/cjs/interaction/SubscriptionClient.js.map +1 -1
- package/dist/cjs/interaction/index.d.ts +1 -1
- package/dist/cjs/interaction/index.d.ts.map +1 -1
- package/dist/cjs/interaction/index.js +1 -1
- package/dist/cjs/interaction/index.js.map +1 -1
- package/dist/cjs/peer/CommissioningError.d.ts +13 -0
- package/dist/cjs/peer/CommissioningError.d.ts.map +1 -0
- package/dist/cjs/peer/CommissioningError.js +32 -0
- package/dist/cjs/peer/CommissioningError.js.map +6 -0
- package/dist/cjs/peer/ControllerCommissioner.d.ts +2 -3
- package/dist/cjs/peer/ControllerCommissioner.d.ts.map +1 -1
- package/dist/cjs/peer/ControllerCommissioner.js +20 -13
- package/dist/cjs/peer/ControllerCommissioner.js.map +2 -2
- package/dist/cjs/peer/ControllerCommissioningFlow.d.ts +7 -16
- package/dist/cjs/peer/ControllerCommissioningFlow.d.ts.map +1 -1
- package/dist/cjs/peer/ControllerCommissioningFlow.js +395 -178
- package/dist/cjs/peer/ControllerCommissioningFlow.js.map +1 -1
- package/dist/cjs/peer/ControllerDiscovery.d.ts +4 -0
- package/dist/cjs/peer/ControllerDiscovery.d.ts.map +1 -1
- package/dist/cjs/peer/ControllerDiscovery.js +6 -3
- package/dist/cjs/peer/ControllerDiscovery.js.map +1 -1
- package/dist/cjs/peer/InteractionQueue.d.ts +2 -2
- package/dist/cjs/peer/InteractionQueue.d.ts.map +1 -1
- package/dist/cjs/peer/InteractionQueue.js +1 -1
- package/dist/cjs/peer/InteractionQueue.js.map +1 -1
- package/dist/cjs/peer/PeerAddressStore.d.ts +0 -9
- package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
- package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
- package/dist/cjs/peer/PeerSet.d.ts +0 -2
- package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
- package/dist/cjs/peer/PeerSet.js +32 -18
- package/dist/cjs/peer/PeerSet.js.map +1 -1
- package/dist/cjs/peer/PhysicalDeviceProperties.js +1 -1
- package/dist/cjs/peer/PhysicalDeviceProperties.js.map +1 -1
- package/dist/cjs/peer/index.d.ts +1 -0
- package/dist/cjs/peer/index.d.ts.map +1 -1
- package/dist/cjs/peer/index.js +1 -0
- package/dist/cjs/peer/index.js.map +1 -1
- package/dist/cjs/protocol/DeviceCommissioner.d.ts.map +1 -1
- package/dist/cjs/protocol/DeviceCommissioner.js.map +1 -1
- package/dist/cjs/protocol/ExchangeManager.js +2 -2
- package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
- package/dist/cjs/protocol/ExchangeProvider.d.ts +13 -4
- package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
- package/dist/cjs/protocol/ExchangeProvider.js +5 -3
- package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
- package/dist/cjs/session/NodeSession.d.ts +5 -2
- package/dist/cjs/session/NodeSession.d.ts.map +1 -1
- package/dist/cjs/session/NodeSession.js +5 -4
- package/dist/cjs/session/NodeSession.js.map +1 -1
- package/dist/cjs/session/Session.d.ts +5 -3
- package/dist/cjs/session/Session.d.ts.map +1 -1
- package/dist/cjs/session/Session.js +8 -4
- package/dist/cjs/session/Session.js.map +1 -1
- package/dist/cjs/session/SessionManager.d.ts +8 -0
- package/dist/cjs/session/SessionManager.d.ts.map +1 -1
- package/dist/cjs/session/SessionManager.js +16 -2
- package/dist/cjs/session/SessionManager.js.map +1 -1
- package/dist/esm/action/client/ClientInteraction.d.ts +12 -5
- package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
- package/dist/esm/action/client/ClientInteraction.js +42 -16
- package/dist/esm/action/client/ClientInteraction.js.map +1 -1
- package/dist/esm/action/client/ClientRead.d.ts +10 -0
- package/dist/esm/action/client/ClientRead.d.ts.map +1 -0
- package/dist/esm/action/client/ClientRead.js +6 -0
- package/dist/esm/action/client/ClientRead.js.map +6 -0
- package/dist/esm/action/client/ClientRequest.d.ts +20 -0
- package/dist/esm/action/client/ClientRequest.d.ts.map +1 -0
- package/dist/esm/action/client/ClientRequest.js +6 -0
- package/dist/esm/action/client/ClientRequest.js.map +6 -0
- package/dist/esm/action/client/ClientWrite.d.ts +10 -0
- package/dist/esm/action/client/ClientWrite.d.ts.map +1 -0
- package/dist/esm/action/client/ClientWrite.js +6 -0
- package/dist/esm/action/client/ClientWrite.js.map +6 -0
- package/dist/esm/action/client/QueuedClientInteraction.d.ts +49 -0
- package/dist/esm/action/client/QueuedClientInteraction.d.ts.map +1 -0
- package/dist/esm/action/client/QueuedClientInteraction.js +140 -0
- package/dist/esm/action/client/QueuedClientInteraction.js.map +6 -0
- package/dist/esm/action/client/index.d.ts +4 -0
- package/dist/esm/action/client/index.d.ts.map +1 -1
- package/dist/esm/action/client/index.js +4 -0
- package/dist/esm/action/client/index.js.map +1 -1
- package/dist/esm/action/client/subscription/ClientSubscribe.d.ts +2 -1
- package/dist/esm/action/client/subscription/ClientSubscribe.d.ts.map +1 -1
- package/dist/esm/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
- package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js +14 -3
- package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
- package/dist/esm/action/client/subscription/SustainedSubscription.d.ts +1 -1
- package/dist/esm/action/client/subscription/SustainedSubscription.d.ts.map +1 -1
- package/dist/esm/action/client/subscription/SustainedSubscription.js +1 -4
- package/dist/esm/action/client/subscription/SustainedSubscription.js.map +1 -1
- package/dist/esm/action/request/Invoke.d.ts +7 -1
- package/dist/esm/action/request/Invoke.d.ts.map +1 -1
- package/dist/esm/action/request/Invoke.js +0 -3
- package/dist/esm/action/request/Invoke.js.map +1 -1
- package/dist/esm/action/request/Read.d.ts.map +1 -1
- package/dist/esm/action/request/Read.js +3 -2
- package/dist/esm/action/request/Read.js.map +1 -1
- package/dist/esm/action/request/Specifier.d.ts +1 -1
- package/dist/esm/action/request/Specifier.d.ts.map +1 -1
- package/dist/esm/action/request/Specifier.js +3 -0
- package/dist/esm/action/request/Specifier.js.map +1 -1
- package/dist/esm/action/request/Write.d.ts +1 -0
- package/dist/esm/action/request/Write.d.ts.map +1 -1
- package/dist/esm/action/request/Write.js +10 -2
- package/dist/esm/action/request/Write.js.map +1 -1
- package/dist/esm/action/response/ReadResult.d.ts +1 -1
- package/dist/esm/action/response/ReadResult.d.ts.map +1 -1
- package/dist/esm/cluster/client/ClusterClientTypes.d.ts +37 -8
- package/dist/esm/cluster/client/ClusterClientTypes.d.ts.map +1 -1
- package/dist/esm/cluster/client/index.d.ts +0 -3
- package/dist/esm/cluster/client/index.d.ts.map +1 -1
- package/dist/esm/cluster/client/index.js +0 -3
- package/dist/esm/cluster/client/index.js.map +1 -1
- package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionMessenger.js +4 -3
- package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
- package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -1
- package/dist/esm/interaction/SubscriptionClient.js +2 -1
- package/dist/esm/interaction/SubscriptionClient.js.map +1 -1
- package/dist/esm/interaction/index.d.ts +1 -1
- package/dist/esm/interaction/index.d.ts.map +1 -1
- package/dist/esm/interaction/index.js +1 -1
- package/dist/esm/peer/CommissioningError.d.ts +13 -0
- package/dist/esm/peer/CommissioningError.d.ts.map +1 -0
- package/dist/esm/peer/CommissioningError.js +12 -0
- package/dist/esm/peer/CommissioningError.js.map +6 -0
- package/dist/esm/peer/ControllerCommissioner.d.ts +2 -3
- package/dist/esm/peer/ControllerCommissioner.d.ts.map +1 -1
- package/dist/esm/peer/ControllerCommissioner.js +19 -13
- package/dist/esm/peer/ControllerCommissioner.js.map +2 -2
- package/dist/esm/peer/ControllerCommissioningFlow.d.ts +7 -16
- package/dist/esm/peer/ControllerCommissioningFlow.d.ts.map +1 -1
- package/dist/esm/peer/ControllerCommissioningFlow.js +380 -162
- package/dist/esm/peer/ControllerCommissioningFlow.js.map +1 -1
- package/dist/esm/peer/ControllerDiscovery.d.ts +4 -0
- package/dist/esm/peer/ControllerDiscovery.d.ts.map +1 -1
- package/dist/esm/peer/ControllerDiscovery.js +4 -1
- package/dist/esm/peer/ControllerDiscovery.js.map +1 -1
- package/dist/esm/peer/InteractionQueue.d.ts +2 -2
- package/dist/esm/peer/InteractionQueue.d.ts.map +1 -1
- package/dist/esm/peer/InteractionQueue.js +2 -2
- package/dist/esm/peer/InteractionQueue.js.map +1 -1
- package/dist/esm/peer/PeerAddressStore.d.ts +0 -9
- package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
- package/dist/esm/peer/PeerAddressStore.js.map +1 -1
- package/dist/esm/peer/PeerSet.d.ts +0 -2
- package/dist/esm/peer/PeerSet.d.ts.map +1 -1
- package/dist/esm/peer/PeerSet.js +32 -18
- package/dist/esm/peer/PeerSet.js.map +1 -1
- package/dist/esm/peer/PhysicalDeviceProperties.js +1 -1
- package/dist/esm/peer/PhysicalDeviceProperties.js.map +1 -1
- package/dist/esm/peer/index.d.ts +1 -0
- package/dist/esm/peer/index.d.ts.map +1 -1
- package/dist/esm/peer/index.js +1 -0
- package/dist/esm/peer/index.js.map +1 -1
- package/dist/esm/protocol/DeviceCommissioner.d.ts.map +1 -1
- package/dist/esm/protocol/DeviceCommissioner.js.map +1 -1
- package/dist/esm/protocol/ExchangeManager.js +2 -2
- package/dist/esm/protocol/ExchangeManager.js.map +1 -1
- package/dist/esm/protocol/ExchangeProvider.d.ts +13 -4
- package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
- package/dist/esm/protocol/ExchangeProvider.js +5 -3
- package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
- package/dist/esm/session/NodeSession.d.ts +5 -2
- package/dist/esm/session/NodeSession.d.ts.map +1 -1
- package/dist/esm/session/NodeSession.js +5 -4
- package/dist/esm/session/NodeSession.js.map +1 -1
- package/dist/esm/session/Session.d.ts +5 -3
- package/dist/esm/session/Session.d.ts.map +1 -1
- package/dist/esm/session/Session.js +8 -4
- package/dist/esm/session/Session.js.map +1 -1
- package/dist/esm/session/SessionManager.d.ts +8 -0
- package/dist/esm/session/SessionManager.d.ts.map +1 -1
- package/dist/esm/session/SessionManager.js +17 -2
- package/dist/esm/session/SessionManager.js.map +1 -1
- package/package.json +6 -6
- package/src/action/client/ClientInteraction.ts +58 -19
- package/src/action/client/ClientRead.ts +10 -0
- package/src/action/client/ClientRequest.ts +20 -0
- package/src/action/client/ClientWrite.ts +10 -0
- package/src/action/client/QueuedClientInteraction.ts +91 -0
- package/src/action/client/index.ts +4 -0
- package/src/action/client/subscription/ClientSubscribe.ts +2 -1
- package/src/action/client/subscription/ClientSubscriptionHandler.ts +14 -3
- package/src/action/client/subscription/SustainedSubscription.ts +6 -9
- package/src/action/request/Invoke.ts +11 -4
- package/src/action/request/Read.ts +3 -2
- package/src/action/request/Specifier.ts +4 -1
- package/src/action/request/Write.ts +11 -2
- package/src/action/response/ReadResult.ts +1 -1
- package/src/cluster/client/ClusterClientTypes.ts +47 -7
- package/src/cluster/client/index.ts +0 -3
- package/src/interaction/InteractionMessenger.ts +5 -4
- package/src/interaction/SubscriptionClient.ts +2 -1
- package/src/interaction/index.ts +1 -1
- package/src/peer/CommissioningError.ts +13 -0
- package/src/peer/ControllerCommissioner.ts +21 -13
- package/src/peer/ControllerCommissioningFlow.ts +418 -186
- package/src/peer/ControllerDiscovery.ts +4 -1
- package/src/peer/InteractionQueue.ts +2 -2
- package/src/peer/PeerAddressStore.ts +0 -9
- package/src/peer/PeerSet.ts +56 -23
- package/src/peer/PhysicalDeviceProperties.ts +1 -1
- package/src/peer/index.ts +1 -0
- package/src/protocol/DeviceCommissioner.ts +0 -1
- package/src/protocol/ExchangeManager.ts +2 -2
- package/src/protocol/ExchangeProvider.ts +9 -7
- package/src/session/NodeSession.ts +5 -4
- package/src/session/Session.ts +8 -4
- package/src/session/SessionManager.ts +19 -2
- package/dist/cjs/cluster/client/AttributeClient.d.ts +0 -75
- package/dist/cjs/cluster/client/AttributeClient.d.ts.map +0 -1
- package/dist/cjs/cluster/client/AttributeClient.js +0 -209
- package/dist/cjs/cluster/client/AttributeClient.js.map +0 -6
- package/dist/cjs/cluster/client/ClusterClient.d.ts +0 -11
- package/dist/cjs/cluster/client/ClusterClient.d.ts.map +0 -1
- package/dist/cjs/cluster/client/ClusterClient.js +0 -335
- package/dist/cjs/cluster/client/ClusterClient.js.map +0 -6
- package/dist/cjs/cluster/client/EventClient.d.ts +0 -33
- package/dist/cjs/cluster/client/EventClient.d.ts.map +0 -1
- package/dist/cjs/cluster/client/EventClient.js +0 -89
- package/dist/cjs/cluster/client/EventClient.js.map +0 -6
- package/dist/cjs/interaction/InteractionClient.d.ts +0 -375
- package/dist/cjs/interaction/InteractionClient.d.ts.map +0 -1
- package/dist/cjs/interaction/InteractionClient.js +0 -1046
- package/dist/cjs/interaction/InteractionClient.js.map +0 -6
- package/dist/esm/cluster/client/AttributeClient.d.ts +0 -75
- package/dist/esm/cluster/client/AttributeClient.d.ts.map +0 -1
- package/dist/esm/cluster/client/AttributeClient.js +0 -189
- package/dist/esm/cluster/client/AttributeClient.js.map +0 -6
- package/dist/esm/cluster/client/ClusterClient.d.ts +0 -11
- package/dist/esm/cluster/client/ClusterClient.d.ts.map +0 -1
- package/dist/esm/cluster/client/ClusterClient.js +0 -320
- package/dist/esm/cluster/client/ClusterClient.js.map +0 -6
- package/dist/esm/cluster/client/EventClient.d.ts +0 -33
- package/dist/esm/cluster/client/EventClient.d.ts.map +0 -1
- package/dist/esm/cluster/client/EventClient.js +0 -69
- package/dist/esm/cluster/client/EventClient.js.map +0 -6
- package/dist/esm/interaction/InteractionClient.d.ts +0 -375
- package/dist/esm/interaction/InteractionClient.d.ts.map +0 -1
- package/dist/esm/interaction/InteractionClient.js +0 -1047
- package/dist/esm/interaction/InteractionClient.js.map +0 -6
- package/src/cluster/client/AttributeClient.ts +0 -230
- package/src/cluster/client/ClusterClient.ts +0 -433
- package/src/cluster/client/EventClient.ts +0 -99
- 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
|
-
}
|