@matter/protocol 0.16.0-alpha.0-20251016-b56cf5683 → 0.16.0-alpha.0-20251018-dd1ea6a8a
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/dist/cjs/action/client/ClientInteraction.d.ts +10 -5
- package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
- package/dist/cjs/action/client/ClientInteraction.js +129 -18
- package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
- package/dist/cjs/action/request/Invoke.d.ts +36 -8
- package/dist/cjs/action/request/Invoke.d.ts.map +1 -1
- package/dist/cjs/action/request/Invoke.js +80 -15
- package/dist/cjs/action/request/Invoke.js.map +1 -1
- package/dist/cjs/action/request/Read.d.ts +1 -1
- package/dist/cjs/action/request/Read.d.ts.map +1 -1
- package/dist/cjs/action/request/Read.js +10 -2
- package/dist/cjs/action/request/Read.js.map +1 -1
- package/dist/cjs/action/request/Specifier.d.ts +16 -1
- package/dist/cjs/action/request/Specifier.d.ts.map +1 -1
- package/dist/cjs/action/request/Specifier.js +56 -1
- package/dist/cjs/action/request/Specifier.js.map +1 -1
- package/dist/cjs/action/request/Write.d.ts +5 -3
- package/dist/cjs/action/request/Write.d.ts.map +1 -1
- package/dist/cjs/action/request/Write.js +52 -12
- package/dist/cjs/action/request/Write.js.map +1 -1
- package/dist/cjs/action/response/InvokeResult.d.ts +6 -0
- package/dist/cjs/action/response/InvokeResult.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionClient.js +91 -74
- package/dist/cjs/interaction/InteractionClient.js.map +1 -1
- package/dist/cjs/interaction/InteractionMessenger.d.ts +3 -2
- package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionMessenger.js +10 -3
- package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
- package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
- package/dist/cjs/protocol/ExchangeManager.js +20 -17
- package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
- package/dist/cjs/protocol/MessageChannel.d.ts.map +1 -1
- package/dist/cjs/protocol/MessageChannel.js +1 -1
- package/dist/cjs/protocol/MessageChannel.js.map +1 -1
- package/dist/cjs/protocol/MessageExchange.d.ts +1 -0
- package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
- package/dist/cjs/protocol/MessageExchange.js +14 -4
- package/dist/cjs/protocol/MessageExchange.js.map +1 -1
- package/dist/cjs/protocol/ProtocolHandler.d.ts +7 -2
- package/dist/cjs/protocol/ProtocolHandler.d.ts.map +1 -1
- package/dist/cjs/securechannel/SecureChannelProtocol.d.ts +1 -1
- package/dist/cjs/securechannel/SecureChannelProtocol.d.ts.map +1 -1
- package/dist/cjs/securechannel/SecureChannelProtocol.js +2 -1
- package/dist/cjs/securechannel/SecureChannelProtocol.js.map +1 -1
- package/dist/cjs/session/GroupSession.d.ts +8 -1
- package/dist/cjs/session/GroupSession.d.ts.map +1 -1
- package/dist/cjs/session/GroupSession.js +10 -0
- package/dist/cjs/session/GroupSession.js.map +1 -1
- package/dist/cjs/session/NodeSession.d.ts +3 -1
- package/dist/cjs/session/NodeSession.d.ts.map +1 -1
- package/dist/cjs/session/NodeSession.js +13 -0
- package/dist/cjs/session/NodeSession.js.map +2 -2
- package/dist/cjs/session/SecureSession.d.ts +2 -0
- package/dist/cjs/session/SecureSession.d.ts.map +1 -1
- package/dist/cjs/session/SecureSession.js.map +1 -1
- package/dist/cjs/session/case/CaseClient.d.ts.map +1 -1
- package/dist/cjs/session/case/CaseClient.js +3 -15
- package/dist/cjs/session/case/CaseClient.js.map +1 -1
- package/dist/cjs/session/case/CaseServer.d.ts.map +1 -1
- package/dist/cjs/session/case/CaseServer.js +10 -18
- package/dist/cjs/session/case/CaseServer.js.map +1 -1
- package/dist/cjs/session/pase/PaseClient.js +1 -1
- package/dist/cjs/session/pase/PaseClient.js.map +1 -1
- package/dist/cjs/session/pase/PaseServer.d.ts.map +1 -1
- package/dist/cjs/session/pase/PaseServer.js +2 -2
- package/dist/cjs/session/pase/PaseServer.js.map +1 -1
- package/dist/esm/action/client/ClientInteraction.d.ts +10 -5
- package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
- package/dist/esm/action/client/ClientInteraction.js +140 -20
- package/dist/esm/action/client/ClientInteraction.js.map +1 -1
- package/dist/esm/action/request/Invoke.d.ts +36 -8
- package/dist/esm/action/request/Invoke.d.ts.map +1 -1
- package/dist/esm/action/request/Invoke.js +81 -16
- package/dist/esm/action/request/Invoke.js.map +1 -1
- package/dist/esm/action/request/Read.d.ts +1 -1
- package/dist/esm/action/request/Read.d.ts.map +1 -1
- package/dist/esm/action/request/Read.js +12 -4
- package/dist/esm/action/request/Read.js.map +1 -1
- package/dist/esm/action/request/Specifier.d.ts +16 -1
- package/dist/esm/action/request/Specifier.d.ts.map +1 -1
- package/dist/esm/action/request/Specifier.js +56 -1
- package/dist/esm/action/request/Specifier.js.map +1 -1
- package/dist/esm/action/request/Write.d.ts +5 -3
- package/dist/esm/action/request/Write.d.ts.map +1 -1
- package/dist/esm/action/request/Write.js +53 -13
- package/dist/esm/action/request/Write.js.map +1 -1
- package/dist/esm/action/response/InvokeResult.d.ts +6 -0
- package/dist/esm/action/response/InvokeResult.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionClient.js +92 -74
- package/dist/esm/interaction/InteractionClient.js.map +1 -1
- package/dist/esm/interaction/InteractionMessenger.d.ts +3 -2
- package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionMessenger.js +10 -3
- package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
- package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
- package/dist/esm/protocol/ExchangeManager.js +20 -17
- package/dist/esm/protocol/ExchangeManager.js.map +1 -1
- package/dist/esm/protocol/MessageChannel.d.ts.map +1 -1
- package/dist/esm/protocol/MessageChannel.js +2 -2
- package/dist/esm/protocol/MessageChannel.js.map +1 -1
- package/dist/esm/protocol/MessageExchange.d.ts +1 -0
- package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
- package/dist/esm/protocol/MessageExchange.js +14 -4
- package/dist/esm/protocol/MessageExchange.js.map +1 -1
- package/dist/esm/protocol/ProtocolHandler.d.ts +7 -2
- package/dist/esm/protocol/ProtocolHandler.d.ts.map +1 -1
- package/dist/esm/securechannel/SecureChannelProtocol.d.ts +1 -1
- package/dist/esm/securechannel/SecureChannelProtocol.d.ts.map +1 -1
- package/dist/esm/securechannel/SecureChannelProtocol.js +2 -1
- package/dist/esm/securechannel/SecureChannelProtocol.js.map +1 -1
- package/dist/esm/session/GroupSession.d.ts +8 -1
- package/dist/esm/session/GroupSession.d.ts.map +1 -1
- package/dist/esm/session/GroupSession.js +11 -1
- package/dist/esm/session/GroupSession.js.map +1 -1
- package/dist/esm/session/NodeSession.d.ts +3 -1
- package/dist/esm/session/NodeSession.d.ts.map +1 -1
- package/dist/esm/session/NodeSession.js +13 -0
- package/dist/esm/session/NodeSession.js.map +2 -2
- package/dist/esm/session/SecureSession.d.ts +2 -0
- package/dist/esm/session/SecureSession.d.ts.map +1 -1
- package/dist/esm/session/SecureSession.js.map +1 -1
- package/dist/esm/session/case/CaseClient.d.ts.map +1 -1
- package/dist/esm/session/case/CaseClient.js +5 -17
- package/dist/esm/session/case/CaseClient.js.map +1 -1
- package/dist/esm/session/case/CaseServer.d.ts.map +1 -1
- package/dist/esm/session/case/CaseServer.js +12 -20
- package/dist/esm/session/case/CaseServer.js.map +1 -1
- package/dist/esm/session/pase/PaseClient.js +1 -1
- package/dist/esm/session/pase/PaseClient.js.map +1 -1
- package/dist/esm/session/pase/PaseServer.d.ts.map +1 -1
- package/dist/esm/session/pase/PaseServer.js +3 -2
- package/dist/esm/session/pase/PaseServer.js.map +1 -1
- package/package.json +6 -6
- package/src/action/client/ClientInteraction.ts +181 -27
- package/src/action/request/Invoke.ts +149 -27
- package/src/action/request/Read.ts +27 -7
- package/src/action/request/Specifier.ts +80 -1
- package/src/action/request/Write.ts +71 -19
- package/src/action/response/InvokeResult.ts +8 -0
- package/src/interaction/InteractionClient.ts +135 -96
- package/src/interaction/InteractionMessenger.ts +15 -3
- package/src/protocol/ExchangeManager.ts +27 -29
- package/src/protocol/MessageChannel.ts +2 -2
- package/src/protocol/MessageExchange.ts +18 -4
- package/src/protocol/ProtocolHandler.ts +7 -2
- package/src/securechannel/SecureChannelProtocol.ts +5 -1
- package/src/session/GroupSession.ts +12 -1
- package/src/session/NodeSession.ts +21 -0
- package/src/session/SecureSession.ts +2 -0
- package/src/session/case/CaseClient.ts +3 -23
- package/src/session/case/CaseServer.ts +15 -20
- package/src/session/pase/PaseClient.ts +1 -1
- package/src/session/pase/PaseServer.ts +3 -2
|
@@ -4,23 +4,37 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { resolvePathForSpecifier } from "#action/index.js";
|
|
7
8
|
import { Interactable, InteractionSession } from "#action/Interactable.js";
|
|
8
|
-
import { Invoke } from "#action/request/Invoke.js";
|
|
9
|
+
import { ClientInvoke, Invoke } from "#action/request/Invoke.js";
|
|
9
10
|
import { Read } from "#action/request/Read.js";
|
|
10
11
|
import { Subscribe } from "#action/request/Subscribe.js";
|
|
11
12
|
import { Write } from "#action/request/Write.js";
|
|
12
|
-
import { InvokeResult } from "#action/response/InvokeResult.js";
|
|
13
|
+
import { DecodedInvokeResult, InvokeResult } from "#action/response/InvokeResult.js";
|
|
13
14
|
import { ReadResult } from "#action/response/ReadResult.js";
|
|
14
15
|
import { SubscribeResult } from "#action/response/SubscribeResult.js";
|
|
15
16
|
import { WriteResult } from "#action/response/WriteResult.js";
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
BasicSet,
|
|
19
|
+
Diagnostic,
|
|
20
|
+
Duration,
|
|
21
|
+
Environment,
|
|
22
|
+
Environmental,
|
|
23
|
+
ImplementationError,
|
|
24
|
+
isObject,
|
|
25
|
+
Logger,
|
|
26
|
+
PromiseQueue,
|
|
27
|
+
Seconds,
|
|
28
|
+
} from "#general";
|
|
17
29
|
import { InteractionClientMessenger, MessageType } from "#interaction/InteractionMessenger.js";
|
|
18
30
|
import { InteractionQueue } from "#peer/InteractionQueue.js";
|
|
19
31
|
import { ExchangeProvider } from "#protocol/ExchangeProvider.js";
|
|
20
|
-
import { TlvSubscribeResponse } from "#types";
|
|
32
|
+
import { Status, TlvNoResponse, TlvSubscribeResponse } from "#types";
|
|
21
33
|
import { ClientSubscriptions } from "./ClientSubscriptions.js";
|
|
22
34
|
import { InputChunk } from "./InputChunk.js";
|
|
23
35
|
|
|
36
|
+
const logger = Logger.get("ClientInteraction");
|
|
37
|
+
|
|
24
38
|
export interface ClientInteractionContext {
|
|
25
39
|
exchanges: ExchangeProvider;
|
|
26
40
|
subscriptions: ClientSubscriptions;
|
|
@@ -29,6 +43,9 @@ export interface ClientInteractionContext {
|
|
|
29
43
|
|
|
30
44
|
export const DEFAULT_MIN_INTERVAL_FLOOR = Seconds(1);
|
|
31
45
|
|
|
46
|
+
const DEFAULT_TIMED_REQUEST_TIMEOUT = Seconds(10);
|
|
47
|
+
const DEFAULT_MINIMUM_RESPONSE_TIMEOUT_WITH_FAILSAFE = Seconds(30);
|
|
48
|
+
|
|
32
49
|
/**
|
|
33
50
|
* Client-side implementation of the Matter protocol.
|
|
34
51
|
*/
|
|
@@ -74,37 +91,85 @@ export class ClientInteraction<SessionT extends InteractionSession = Interaction
|
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
async *read(request: Read, _session?: SessionT): ReadResult {
|
|
94
|
+
const readPathsCount = (request.attributeRequests?.length ?? 0) + (request.eventRequests?.length ?? 0);
|
|
95
|
+
if (readPathsCount > 9) {
|
|
96
|
+
logger.debug(
|
|
97
|
+
"Read interactions with more then 9 paths might be not allowed by the device. Consider splitting then into several read requests.",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.#begin(request);
|
|
102
|
+
|
|
103
|
+
let messenger: undefined | InteractionClientMessenger;
|
|
77
104
|
try {
|
|
78
|
-
this.#
|
|
79
|
-
const messenger = await InteractionClientMessenger.create(this.#exchanges);
|
|
105
|
+
messenger = await InteractionClientMessenger.create(this.#exchanges);
|
|
80
106
|
|
|
107
|
+
logger.debug("Read »", messenger.exchange.via, request);
|
|
81
108
|
await messenger.sendReadRequest(request);
|
|
82
109
|
|
|
110
|
+
let attributeReportCount = 0;
|
|
111
|
+
let eventReportCount = 0;
|
|
112
|
+
|
|
83
113
|
for await (const report of messenger.readDataReports()) {
|
|
114
|
+
attributeReportCount += report.attributeReports?.length ?? 0;
|
|
115
|
+
eventReportCount += report.eventReports?.length ?? 0;
|
|
84
116
|
yield InputChunk(report);
|
|
85
117
|
}
|
|
118
|
+
|
|
119
|
+
logger.debug(
|
|
120
|
+
"Read «",
|
|
121
|
+
messenger.exchange.via,
|
|
122
|
+
Diagnostic.weak(
|
|
123
|
+
attributeReportCount + eventReportCount === 0
|
|
124
|
+
? "(empty)"
|
|
125
|
+
: Diagnostic.dict({ attributes: attributeReportCount, events: eventReportCount }),
|
|
126
|
+
),
|
|
127
|
+
);
|
|
86
128
|
} finally {
|
|
129
|
+
await messenger?.close();
|
|
87
130
|
this.#end(request);
|
|
88
131
|
}
|
|
89
132
|
}
|
|
90
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Write chosen attributes remotely to the node.
|
|
136
|
+
* The returned attribute write status information is returned. No error is thrown for individual attribute write
|
|
137
|
+
* failures.
|
|
138
|
+
*/
|
|
91
139
|
async write<T extends Write>(request: T, _session?: SessionT): WriteResult<T> {
|
|
140
|
+
this.#begin(request);
|
|
141
|
+
|
|
92
142
|
let messenger: undefined | InteractionClientMessenger;
|
|
93
143
|
try {
|
|
94
|
-
this.#begin(request);
|
|
95
144
|
messenger = await InteractionClientMessenger.create(this.#exchanges);
|
|
145
|
+
|
|
146
|
+
if (request.timedRequest) {
|
|
147
|
+
await messenger.sendTimedRequest(request.timeout ?? DEFAULT_TIMED_REQUEST_TIMEOUT);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
logger.info("Write »", messenger.exchange.via, request);
|
|
151
|
+
|
|
96
152
|
const response = await messenger.sendWriteCommand(request);
|
|
97
153
|
if (request.suppressResponse) {
|
|
98
154
|
return undefined as Awaited<WriteResult<T>>;
|
|
99
155
|
}
|
|
100
156
|
if (!response || !response.writeResponses?.length) {
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
157
|
+
return [] as Awaited<WriteResult<T>>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let successCount = 0;
|
|
161
|
+
let failureCount = 0;
|
|
162
|
+
const result = response.writeResponses.map(
|
|
163
|
+
({
|
|
164
|
+
path: { nodeId, endpointId, clusterId, attributeId, listIndex },
|
|
165
|
+
status: { status, clusterStatus },
|
|
166
|
+
}) => {
|
|
167
|
+
if (status === Status.Success) {
|
|
168
|
+
successCount++;
|
|
169
|
+
} else {
|
|
170
|
+
failureCount++;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
108
173
|
kind: "attr-status",
|
|
109
174
|
path: {
|
|
110
175
|
nodeId,
|
|
@@ -115,21 +180,53 @@ export class ClientInteraction<SessionT extends InteractionSession = Interaction
|
|
|
115
180
|
},
|
|
116
181
|
status,
|
|
117
182
|
clusterStatus,
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
) as Awaited<WriteResult<T>>;
|
|
186
|
+
|
|
187
|
+
logger.info(
|
|
188
|
+
"Write «",
|
|
189
|
+
messenger.exchange.via,
|
|
190
|
+
Diagnostic.weak(
|
|
191
|
+
successCount + failureCount === 0
|
|
192
|
+
? "(empty)"
|
|
193
|
+
: Diagnostic.dict({ success: successCount, failure: failureCount }),
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
return result;
|
|
121
198
|
} finally {
|
|
122
199
|
await messenger?.close();
|
|
123
200
|
this.#end(request);
|
|
124
201
|
}
|
|
125
202
|
}
|
|
126
203
|
|
|
127
|
-
async *invoke(request:
|
|
128
|
-
|
|
204
|
+
async *invoke(request: ClientInvoke, _session?: SessionT): DecodedInvokeResult {
|
|
205
|
+
this.#begin(request);
|
|
206
|
+
|
|
207
|
+
let messenger: InteractionClientMessenger | undefined;
|
|
129
208
|
try {
|
|
130
|
-
this.#begin(request);
|
|
131
209
|
messenger = await InteractionClientMessenger.create(this.#exchanges);
|
|
132
|
-
|
|
210
|
+
|
|
211
|
+
if (request.timedRequest) {
|
|
212
|
+
await messenger.sendTimedRequest(request.timeout ?? DEFAULT_TIMED_REQUEST_TIMEOUT);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
logger.info(
|
|
216
|
+
"Invoke »",
|
|
217
|
+
messenger.exchange.via,
|
|
218
|
+
Diagnostic.asFlags({ suppressResponse: request.suppressResponse, timed: request.timedRequest }),
|
|
219
|
+
request,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const { expectedProcessingTime, useExtendedFailSafeMessageResponseTimeout } = request;
|
|
223
|
+
const result = await messenger.sendInvokeCommand(
|
|
224
|
+
request,
|
|
225
|
+
expectedProcessingTime ??
|
|
226
|
+
(useExtendedFailSafeMessageResponseTimeout
|
|
227
|
+
? DEFAULT_MINIMUM_RESPONSE_TIMEOUT_WITH_FAILSAFE
|
|
228
|
+
: undefined),
|
|
229
|
+
);
|
|
133
230
|
if (!request.suppressResponse) {
|
|
134
231
|
if (result && result.invokeResponses?.length) {
|
|
135
232
|
const chunk: InvokeResult.Chunk = result.invokeResponses
|
|
@@ -140,15 +237,38 @@ export class ClientInteraction<SessionT extends InteractionSession = Interaction
|
|
|
140
237
|
commandRef,
|
|
141
238
|
commandFields,
|
|
142
239
|
} = response.command;
|
|
143
|
-
const
|
|
240
|
+
const cmd = request.commands.get(commandRef);
|
|
241
|
+
if (!cmd) {
|
|
242
|
+
throw new ImplementationError(
|
|
243
|
+
`No response schema found for commandRef ${commandRef} (endpoint ${endpointId}, cluster ${clusterId}, command ${commandId})`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const responseSchema = Invoke.commandOf(cmd).responseSchema;
|
|
247
|
+
if (commandFields === undefined && responseSchema !== TlvNoResponse) {
|
|
248
|
+
throw new ImplementationError(
|
|
249
|
+
`No command fields found for commandRef ${commandRef} (endpoint ${endpointId}, cluster ${clusterId}, command ${commandId})`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const data =
|
|
254
|
+
commandFields === undefined ? undefined : responseSchema.decodeTlv(commandFields);
|
|
255
|
+
|
|
256
|
+
logger.info(
|
|
257
|
+
"Invoke «",
|
|
258
|
+
messenger!.exchange.via,
|
|
259
|
+
Diagnostic.strong(resolvePathForSpecifier(cmd)),
|
|
260
|
+
isObject(data) ? Diagnostic.dict(data) : Diagnostic.weak("(no payload)"),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const res: InvokeResult.DecodedCommandResponse = {
|
|
144
264
|
kind: "cmd-response",
|
|
145
265
|
path: {
|
|
146
266
|
endpointId: endpointId!,
|
|
147
|
-
clusterId
|
|
148
|
-
commandId
|
|
267
|
+
clusterId,
|
|
268
|
+
commandId,
|
|
149
269
|
},
|
|
150
270
|
commandRef,
|
|
151
|
-
data
|
|
271
|
+
data,
|
|
152
272
|
};
|
|
153
273
|
return res;
|
|
154
274
|
} else if (response.status !== undefined) {
|
|
@@ -187,12 +307,37 @@ export class ClientInteraction<SessionT extends InteractionSession = Interaction
|
|
|
187
307
|
}
|
|
188
308
|
|
|
189
309
|
async subscribe(request: Subscribe, _session?: SessionT): SubscribeResult {
|
|
310
|
+
const subscriptionPathsCount = (request.attributeRequests?.length ?? 0) + (request.eventRequests?.length ?? 0);
|
|
311
|
+
if (subscriptionPathsCount > 3) {
|
|
312
|
+
logger.debug("Subscribe interactions with more then 3 paths might be not allowed by the device.");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!request.keepSubscriptions) {
|
|
316
|
+
for (const subscription of this.#subscriptions) {
|
|
317
|
+
logger.debug(
|
|
318
|
+
`Removing subscription with ID ${subscription.subscriptionId} because new subscription replaces it`,
|
|
319
|
+
);
|
|
320
|
+
subscription.close();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
this.#begin(request);
|
|
325
|
+
|
|
190
326
|
let messenger: undefined | InteractionClientMessenger;
|
|
191
327
|
try {
|
|
192
|
-
this.#begin(request);
|
|
193
|
-
|
|
194
328
|
messenger = await InteractionClientMessenger.create(this.#exchanges);
|
|
195
329
|
|
|
330
|
+
logger.info(
|
|
331
|
+
"Subscribe »",
|
|
332
|
+
messenger.exchange.via,
|
|
333
|
+
Diagnostic.asFlags({ keepSubscriptions: request.keepSubscriptions }),
|
|
334
|
+
Diagnostic.dict({
|
|
335
|
+
min: Duration.format(request.minIntervalFloor),
|
|
336
|
+
max: Duration.format(request.maxIntervalCeiling),
|
|
337
|
+
}),
|
|
338
|
+
request,
|
|
339
|
+
);
|
|
340
|
+
|
|
196
341
|
await messenger.sendSubscribeRequest({
|
|
197
342
|
minIntervalFloorSeconds: Seconds.of(DEFAULT_MIN_INTERVAL_FLOOR),
|
|
198
343
|
maxIntervalCeilingSeconds: Seconds.of(DEFAULT_MIN_INTERVAL_FLOOR), // TODO use better max fallback
|
|
@@ -204,6 +349,15 @@ export class ClientInteraction<SessionT extends InteractionSession = Interaction
|
|
|
204
349
|
const responseMessage = await messenger.nextMessage(MessageType.SubscribeResponse);
|
|
205
350
|
const response = TlvSubscribeResponse.decode(responseMessage.payload);
|
|
206
351
|
|
|
352
|
+
logger.info(
|
|
353
|
+
"Subscription successful «",
|
|
354
|
+
messenger.exchange.via,
|
|
355
|
+
Diagnostic.dict({
|
|
356
|
+
subId: response.subscriptionId,
|
|
357
|
+
interval: Duration.format(Seconds(response.maxInterval)),
|
|
358
|
+
}),
|
|
359
|
+
);
|
|
360
|
+
|
|
207
361
|
return this.#subscriptions.add(request, response);
|
|
208
362
|
} finally {
|
|
209
363
|
await messenger?.close();
|
|
@@ -4,32 +4,46 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { Diagnostic, Duration, isObject } from "#general";
|
|
7
8
|
import { FALLBACK_INTERACTIONMODEL_REVISION } from "#session/Session.js";
|
|
8
|
-
import { ClusterType, CommandData, InvokeRequest,
|
|
9
|
+
import { ClusterType, CommandData, FabricIndex, InvokeRequest, ObjectSchema, TlvSchema, TypeFromSchema } from "#types";
|
|
9
10
|
import { MalformedRequestError } from "./MalformedRequestError.js";
|
|
10
|
-
import { Specifier } from "./Specifier.js";
|
|
11
|
+
import { resolvePathForSpecifier, Specifier } from "./Specifier.js";
|
|
12
|
+
|
|
13
|
+
export interface InvokeCommandData extends CommandData {
|
|
14
|
+
timed?: boolean;
|
|
15
|
+
}
|
|
11
16
|
|
|
12
17
|
export interface Invoke extends InvokeRequest {
|
|
13
18
|
/** Timeout only relevant for Client Interactions */
|
|
14
|
-
timeout?:
|
|
19
|
+
timeout?: Duration;
|
|
20
|
+
expectedProcessingTime?: Duration;
|
|
21
|
+
useExtendedFailSafeMessageResponseTimeout?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ClientInvoke extends Invoke {
|
|
25
|
+
commands: Map<number | undefined, Invoke.CommandRequest<any>>;
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
/**
|
|
18
29
|
* Request invocation of one or more commands.
|
|
19
30
|
*/
|
|
20
|
-
export function Invoke(options: Invoke.Definition):
|
|
31
|
+
export function Invoke(options: Invoke.Definition): ClientInvoke;
|
|
21
32
|
|
|
22
33
|
/**
|
|
23
34
|
* Request invocation multiple commands with defined options
|
|
24
35
|
*/
|
|
25
|
-
export function Invoke(options: Invoke.Definition, ...commands:
|
|
36
|
+
export function Invoke(options: Invoke.Definition, ...commands: Invoke.CommandRequest<any>[]): ClientInvoke;
|
|
26
37
|
|
|
27
38
|
/**
|
|
28
39
|
* Request invocation multiple commands as list of Commands with default options.
|
|
29
40
|
*/
|
|
30
|
-
export function Invoke(...commands:
|
|
41
|
+
export function Invoke(...commands: Invoke.CommandRequest<any>[]): ClientInvoke;
|
|
31
42
|
|
|
32
|
-
export function Invoke(
|
|
43
|
+
export function Invoke(
|
|
44
|
+
optionsOrData: Invoke.Definition | Invoke.CommandRequest<any>,
|
|
45
|
+
...commands: Invoke.CommandRequest<any>[]
|
|
46
|
+
): ClientInvoke {
|
|
33
47
|
if (optionsOrData === undefined) {
|
|
34
48
|
throw new MalformedRequestError(`Invocation requires at least one command`);
|
|
35
49
|
}
|
|
@@ -37,62 +51,141 @@ export function Invoke(optionsOrData: Invoke.Definition | CommandData, ...comman
|
|
|
37
51
|
let options;
|
|
38
52
|
if ("commands" in optionsOrData) {
|
|
39
53
|
options = optionsOrData;
|
|
54
|
+
commands = [...optionsOrData.commands, ...commands];
|
|
40
55
|
} else {
|
|
41
56
|
commands = [optionsOrData, ...commands];
|
|
42
57
|
options = {};
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
const {
|
|
46
|
-
commands: invokeRequests = [],
|
|
47
61
|
interactionModelRevision = FALLBACK_INTERACTIONMODEL_REVISION,
|
|
48
62
|
suppressResponse = false,
|
|
49
|
-
|
|
63
|
+
timeout,
|
|
64
|
+
expectedProcessingTime,
|
|
65
|
+
useExtendedFailSafeMessageResponseTimeout = false,
|
|
66
|
+
skipValidation = false,
|
|
50
67
|
} = options;
|
|
68
|
+
let timedRequest = !!options.timed || !!timeout;
|
|
51
69
|
|
|
52
|
-
if (commands
|
|
53
|
-
|
|
54
|
-
invokeRequests.push(entry);
|
|
55
|
-
}
|
|
70
|
+
if (!commands?.length) {
|
|
71
|
+
throw new MalformedRequestError(`Invocation requires at least one command`);
|
|
56
72
|
}
|
|
57
73
|
|
|
58
|
-
if (
|
|
59
|
-
|
|
74
|
+
if (commands.length > 1) {
|
|
75
|
+
const commandRefs = new Set<number>();
|
|
76
|
+
for (const { commandRef } of commands) {
|
|
77
|
+
if (commandRef === undefined) {
|
|
78
|
+
throw new MalformedRequestError(`CommandRef required when invoking multiple commands`);
|
|
79
|
+
}
|
|
80
|
+
if (commandRefs.has(commandRef)) {
|
|
81
|
+
throw new MalformedRequestError(`Duplicate commandRef ${commandRef} in multiple command invoke`);
|
|
82
|
+
}
|
|
83
|
+
commandRefs.add(commandRef);
|
|
84
|
+
}
|
|
60
85
|
}
|
|
61
86
|
|
|
87
|
+
const commandMap = new Map<number | undefined, Invoke.CommandRequest<any>>();
|
|
88
|
+
const invokeRequests: InvokeCommandData[] = commands.map(cmd => {
|
|
89
|
+
const cmdData = Invoke.Command(cmd, skipValidation);
|
|
90
|
+
timedRequest ||= !!cmdData.timed;
|
|
91
|
+
commandMap.set(cmdData.commandRef, cmd);
|
|
92
|
+
return cmdData;
|
|
93
|
+
});
|
|
94
|
+
|
|
62
95
|
return {
|
|
96
|
+
timedRequest,
|
|
97
|
+
timeout,
|
|
63
98
|
invokeRequests,
|
|
64
99
|
interactionModelRevision,
|
|
65
100
|
suppressResponse,
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
expectedProcessingTime,
|
|
102
|
+
useExtendedFailSafeMessageResponseTimeout,
|
|
103
|
+
|
|
104
|
+
// Additional meta-data for client side processing
|
|
105
|
+
commands: commandMap,
|
|
106
|
+
[Diagnostic.value]: () =>
|
|
107
|
+
Diagnostic.list(
|
|
108
|
+
commands.map(cmd => {
|
|
109
|
+
const { commandRef } = cmd;
|
|
110
|
+
const fields = "fields" in cmd ? cmd.fields : undefined;
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
Diagnostic.strong(resolvePathForSpecifier(cmd)),
|
|
114
|
+
"with",
|
|
115
|
+
isObject(fields) ? Diagnostic.dict(fields) : "(no payload)",
|
|
116
|
+
commandRef !== undefined ? `(ref ${commandRef})` : "",
|
|
117
|
+
];
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
} as ClientInvoke;
|
|
68
121
|
}
|
|
69
122
|
|
|
70
123
|
export namespace Invoke {
|
|
71
124
|
export interface Definition {
|
|
72
|
-
commands
|
|
125
|
+
/** List of commands to invoke */
|
|
126
|
+
commands: Invoke.CommandRequest<any>[];
|
|
127
|
+
|
|
128
|
+
/** Tell the server to not send a response */
|
|
73
129
|
suppressResponse?: boolean;
|
|
130
|
+
|
|
131
|
+
/** Whether this is sent as a timed request, if no timeout is specified a default is used */
|
|
74
132
|
timed?: boolean;
|
|
133
|
+
|
|
134
|
+
/** Timeout when sent as a timed request, if timed flag is not set it will be set automatically */
|
|
135
|
+
timeout?: Duration;
|
|
136
|
+
|
|
137
|
+
/** Interaction model revision to use, if not specified a default is used */
|
|
75
138
|
interactionModelRevision?: number;
|
|
139
|
+
|
|
140
|
+
/** Processing time of the command of the server assumed for this invoke. If not set a default is used */
|
|
141
|
+
expectedProcessingTime?: Duration;
|
|
142
|
+
|
|
143
|
+
/** Whether to use extended timeout for fail-safe messages. Overwrites the expectedProcessingTime if both are set */
|
|
144
|
+
useExtendedFailSafeMessageResponseTimeout?: boolean;
|
|
145
|
+
|
|
146
|
+
/** Whether to skip validation of command fields against schema */
|
|
147
|
+
skipValidation?: boolean;
|
|
76
148
|
}
|
|
77
149
|
|
|
78
|
-
export function Command<const C extends ClusterType>(
|
|
150
|
+
export function Command<const C extends ClusterType>(
|
|
151
|
+
request: Invoke.CommandRequest<C>,
|
|
152
|
+
skipValidation = false,
|
|
153
|
+
): InvokeCommandData {
|
|
79
154
|
const command = Invoke.commandOf(request);
|
|
155
|
+
const { requestSchema, requestId, timed } = command;
|
|
156
|
+
const { commandRef } = request;
|
|
157
|
+
|
|
158
|
+
let fields: any = "fields" in request ? request.fields : undefined;
|
|
159
|
+
if (requestSchema instanceof ObjectSchema) {
|
|
160
|
+
if (fields === undefined) {
|
|
161
|
+
// If developer did not provide a request object, create an empty one if it needs to be an object
|
|
162
|
+
// This can happen when all object properties are optional
|
|
163
|
+
fields = {};
|
|
164
|
+
}
|
|
165
|
+
if (requestSchema.isFabricScoped && fields.fabricIndex === undefined) {
|
|
166
|
+
fields.fabricIndex = FabricIndex.NO_FABRIC;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
80
169
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if ("fields" in request) {
|
|
84
|
-
commandFields = command.requestSchema.encodeTlv(request.fields, { forWriteInteraction: true });
|
|
170
|
+
if (!skipValidation) {
|
|
171
|
+
requestSchema.validate(fields);
|
|
85
172
|
}
|
|
86
173
|
|
|
87
|
-
const
|
|
174
|
+
const commandFields = requestSchema.encodeTlv(fields);
|
|
175
|
+
|
|
176
|
+
const result: InvokeCommandData = {
|
|
88
177
|
commandPath: {
|
|
178
|
+
// Endpoint id is added below if not wildcard
|
|
89
179
|
clusterId: Specifier.clusterFor(request.cluster).id,
|
|
90
|
-
commandId:
|
|
180
|
+
commandId: requestId,
|
|
91
181
|
},
|
|
92
182
|
commandFields,
|
|
183
|
+
timed: timed ?? false,
|
|
184
|
+
commandRef,
|
|
93
185
|
};
|
|
94
186
|
|
|
95
|
-
|
|
187
|
+
// Optional endpoint is handled by the Specifier utility, so we can just cast here
|
|
188
|
+
const endpointId = Specifier.endpointIdOf(request as ConcreteCommandRequest);
|
|
96
189
|
if (endpointId !== undefined) {
|
|
97
190
|
result.commandPath.endpointId = endpointId;
|
|
98
191
|
}
|
|
@@ -103,12 +196,41 @@ export namespace Invoke {
|
|
|
103
196
|
export type CommandRequest<
|
|
104
197
|
C extends Specifier.Cluster = Specifier.Cluster,
|
|
105
198
|
CMD extends Specifier.Command<Specifier.ClusterFor<C>> = Specifier.Command<Specifier.ClusterFor<C>>,
|
|
199
|
+
> = ConcreteCommandRequest<C, CMD> | WildcardCommandRequest<C, CMD>;
|
|
200
|
+
|
|
201
|
+
export type ConcreteCommandRequest<
|
|
202
|
+
C extends Specifier.Cluster = Specifier.Cluster,
|
|
203
|
+
CMD extends Specifier.Command<Specifier.ClusterFor<C>> = Specifier.Command<Specifier.ClusterFor<C>>,
|
|
204
|
+
> = WildcardCommandRequest<C, CMD> & { endpoint: Specifier.Endpoint };
|
|
205
|
+
|
|
206
|
+
export function ConcreteCommandRequest<const C extends ClusterType>(
|
|
207
|
+
data: Invoke.ConcreteCommandRequest<C>,
|
|
208
|
+
): Invoke.ConcreteCommandRequest<any> {
|
|
209
|
+
if (data.endpoint === undefined) {
|
|
210
|
+
throw new MalformedRequestError(`ConcreteCommandRequest requires an endpoint`);
|
|
211
|
+
}
|
|
212
|
+
return data;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export type WildcardCommandRequest<
|
|
216
|
+
C extends Specifier.Cluster = Specifier.Cluster,
|
|
217
|
+
CMD extends Specifier.Command<Specifier.ClusterFor<C>> = Specifier.Command<Specifier.ClusterFor<C>>,
|
|
106
218
|
> = {
|
|
107
|
-
endpoint?: Specifier.Endpoint;
|
|
108
219
|
cluster: C;
|
|
109
220
|
command: CMD;
|
|
221
|
+
commandName?: string;
|
|
222
|
+
commandRef?: number;
|
|
110
223
|
} & Fields<Specifier.CommandFor<Specifier.ClusterFor<C>, CMD>["requestSchema"]>;
|
|
111
224
|
|
|
225
|
+
export function WildcardCommandRequest<const C extends ClusterType>(
|
|
226
|
+
data: Invoke.WildcardCommandRequest<C>,
|
|
227
|
+
): Invoke.WildcardCommandRequest<any> {
|
|
228
|
+
if ("endpoint" in data && data.endpoint !== undefined) {
|
|
229
|
+
throw new MalformedRequestError(`ConcreteCommandRequest must not have an endpoint`);
|
|
230
|
+
}
|
|
231
|
+
return data;
|
|
232
|
+
}
|
|
233
|
+
|
|
112
234
|
export function commandOf<const R extends CommandRequest>(request: R): ClusterType.Command {
|
|
113
235
|
if (typeof request.command === "string") {
|
|
114
236
|
const cluster = Specifier.clusterFor(request.cluster);
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { camelize } from "#general";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
7
|
+
import { camelize, Diagnostic } from "#general";
|
|
8
|
+
import { Specification } from "#model";
|
|
9
|
+
import {
|
|
10
10
|
AttributePath,
|
|
11
11
|
ClusterType,
|
|
12
12
|
DataVersionFilter,
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
ReadRequest,
|
|
17
17
|
} from "#types";
|
|
18
18
|
import { MalformedRequestError } from "./MalformedRequestError.js";
|
|
19
|
-
import { Specifier } from "./Specifier.js";
|
|
19
|
+
import { resolvePathForSpecifier, Specifier } from "./Specifier.js";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* A read request formulated using Matter protocol semantics.
|
|
@@ -47,10 +47,30 @@ export function Read(optionsOrSelector: Read.Options | Read.Selector, ...selecto
|
|
|
47
47
|
}
|
|
48
48
|
let { attributes: attributeRequests, versionFilters, events: eventRequests, eventFilters } = options;
|
|
49
49
|
|
|
50
|
-
const result
|
|
50
|
+
const result = {
|
|
51
51
|
isFabricFiltered: options.fabricFilter ?? true,
|
|
52
|
-
interactionModelRevision: options.interactionModelRevision ??
|
|
53
|
-
|
|
52
|
+
interactionModelRevision: options.interactionModelRevision ?? Specification.INTERACTION_MODEL_REVISION,
|
|
53
|
+
[Diagnostic.value]: () =>
|
|
54
|
+
Diagnostic.dict({
|
|
55
|
+
attributes: attributeRequests?.length
|
|
56
|
+
? attributeRequests?.map(path => resolvePathForSpecifier(path)).join(", ")
|
|
57
|
+
: undefined,
|
|
58
|
+
events: eventRequests?.length
|
|
59
|
+
? eventRequests?.map(path => resolvePathForSpecifier(path)).join(", ")
|
|
60
|
+
: undefined,
|
|
61
|
+
dataVersionFilters: versionFilters?.length
|
|
62
|
+
? versionFilters
|
|
63
|
+
.map(
|
|
64
|
+
({ path: { endpointId, clusterId }, dataVersion }) =>
|
|
65
|
+
`${endpointId}/${clusterId}=${dataVersion}`,
|
|
66
|
+
)
|
|
67
|
+
.join(", ")
|
|
68
|
+
: undefined,
|
|
69
|
+
eventFilters: eventFilters?.length
|
|
70
|
+
? eventFilters.map(({ nodeId, eventMin }) => `${nodeId}=${eventMin}`).join(", ")
|
|
71
|
+
: undefined,
|
|
72
|
+
}),
|
|
73
|
+
} as Read;
|
|
54
74
|
|
|
55
75
|
for (const selector of selectors) {
|
|
56
76
|
reifySelector(selector);
|