@matter/protocol 0.15.4 → 0.15.5
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/ReadScope.d.ts +4 -0
- package/dist/cjs/action/client/ReadScope.d.ts.map +1 -1
- package/dist/cjs/action/client/ReadScope.js +2 -1
- package/dist/cjs/action/client/ReadScope.js.map +1 -1
- package/dist/cjs/fabric/Fabric.d.ts.map +1 -1
- package/dist/cjs/fabric/Fabric.js.map +1 -1
- package/dist/cjs/fabric/FabricAuthority.d.ts +0 -1
- package/dist/cjs/fabric/FabricAuthority.d.ts.map +1 -1
- package/dist/cjs/fabric/FabricAuthority.js +2 -3
- package/dist/cjs/fabric/FabricAuthority.js.map +1 -1
- package/dist/cjs/fabric/TestFabric.d.ts.map +1 -1
- package/dist/cjs/fabric/TestFabric.js +2 -1
- package/dist/cjs/fabric/TestFabric.js.map +1 -1
- package/dist/cjs/interaction/AttributeDataDecoder.d.ts +9 -7
- package/dist/cjs/interaction/AttributeDataDecoder.d.ts.map +1 -1
- package/dist/cjs/interaction/AttributeDataDecoder.js.map +1 -1
- package/dist/cjs/interaction/DecodedDataReport.d.ts +1 -0
- package/dist/cjs/interaction/DecodedDataReport.d.ts.map +1 -1
- package/dist/cjs/interaction/DecodedDataReport.js.map +1 -1
- package/dist/cjs/interaction/InteractionClient.d.ts +12 -2
- package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionClient.js +94 -67
- package/dist/cjs/interaction/InteractionClient.js.map +1 -1
- package/dist/cjs/interaction/InteractionMessenger.d.ts +10 -90
- package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionMessenger.js +98 -22
- package/dist/cjs/interaction/InteractionMessenger.js.map +2 -2
- package/dist/cjs/interaction/SubscriptionClient.d.ts +2 -2
- package/dist/cjs/interaction/SubscriptionClient.d.ts.map +1 -1
- package/dist/cjs/interaction/SubscriptionClient.js +1 -1
- package/dist/cjs/interaction/SubscriptionClient.js.map +1 -1
- package/dist/cjs/mdns/MdnsScanner.d.ts.map +1 -1
- package/dist/cjs/mdns/MdnsScanner.js +1 -2
- package/dist/cjs/mdns/MdnsScanner.js.map +1 -1
- package/dist/cjs/mdns/MdnsServer.js +1 -1
- package/dist/cjs/mdns/MdnsServer.js.map +1 -1
- package/dist/cjs/peer/PeerAddressStore.d.ts +3 -1
- package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
- package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
- package/dist/cjs/session/SessionManager.d.ts.map +1 -1
- package/dist/cjs/session/SessionManager.js +17 -9
- package/dist/cjs/session/SessionManager.js.map +1 -1
- package/dist/cjs/session/pase/PaseServer.d.ts.map +1 -1
- package/dist/cjs/session/pase/PaseServer.js +2 -1
- package/dist/cjs/session/pase/PaseServer.js.map +1 -1
- package/dist/esm/action/client/ReadScope.d.ts +4 -0
- package/dist/esm/action/client/ReadScope.d.ts.map +1 -1
- package/dist/esm/action/client/ReadScope.js +2 -1
- package/dist/esm/action/client/ReadScope.js.map +1 -1
- package/dist/esm/fabric/Fabric.d.ts.map +1 -1
- package/dist/esm/fabric/Fabric.js.map +1 -1
- package/dist/esm/fabric/FabricAuthority.d.ts +0 -1
- package/dist/esm/fabric/FabricAuthority.d.ts.map +1 -1
- package/dist/esm/fabric/FabricAuthority.js +2 -3
- package/dist/esm/fabric/FabricAuthority.js.map +1 -1
- package/dist/esm/fabric/TestFabric.d.ts.map +1 -1
- package/dist/esm/fabric/TestFabric.js +3 -2
- package/dist/esm/fabric/TestFabric.js.map +1 -1
- package/dist/esm/interaction/AttributeDataDecoder.d.ts +9 -7
- package/dist/esm/interaction/AttributeDataDecoder.d.ts.map +1 -1
- package/dist/esm/interaction/AttributeDataDecoder.js.map +1 -1
- package/dist/esm/interaction/DecodedDataReport.d.ts +1 -0
- package/dist/esm/interaction/DecodedDataReport.d.ts.map +1 -1
- package/dist/esm/interaction/DecodedDataReport.js.map +1 -1
- package/dist/esm/interaction/InteractionClient.d.ts +12 -2
- package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionClient.js +96 -68
- package/dist/esm/interaction/InteractionClient.js.map +1 -1
- package/dist/esm/interaction/InteractionMessenger.d.ts +10 -90
- package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionMessenger.js +98 -22
- package/dist/esm/interaction/InteractionMessenger.js.map +2 -2
- package/dist/esm/interaction/SubscriptionClient.d.ts +2 -2
- package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -1
- package/dist/esm/interaction/SubscriptionClient.js +1 -1
- package/dist/esm/interaction/SubscriptionClient.js.map +1 -1
- package/dist/esm/mdns/MdnsScanner.d.ts.map +1 -1
- package/dist/esm/mdns/MdnsScanner.js +1 -2
- package/dist/esm/mdns/MdnsScanner.js.map +1 -1
- package/dist/esm/mdns/MdnsServer.js +1 -1
- package/dist/esm/mdns/MdnsServer.js.map +1 -1
- package/dist/esm/peer/PeerAddressStore.d.ts +3 -1
- package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
- package/dist/esm/peer/PeerAddressStore.js.map +1 -1
- package/dist/esm/session/SessionManager.d.ts.map +1 -1
- package/dist/esm/session/SessionManager.js +19 -10
- package/dist/esm/session/SessionManager.js.map +1 -1
- package/dist/esm/session/pase/PaseServer.d.ts.map +1 -1
- package/dist/esm/session/pase/PaseServer.js +2 -1
- package/dist/esm/session/pase/PaseServer.js.map +1 -1
- package/package.json +6 -6
- package/src/action/client/ReadScope.ts +7 -0
- package/src/fabric/Fabric.ts +1 -1
- package/src/fabric/FabricAuthority.ts +2 -2
- package/src/fabric/TestFabric.ts +2 -1
- package/src/interaction/AttributeDataDecoder.ts +4 -1
- package/src/interaction/DecodedDataReport.ts +1 -0
- package/src/interaction/InteractionClient.ts +152 -77
- package/src/interaction/InteractionMessenger.ts +126 -22
- package/src/interaction/SubscriptionClient.ts +6 -5
- package/src/mdns/MdnsScanner.ts +1 -2
- package/src/mdns/MdnsServer.ts +2 -2
- package/src/peer/PeerAddressStore.ts +3 -1
- package/src/session/SessionManager.ts +35 -24
- package/src/session/pase/PaseServer.ts +2 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { ReadScope } from "#action/client/ReadScope.js";
|
|
7
8
|
import { AccessControl } from "#clusters/access-control";
|
|
8
9
|
import {
|
|
9
10
|
Diagnostic,
|
|
@@ -12,11 +13,13 @@ import {
|
|
|
12
13
|
ImplementationError,
|
|
13
14
|
Logger,
|
|
14
15
|
MatterFlowError,
|
|
16
|
+
MaybePromise,
|
|
15
17
|
PromiseQueue,
|
|
16
18
|
ServerAddressIp,
|
|
17
19
|
Timer,
|
|
18
20
|
UnexpectedDataError,
|
|
19
21
|
isDeepEqual,
|
|
22
|
+
serialize,
|
|
20
23
|
} from "#general";
|
|
21
24
|
import { Specification } from "#model";
|
|
22
25
|
import { PeerAddress, PeerAddressMap } from "#peer/PeerAddress.js";
|
|
@@ -40,6 +43,7 @@ import {
|
|
|
40
43
|
ResponseType,
|
|
41
44
|
StatusCode,
|
|
42
45
|
StatusResponseError,
|
|
46
|
+
SubscribeRequest,
|
|
43
47
|
TlvEventFilter,
|
|
44
48
|
TlvInvokeResponse,
|
|
45
49
|
TlvNoResponse,
|
|
@@ -55,7 +59,7 @@ import { MessageChannel } from "../protocol/MessageChannel.js";
|
|
|
55
59
|
import { DecodedAttributeReportStatus, DecodedAttributeReportValue } from "./AttributeDataDecoder.js";
|
|
56
60
|
import { DecodedDataReport } from "./DecodedDataReport.js";
|
|
57
61
|
import { DecodedEventData, DecodedEventReportStatus, DecodedEventReportValue } from "./EventDataDecoder.js";
|
|
58
|
-
import {
|
|
62
|
+
import { InteractionClientMessenger, ReadRequest } from "./InteractionMessenger.js";
|
|
59
63
|
import { RegisteredSubscription, SubscriptionClient } from "./SubscriptionClient.js";
|
|
60
64
|
|
|
61
65
|
const logger = Logger.get("InteractionClient");
|
|
@@ -219,6 +223,11 @@ export class InteractionClient {
|
|
|
219
223
|
enrichCachedAttributeData?: boolean;
|
|
220
224
|
isFabricFiltered?: boolean;
|
|
221
225
|
executeQueued?: boolean;
|
|
226
|
+
attributeChangeListener?: (
|
|
227
|
+
data: DecodedAttributeReportValue<any>,
|
|
228
|
+
valueChanged?: boolean,
|
|
229
|
+
oldValue?: any,
|
|
230
|
+
) => void;
|
|
222
231
|
} = {},
|
|
223
232
|
): Promise<DecodedAttributeReportValue<any>[]> {
|
|
224
233
|
return (
|
|
@@ -255,6 +264,11 @@ export class InteractionClient {
|
|
|
255
264
|
eventFilters?: TypeFromSchema<typeof TlvEventFilter>[];
|
|
256
265
|
isFabricFiltered?: boolean;
|
|
257
266
|
executeQueued?: boolean;
|
|
267
|
+
attributeChangeListener?: (
|
|
268
|
+
data: DecodedAttributeReportValue<any>,
|
|
269
|
+
valueChanged?: boolean,
|
|
270
|
+
oldValue?: any,
|
|
271
|
+
) => void;
|
|
258
272
|
} = {},
|
|
259
273
|
): Promise<{
|
|
260
274
|
attributeReports: DecodedAttributeReportValue<any>[];
|
|
@@ -274,6 +288,11 @@ export class InteractionClient {
|
|
|
274
288
|
enrichCachedAttributeData?: boolean;
|
|
275
289
|
isFabricFiltered?: boolean;
|
|
276
290
|
executeQueued?: boolean;
|
|
291
|
+
attributeChangeListener?: (
|
|
292
|
+
data: DecodedAttributeReportValue<any>,
|
|
293
|
+
valueChanged?: boolean,
|
|
294
|
+
oldValue?: any,
|
|
295
|
+
) => void;
|
|
277
296
|
} = {},
|
|
278
297
|
): Promise<DecodedAttributeReportValue<any>[]> {
|
|
279
298
|
return (await this.getMultipleAttributesAndEvents(options)).attributeReports;
|
|
@@ -286,6 +305,11 @@ export class InteractionClient {
|
|
|
286
305
|
enrichCachedAttributeData?: boolean;
|
|
287
306
|
isFabricFiltered?: boolean;
|
|
288
307
|
executeQueued?: boolean;
|
|
308
|
+
attributeChangeListener?: (
|
|
309
|
+
data: DecodedAttributeReportValue<any>,
|
|
310
|
+
valueChanged?: boolean,
|
|
311
|
+
oldValue?: any,
|
|
312
|
+
) => void;
|
|
289
313
|
} = {},
|
|
290
314
|
): Promise<{
|
|
291
315
|
attributeData: DecodedAttributeReportValue<any>[];
|
|
@@ -327,6 +351,11 @@ export class InteractionClient {
|
|
|
327
351
|
eventFilters?: TypeFromSchema<typeof TlvEventFilter>[];
|
|
328
352
|
isFabricFiltered?: boolean;
|
|
329
353
|
executeQueued?: boolean;
|
|
354
|
+
attributeChangeListener?: (
|
|
355
|
+
data: DecodedAttributeReportValue<any>,
|
|
356
|
+
valueChanged?: boolean,
|
|
357
|
+
oldValue?: any,
|
|
358
|
+
) => void;
|
|
330
359
|
} = {},
|
|
331
360
|
): Promise<DecodedDataReport> {
|
|
332
361
|
if (this.isGroupAddress) {
|
|
@@ -340,6 +369,8 @@ export class InteractionClient {
|
|
|
340
369
|
events: eventRequests,
|
|
341
370
|
enrichCachedAttributeData,
|
|
342
371
|
eventFilters,
|
|
372
|
+
isFabricFiltered = true,
|
|
373
|
+
attributeChangeListener,
|
|
343
374
|
} = options;
|
|
344
375
|
if (attributeRequests === undefined && eventRequests === undefined) {
|
|
345
376
|
throw new ImplementationError("When reading attributes and events, at least one must be specified.");
|
|
@@ -373,18 +404,21 @@ export class InteractionClient {
|
|
|
373
404
|
}
|
|
374
405
|
|
|
375
406
|
const result = await this.withMessenger(async messenger => {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
407
|
+
return await this.processReadRequest(
|
|
408
|
+
messenger,
|
|
409
|
+
{
|
|
410
|
+
attributeRequests,
|
|
411
|
+
dataVersionFilters: dataVersionFilters?.map(({ endpointId, clusterId, dataVersion }) => ({
|
|
412
|
+
path: { endpointId, clusterId },
|
|
413
|
+
dataVersion,
|
|
414
|
+
})),
|
|
415
|
+
eventRequests,
|
|
416
|
+
eventFilters,
|
|
417
|
+
isFabricFiltered,
|
|
418
|
+
interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
|
|
419
|
+
},
|
|
420
|
+
attributeChangeListener,
|
|
421
|
+
);
|
|
388
422
|
}, executeQueued);
|
|
389
423
|
|
|
390
424
|
if (dataVersionFilters !== undefined && dataVersionFilters.length > 0 && enrichCachedAttributeData) {
|
|
@@ -401,6 +435,11 @@ export class InteractionClient {
|
|
|
401
435
|
isFabricFiltered?: boolean;
|
|
402
436
|
requestFromRemote?: boolean;
|
|
403
437
|
executeQueued?: boolean;
|
|
438
|
+
attributeChangeListener?: (
|
|
439
|
+
data: DecodedAttributeReportValue<any>,
|
|
440
|
+
valueChanged?: boolean,
|
|
441
|
+
oldValue?: any,
|
|
442
|
+
) => void;
|
|
404
443
|
}): Promise<AttributeJsType<A> | undefined> {
|
|
405
444
|
const { requestFromRemote } = options;
|
|
406
445
|
const response = await this.getAttributeWithVersion({
|
|
@@ -445,12 +484,25 @@ export class InteractionClient {
|
|
|
445
484
|
isFabricFiltered?: boolean;
|
|
446
485
|
requestFromRemote?: boolean;
|
|
447
486
|
executeQueued?: boolean;
|
|
487
|
+
attributeChangeListener?: (
|
|
488
|
+
data: DecodedAttributeReportValue<any>,
|
|
489
|
+
valueChanged?: boolean,
|
|
490
|
+
oldValue?: any,
|
|
491
|
+
) => void;
|
|
448
492
|
}): Promise<{ value: AttributeJsType<A>; version: number } | undefined> {
|
|
449
493
|
if (this.isGroupAddress) {
|
|
450
494
|
throw new ImplementationError("Reading data from group addresses is not supported.");
|
|
451
495
|
}
|
|
452
496
|
|
|
453
|
-
const {
|
|
497
|
+
const {
|
|
498
|
+
endpointId,
|
|
499
|
+
clusterId,
|
|
500
|
+
attribute,
|
|
501
|
+
requestFromRemote,
|
|
502
|
+
isFabricFiltered,
|
|
503
|
+
executeQueued,
|
|
504
|
+
attributeChangeListener,
|
|
505
|
+
} = options;
|
|
454
506
|
const { id: attributeId } = attribute;
|
|
455
507
|
if (this.#nodeStore !== undefined) {
|
|
456
508
|
if (!requestFromRemote) {
|
|
@@ -468,6 +520,7 @@ export class InteractionClient {
|
|
|
468
520
|
attributes: [{ endpointId, clusterId, attributeId }],
|
|
469
521
|
isFabricFiltered,
|
|
470
522
|
executeQueued,
|
|
523
|
+
attributeChangeListener,
|
|
471
524
|
});
|
|
472
525
|
|
|
473
526
|
if (attributeReports.length === 0) {
|
|
@@ -501,6 +554,11 @@ export class InteractionClient {
|
|
|
501
554
|
private async processReadRequest(
|
|
502
555
|
messenger: InteractionClientMessenger,
|
|
503
556
|
request: ReadRequest,
|
|
557
|
+
attributeChangeListener?: (
|
|
558
|
+
data: DecodedAttributeReportValue<any>,
|
|
559
|
+
valueChanged?: boolean,
|
|
560
|
+
oldValue?: any,
|
|
561
|
+
) => void,
|
|
504
562
|
): Promise<DecodedDataReport> {
|
|
505
563
|
const { attributeRequests, eventRequests } = request;
|
|
506
564
|
logger.debug(
|
|
@@ -510,15 +568,19 @@ export class InteractionClient {
|
|
|
510
568
|
);
|
|
511
569
|
// Send read request and combine all (potentially chunked) responses
|
|
512
570
|
await messenger.sendReadRequest(request);
|
|
513
|
-
const
|
|
571
|
+
const scope = ReadScope(request);
|
|
572
|
+
const response = await messenger.readAggregateDataReport(chunk =>
|
|
573
|
+
this.processAttributeUpdates(scope, chunk, attributeChangeListener),
|
|
574
|
+
);
|
|
514
575
|
|
|
515
576
|
// Normalize and decode the response
|
|
516
|
-
const
|
|
517
|
-
const { attributeReports, attributeStatus, eventReports, eventStatus } = normalizedResult;
|
|
577
|
+
const { attributeReports, attributeStatus, eventReports, eventStatus } = response;
|
|
518
578
|
|
|
519
579
|
const logData = Array<string>();
|
|
520
580
|
if (attributeReports.length > 0) {
|
|
521
|
-
logData.push(
|
|
581
|
+
logData.push(
|
|
582
|
+
`attributes ${attributeReports.map(({ path, value }) => `${resolveAttributeName(path)}=${serialize(value)}`).join(", ")}`,
|
|
583
|
+
);
|
|
522
584
|
}
|
|
523
585
|
if (eventReports.length > 0) {
|
|
524
586
|
logData.push(`events ${eventReports.map(({ path }) => resolveEventName(path)).join(", ")}`);
|
|
@@ -532,7 +594,7 @@ export class InteractionClient {
|
|
|
532
594
|
logger.debug(
|
|
533
595
|
logData.length ? `Received read response with ${logData.join(", ")}` : "Received empty read response",
|
|
534
596
|
);
|
|
535
|
-
return
|
|
597
|
+
return response;
|
|
536
598
|
}
|
|
537
599
|
|
|
538
600
|
async setAttribute<T>(options: {
|
|
@@ -744,27 +806,30 @@ export class InteractionClient {
|
|
|
744
806
|
})}${knownDataVersion !== undefined ? ` (knownDataVersion=${knownDataVersion})` : ""} with minInterval=${minIntervalFloorSeconds}s/maxInterval=${maxIntervalCeilingSeconds}s`,
|
|
745
807
|
);
|
|
746
808
|
|
|
809
|
+
const request: SubscribeRequest = {
|
|
810
|
+
interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
|
|
811
|
+
attributeRequests: [{ endpointId, clusterId, attributeId }],
|
|
812
|
+
dataVersionFilters:
|
|
813
|
+
knownDataVersion !== undefined
|
|
814
|
+
? [{ path: { endpointId, clusterId }, dataVersion: knownDataVersion }]
|
|
815
|
+
: undefined,
|
|
816
|
+
keepSubscriptions,
|
|
817
|
+
minIntervalFloorSeconds,
|
|
818
|
+
maxIntervalCeilingSeconds,
|
|
819
|
+
isFabricFiltered,
|
|
820
|
+
};
|
|
821
|
+
const scope = ReadScope(request);
|
|
822
|
+
|
|
747
823
|
const {
|
|
748
824
|
subscribeResponse: { subscriptionId, maxInterval },
|
|
749
825
|
report,
|
|
750
826
|
maximumPeerResponseTimeMs,
|
|
751
827
|
} = await this.withMessenger<{
|
|
752
828
|
subscribeResponse: TypeFromSchema<typeof TlvSubscribeResponse>;
|
|
753
|
-
report:
|
|
829
|
+
report: DecodedDataReport;
|
|
754
830
|
maximumPeerResponseTimeMs: number;
|
|
755
831
|
}>(async messenger => {
|
|
756
|
-
await messenger.sendSubscribeRequest(
|
|
757
|
-
interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
|
|
758
|
-
attributeRequests: [{ endpointId, clusterId, attributeId }],
|
|
759
|
-
dataVersionFilters:
|
|
760
|
-
knownDataVersion !== undefined
|
|
761
|
-
? [{ path: { endpointId, clusterId }, dataVersion: knownDataVersion }]
|
|
762
|
-
: undefined,
|
|
763
|
-
keepSubscriptions,
|
|
764
|
-
minIntervalFloorSeconds,
|
|
765
|
-
maxIntervalCeilingSeconds,
|
|
766
|
-
isFabricFiltered,
|
|
767
|
-
});
|
|
832
|
+
await messenger.sendSubscribeRequest(request);
|
|
768
833
|
const { subscribeResponse, report } = await messenger.readAggregateSubscribeResponse();
|
|
769
834
|
return {
|
|
770
835
|
subscribeResponse,
|
|
@@ -773,14 +838,8 @@ export class InteractionClient {
|
|
|
773
838
|
};
|
|
774
839
|
}, executeQueued);
|
|
775
840
|
|
|
776
|
-
const subscriptionListener = async (dataReport:
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
if (!Array.isArray(dataReport.attributeReports) || !dataReport.attributeReports.length) {
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
const { attributeReports } = DecodedDataReport(dataReport);
|
|
841
|
+
const subscriptionListener = async (dataReport: DecodedDataReport) => {
|
|
842
|
+
const { attributeReports } = dataReport;
|
|
784
843
|
|
|
785
844
|
if (attributeReports.length === 0) {
|
|
786
845
|
throw new MatterFlowError("Subscription result reporting undefined/no value not specified");
|
|
@@ -792,9 +851,11 @@ export class InteractionClient {
|
|
|
792
851
|
if (value === undefined)
|
|
793
852
|
throw new MatterFlowError("Subscription result reporting undefined value not specified.");
|
|
794
853
|
|
|
795
|
-
await this.#nodeStore?.persistAttributes(
|
|
854
|
+
await this.#nodeStore?.persistAttributes(attributeReports, scope);
|
|
796
855
|
|
|
797
856
|
listener?.(value, version);
|
|
857
|
+
|
|
858
|
+
updateReceived?.();
|
|
798
859
|
};
|
|
799
860
|
|
|
800
861
|
await this.#registerSubscription(
|
|
@@ -811,7 +872,7 @@ export class InteractionClient {
|
|
|
811
872
|
return { maxInterval };
|
|
812
873
|
}
|
|
813
874
|
|
|
814
|
-
async #registerSubscription(subscription: RegisteredSubscription, initialReport:
|
|
875
|
+
async #registerSubscription(subscription: RegisteredSubscription, initialReport: DecodedDataReport) {
|
|
815
876
|
this.#ownSubscriptionIds.add(subscription.id);
|
|
816
877
|
this.#subscriptionClient.add(subscription);
|
|
817
878
|
await subscription.onData(initialReport);
|
|
@@ -862,7 +923,7 @@ export class InteractionClient {
|
|
|
862
923
|
maximumPeerResponseTimeMs,
|
|
863
924
|
} = await this.withMessenger<{
|
|
864
925
|
subscribeResponse: TypeFromSchema<typeof TlvSubscribeResponse>;
|
|
865
|
-
report:
|
|
926
|
+
report: DecodedDataReport;
|
|
866
927
|
maximumPeerResponseTimeMs: number;
|
|
867
928
|
}>(async messenger => {
|
|
868
929
|
await messenger.sendSubscribeRequest({
|
|
@@ -882,14 +943,8 @@ export class InteractionClient {
|
|
|
882
943
|
};
|
|
883
944
|
}, executeQueued);
|
|
884
945
|
|
|
885
|
-
const subscriptionListener = (dataReport:
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
if (!Array.isArray(dataReport.eventReports) || !dataReport.eventReports.length) {
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
const { eventReports } = DecodedDataReport(dataReport);
|
|
946
|
+
const subscriptionListener = (dataReport: DecodedDataReport) => {
|
|
947
|
+
const { eventReports } = dataReport;
|
|
893
948
|
|
|
894
949
|
if (eventReports.length === 0) {
|
|
895
950
|
throw new MatterFlowError("Received empty subscription result value.");
|
|
@@ -902,7 +957,10 @@ export class InteractionClient {
|
|
|
902
957
|
throw new MatterFlowError("Subscription result reporting undefined value not specified.");
|
|
903
958
|
|
|
904
959
|
events.forEach(event => listener?.(event));
|
|
960
|
+
|
|
961
|
+
updateReceived?.();
|
|
905
962
|
};
|
|
963
|
+
|
|
906
964
|
await this.#registerSubscription(
|
|
907
965
|
{
|
|
908
966
|
id: subscriptionId,
|
|
@@ -1024,30 +1082,36 @@ export class InteractionClient {
|
|
|
1024
1082
|
);
|
|
1025
1083
|
}
|
|
1026
1084
|
|
|
1085
|
+
const request: SubscribeRequest = {
|
|
1086
|
+
interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
|
|
1087
|
+
attributeRequests,
|
|
1088
|
+
eventRequests,
|
|
1089
|
+
keepSubscriptions,
|
|
1090
|
+
minIntervalFloorSeconds,
|
|
1091
|
+
maxIntervalCeilingSeconds,
|
|
1092
|
+
isFabricFiltered,
|
|
1093
|
+
eventFilters,
|
|
1094
|
+
dataVersionFilters: dataVersionFilters?.map(({ endpointId, clusterId, dataVersion }) => ({
|
|
1095
|
+
path: { endpointId, clusterId },
|
|
1096
|
+
dataVersion,
|
|
1097
|
+
})),
|
|
1098
|
+
};
|
|
1099
|
+
const scope = ReadScope(request);
|
|
1100
|
+
|
|
1101
|
+
let processNewAttributeChangesInListener = false;
|
|
1027
1102
|
const {
|
|
1028
1103
|
report,
|
|
1029
1104
|
subscribeResponse: { subscriptionId, maxInterval },
|
|
1030
1105
|
maximumPeerResponseTimeMs,
|
|
1031
1106
|
} = await this.withMessenger<{
|
|
1032
1107
|
subscribeResponse: TypeFromSchema<typeof TlvSubscribeResponse>;
|
|
1033
|
-
report:
|
|
1108
|
+
report: DecodedDataReport;
|
|
1034
1109
|
maximumPeerResponseTimeMs: number;
|
|
1035
1110
|
}>(async messenger => {
|
|
1036
|
-
await messenger.sendSubscribeRequest(
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
keepSubscriptions,
|
|
1041
|
-
minIntervalFloorSeconds,
|
|
1042
|
-
maxIntervalCeilingSeconds,
|
|
1043
|
-
isFabricFiltered,
|
|
1044
|
-
eventFilters,
|
|
1045
|
-
dataVersionFilters: dataVersionFilters?.map(({ endpointId, clusterId, dataVersion }) => ({
|
|
1046
|
-
path: { endpointId, clusterId },
|
|
1047
|
-
dataVersion,
|
|
1048
|
-
})),
|
|
1049
|
-
});
|
|
1050
|
-
const { subscribeResponse, report } = await messenger.readAggregateSubscribeResponse();
|
|
1111
|
+
await messenger.sendSubscribeRequest(request);
|
|
1112
|
+
const { subscribeResponse, report } = await messenger.readAggregateSubscribeResponse(attributeReports =>
|
|
1113
|
+
this.processAttributeUpdates(scope, attributeReports, attributeListener),
|
|
1114
|
+
);
|
|
1051
1115
|
return {
|
|
1052
1116
|
subscribeResponse,
|
|
1053
1117
|
report,
|
|
@@ -1092,33 +1156,33 @@ export class InteractionClient {
|
|
|
1092
1156
|
await this.#nodeStore?.updateLastEventNumber(maxEventNumber);
|
|
1093
1157
|
}
|
|
1094
1158
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1159
|
+
// Initial Data reports during seeding are handled directly
|
|
1160
|
+
if (processNewAttributeChangesInListener && attributeReports !== undefined) {
|
|
1161
|
+
await this.processAttributeUpdates(scope, attributeReports, attributeListener);
|
|
1097
1162
|
}
|
|
1098
1163
|
updateReceived?.();
|
|
1099
1164
|
};
|
|
1100
1165
|
|
|
1101
|
-
const seedReport = DecodedDataReport(report);
|
|
1102
|
-
|
|
1103
1166
|
await this.#registerSubscription(
|
|
1104
1167
|
{
|
|
1105
1168
|
id: subscriptionId,
|
|
1106
1169
|
maximumPeerResponseTimeMs,
|
|
1107
1170
|
maxIntervalS: maxInterval,
|
|
1108
1171
|
|
|
1109
|
-
onData: dataReport => subscriptionListener(
|
|
1172
|
+
onData: dataReport => subscriptionListener(dataReport),
|
|
1110
1173
|
|
|
1111
1174
|
onTimeout: updateTimeoutHandler,
|
|
1112
1175
|
},
|
|
1113
|
-
|
|
1176
|
+
report,
|
|
1114
1177
|
);
|
|
1178
|
+
processNewAttributeChangesInListener = true;
|
|
1115
1179
|
|
|
1116
1180
|
if (dataVersionFilters !== undefined && dataVersionFilters.length > 0 && enrichCachedAttributeData) {
|
|
1117
|
-
this.#enrichCachedAttributeData(
|
|
1181
|
+
this.#enrichCachedAttributeData(report.attributeReports, dataVersionFilters);
|
|
1118
1182
|
}
|
|
1119
1183
|
|
|
1120
1184
|
return {
|
|
1121
|
-
...
|
|
1185
|
+
...report,
|
|
1122
1186
|
maxInterval,
|
|
1123
1187
|
};
|
|
1124
1188
|
}
|
|
@@ -1127,6 +1191,7 @@ export class InteractionClient {
|
|
|
1127
1191
|
* Process changed attributes, detect changes and persist them to the node store
|
|
1128
1192
|
*/
|
|
1129
1193
|
async processAttributeUpdates(
|
|
1194
|
+
scope: ReadScope,
|
|
1130
1195
|
attributeReports: DecodedAttributeReportValue<any>[],
|
|
1131
1196
|
attributeListener?: (data: DecodedAttributeReportValue<any>, valueChanged?: boolean, oldValue?: any) => void,
|
|
1132
1197
|
) {
|
|
@@ -1142,14 +1207,14 @@ export class InteractionClient {
|
|
|
1142
1207
|
this.#nodeStore?.retrieveAttribute(endpointId, clusterId, attributeId) ?? {};
|
|
1143
1208
|
const changed = oldValue !== undefined ? !isDeepEqual(oldValue, value) : undefined;
|
|
1144
1209
|
if (changed !== false || version !== oldVersion) {
|
|
1145
|
-
await this.#nodeStore?.persistAttributes([data]);
|
|
1210
|
+
await this.#nodeStore?.persistAttributes([data], scope);
|
|
1146
1211
|
}
|
|
1147
1212
|
logger.debug(
|
|
1148
1213
|
`Received attribute update${changed ? "(value changed)" : ""}: ${resolveAttributeName({
|
|
1149
1214
|
endpointId,
|
|
1150
1215
|
clusterId,
|
|
1151
1216
|
attributeId,
|
|
1152
|
-
})} = ${
|
|
1217
|
+
})} = ${serialize(value)} (version=${version})`,
|
|
1153
1218
|
);
|
|
1154
1219
|
|
|
1155
1220
|
attributeListener?.(data, changed, oldValue);
|
|
@@ -1468,4 +1533,14 @@ export class InteractionClient {
|
|
|
1468
1533
|
get maxKnownEventNumber() {
|
|
1469
1534
|
return this.#nodeStore?.maxEventNumber;
|
|
1470
1535
|
}
|
|
1536
|
+
|
|
1537
|
+
cleanupAttributeData(endpointId: EndpointNumber, clusterIds?: ClusterId[]): MaybePromise<void> {
|
|
1538
|
+
return this.#nodeStore?.cleanupAttributeData(endpointId, clusterIds);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
getAllCachedClusterData() {
|
|
1542
|
+
const result = new Array<DecodedAttributeReportValue<any>>();
|
|
1543
|
+
this.#enrichCachedAttributeData(result, this.getCachedClusterDataVersions());
|
|
1544
|
+
return result;
|
|
1545
|
+
}
|
|
1471
1546
|
}
|
|
@@ -13,14 +13,20 @@ import {
|
|
|
13
13
|
NoResponseTimeoutError,
|
|
14
14
|
UnexpectedDataError,
|
|
15
15
|
} from "#general";
|
|
16
|
+
import { DecodedAttributeReportValue } from "#interaction/AttributeDataDecoder.js";
|
|
17
|
+
import { DecodedDataReport } from "#interaction/DecodedDataReport.js";
|
|
16
18
|
import { Specification } from "#model";
|
|
17
19
|
import { ChannelNotConnectedError } from "#protocol/MessageChannel.js";
|
|
18
20
|
import {
|
|
21
|
+
AttributeId,
|
|
22
|
+
ClusterId,
|
|
23
|
+
EndpointNumber,
|
|
19
24
|
ReceivedStatusResponseError,
|
|
20
25
|
Status,
|
|
21
26
|
StatusCode,
|
|
22
27
|
StatusResponseError,
|
|
23
28
|
TlvAny,
|
|
29
|
+
TlvAttributeReport,
|
|
24
30
|
TlvDataReport,
|
|
25
31
|
TlvDataReportForSend,
|
|
26
32
|
TlvDataVersionFilter,
|
|
@@ -796,8 +802,105 @@ export class IncomingInteractionClientMessenger extends InteractionMessenger {
|
|
|
796
802
|
return message;
|
|
797
803
|
}
|
|
798
804
|
|
|
799
|
-
|
|
800
|
-
|
|
805
|
+
/**
|
|
806
|
+
* Reads data report stream and aggregates them into a single report.
|
|
807
|
+
* Additionally, a callback can be provided that is called for each cluster chunk received.
|
|
808
|
+
*/
|
|
809
|
+
async readAggregateDataReport(
|
|
810
|
+
chunkListener?: (chunk: DecodedAttributeReportValue<any>[]) => Promise<void>,
|
|
811
|
+
expectedSubscriptionIds?: number[],
|
|
812
|
+
): Promise<DecodedDataReport> {
|
|
813
|
+
let result: DecodedDataReport | undefined = undefined;
|
|
814
|
+
let currentEndpointId: EndpointNumber | undefined = undefined;
|
|
815
|
+
let currentClusterId: ClusterId | undefined = undefined;
|
|
816
|
+
const currentClusterChunk = new Array<DecodedAttributeReportValue<any>>();
|
|
817
|
+
let pendingAttributeReports: TypeFromSchema<typeof TlvAttributeReport>[] | undefined = undefined;
|
|
818
|
+
|
|
819
|
+
const handleAttributeReportEntries = (
|
|
820
|
+
attributeReports: TypeFromSchema<typeof TlvAttributeReport>[] | undefined,
|
|
821
|
+
previousPendingAttributeReports: TypeFromSchema<typeof TlvAttributeReport>[] | undefined,
|
|
822
|
+
) => {
|
|
823
|
+
if (previousPendingAttributeReports?.length) {
|
|
824
|
+
attributeReports = attributeReports ?? [];
|
|
825
|
+
attributeReports.unshift(...previousPendingAttributeReports);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
let lastAttributeDataIndex = -1;
|
|
829
|
+
if (attributeReports?.length) {
|
|
830
|
+
let lastEndpointId: EndpointNumber | undefined = undefined;
|
|
831
|
+
let lastClusterId: ClusterId | undefined = undefined;
|
|
832
|
+
let lastAttributeId: AttributeId | undefined = undefined;
|
|
833
|
+
for (let i = attributeReports.length - 1; i >= 0; i--) {
|
|
834
|
+
const attributeReport = attributeReports[i];
|
|
835
|
+
if (attributeReport.attributeData === undefined) {
|
|
836
|
+
break; // No data report, so nothing more to search for
|
|
837
|
+
}
|
|
838
|
+
const {
|
|
839
|
+
path: { endpointId, clusterId, attributeId },
|
|
840
|
+
} = attributeReport.attributeData;
|
|
841
|
+
if (lastEndpointId === undefined && lastClusterId === undefined && lastAttributeId === undefined) {
|
|
842
|
+
// Remember path of the last attribute data entry and check if previous entries match
|
|
843
|
+
lastEndpointId = endpointId;
|
|
844
|
+
lastClusterId = clusterId;
|
|
845
|
+
lastAttributeId = attributeId;
|
|
846
|
+
}
|
|
847
|
+
if (
|
|
848
|
+
endpointId === lastEndpointId &&
|
|
849
|
+
clusterId === lastClusterId &&
|
|
850
|
+
attributeId === lastAttributeId
|
|
851
|
+
) {
|
|
852
|
+
lastAttributeDataIndex = i;
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
break; // We found an attribute that does not match the last one, so we are done
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (lastAttributeDataIndex > 0) {
|
|
859
|
+
return attributeReports.splice(lastAttributeDataIndex);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
const processDecodedReport = async (
|
|
865
|
+
decodedReport: DecodedDataReport,
|
|
866
|
+
result: DecodedDataReport | undefined,
|
|
867
|
+
) => {
|
|
868
|
+
if (!result) {
|
|
869
|
+
result = decodedReport;
|
|
870
|
+
} else {
|
|
871
|
+
if (!result.attributeReports) {
|
|
872
|
+
result.attributeReports = decodedReport.attributeReports;
|
|
873
|
+
} else {
|
|
874
|
+
result.attributeReports.push(...decodedReport.attributeReports);
|
|
875
|
+
}
|
|
876
|
+
if (Array.isArray(decodedReport.eventReports)) {
|
|
877
|
+
if (!result.eventReports) {
|
|
878
|
+
result.eventReports = decodedReport.eventReports;
|
|
879
|
+
} else {
|
|
880
|
+
result.eventReports.push(...decodedReport.eventReports);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (chunkListener !== undefined && decodedReport.attributeReports) {
|
|
886
|
+
for (const data of decodedReport.attributeReports) {
|
|
887
|
+
const {
|
|
888
|
+
path: { endpointId, clusterId },
|
|
889
|
+
} = data;
|
|
890
|
+
if (currentEndpointId !== endpointId || currentClusterId !== clusterId) {
|
|
891
|
+
// We switched the cluster, so we need to send the current chunk first
|
|
892
|
+
if (currentClusterChunk.length > 0) {
|
|
893
|
+
await chunkListener(currentClusterChunk);
|
|
894
|
+
currentClusterChunk.length = 0;
|
|
895
|
+
}
|
|
896
|
+
currentEndpointId = endpointId;
|
|
897
|
+
currentClusterId = clusterId;
|
|
898
|
+
}
|
|
899
|
+
currentClusterChunk.push(data);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return result;
|
|
903
|
+
};
|
|
801
904
|
|
|
802
905
|
for await (const report of this.readDataReports()) {
|
|
803
906
|
if (expectedSubscriptionIds !== undefined) {
|
|
@@ -820,24 +923,25 @@ export class IncomingInteractionClientMessenger extends InteractionMessenger {
|
|
|
820
923
|
throw new UnexpectedDataError(`Invalid subscription ID ${report.subscriptionId} received`);
|
|
821
924
|
}
|
|
822
925
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
926
|
+
report.attributeReports = report.attributeReports ?? [];
|
|
927
|
+
pendingAttributeReports = handleAttributeReportEntries(report.attributeReports, pendingAttributeReports);
|
|
928
|
+
|
|
929
|
+
result = await processDecodedReport(DecodedDataReport(report), result);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (pendingAttributeReports?.length && result !== undefined) {
|
|
933
|
+
result = await processDecodedReport(
|
|
934
|
+
DecodedDataReport({
|
|
935
|
+
interactionModelRevision: result.interactionModelRevision,
|
|
936
|
+
attributeReports: pendingAttributeReports,
|
|
937
|
+
}),
|
|
938
|
+
result,
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (chunkListener !== undefined && currentClusterChunk.length > 0) {
|
|
943
|
+
await chunkListener(currentClusterChunk);
|
|
944
|
+
currentClusterChunk.length = 0;
|
|
841
945
|
}
|
|
842
946
|
|
|
843
947
|
if (result === undefined) {
|
|
@@ -1007,8 +1111,8 @@ export class InteractionClientMessenger extends IncomingInteractionClientMesseng
|
|
|
1007
1111
|
await this.send(MessageType.SubscribeRequest, request);
|
|
1008
1112
|
}
|
|
1009
1113
|
|
|
1010
|
-
async readAggregateSubscribeResponse() {
|
|
1011
|
-
const report = await this.readAggregateDataReport();
|
|
1114
|
+
async readAggregateSubscribeResponse(chunkListener?: (chunk: DecodedAttributeReportValue<any>[]) => Promise<void>) {
|
|
1115
|
+
const report = await this.readAggregateDataReport(chunkListener);
|
|
1012
1116
|
const { subscriptionId } = report;
|
|
1013
1117
|
|
|
1014
1118
|
if (subscriptionId === undefined) {
|