@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.
Files changed (105) hide show
  1. package/dist/cjs/action/client/ReadScope.d.ts +4 -0
  2. package/dist/cjs/action/client/ReadScope.d.ts.map +1 -1
  3. package/dist/cjs/action/client/ReadScope.js +2 -1
  4. package/dist/cjs/action/client/ReadScope.js.map +1 -1
  5. package/dist/cjs/fabric/Fabric.d.ts.map +1 -1
  6. package/dist/cjs/fabric/Fabric.js.map +1 -1
  7. package/dist/cjs/fabric/FabricAuthority.d.ts +0 -1
  8. package/dist/cjs/fabric/FabricAuthority.d.ts.map +1 -1
  9. package/dist/cjs/fabric/FabricAuthority.js +2 -3
  10. package/dist/cjs/fabric/FabricAuthority.js.map +1 -1
  11. package/dist/cjs/fabric/TestFabric.d.ts.map +1 -1
  12. package/dist/cjs/fabric/TestFabric.js +2 -1
  13. package/dist/cjs/fabric/TestFabric.js.map +1 -1
  14. package/dist/cjs/interaction/AttributeDataDecoder.d.ts +9 -7
  15. package/dist/cjs/interaction/AttributeDataDecoder.d.ts.map +1 -1
  16. package/dist/cjs/interaction/AttributeDataDecoder.js.map +1 -1
  17. package/dist/cjs/interaction/DecodedDataReport.d.ts +1 -0
  18. package/dist/cjs/interaction/DecodedDataReport.d.ts.map +1 -1
  19. package/dist/cjs/interaction/DecodedDataReport.js.map +1 -1
  20. package/dist/cjs/interaction/InteractionClient.d.ts +12 -2
  21. package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
  22. package/dist/cjs/interaction/InteractionClient.js +94 -67
  23. package/dist/cjs/interaction/InteractionClient.js.map +1 -1
  24. package/dist/cjs/interaction/InteractionMessenger.d.ts +10 -90
  25. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  26. package/dist/cjs/interaction/InteractionMessenger.js +98 -22
  27. package/dist/cjs/interaction/InteractionMessenger.js.map +2 -2
  28. package/dist/cjs/interaction/SubscriptionClient.d.ts +2 -2
  29. package/dist/cjs/interaction/SubscriptionClient.d.ts.map +1 -1
  30. package/dist/cjs/interaction/SubscriptionClient.js +1 -1
  31. package/dist/cjs/interaction/SubscriptionClient.js.map +1 -1
  32. package/dist/cjs/mdns/MdnsScanner.d.ts.map +1 -1
  33. package/dist/cjs/mdns/MdnsScanner.js +1 -2
  34. package/dist/cjs/mdns/MdnsScanner.js.map +1 -1
  35. package/dist/cjs/mdns/MdnsServer.js +1 -1
  36. package/dist/cjs/mdns/MdnsServer.js.map +1 -1
  37. package/dist/cjs/peer/PeerAddressStore.d.ts +3 -1
  38. package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
  39. package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
  40. package/dist/cjs/session/SessionManager.d.ts.map +1 -1
  41. package/dist/cjs/session/SessionManager.js +17 -9
  42. package/dist/cjs/session/SessionManager.js.map +1 -1
  43. package/dist/cjs/session/pase/PaseServer.d.ts.map +1 -1
  44. package/dist/cjs/session/pase/PaseServer.js +2 -1
  45. package/dist/cjs/session/pase/PaseServer.js.map +1 -1
  46. package/dist/esm/action/client/ReadScope.d.ts +4 -0
  47. package/dist/esm/action/client/ReadScope.d.ts.map +1 -1
  48. package/dist/esm/action/client/ReadScope.js +2 -1
  49. package/dist/esm/action/client/ReadScope.js.map +1 -1
  50. package/dist/esm/fabric/Fabric.d.ts.map +1 -1
  51. package/dist/esm/fabric/Fabric.js.map +1 -1
  52. package/dist/esm/fabric/FabricAuthority.d.ts +0 -1
  53. package/dist/esm/fabric/FabricAuthority.d.ts.map +1 -1
  54. package/dist/esm/fabric/FabricAuthority.js +2 -3
  55. package/dist/esm/fabric/FabricAuthority.js.map +1 -1
  56. package/dist/esm/fabric/TestFabric.d.ts.map +1 -1
  57. package/dist/esm/fabric/TestFabric.js +3 -2
  58. package/dist/esm/fabric/TestFabric.js.map +1 -1
  59. package/dist/esm/interaction/AttributeDataDecoder.d.ts +9 -7
  60. package/dist/esm/interaction/AttributeDataDecoder.d.ts.map +1 -1
  61. package/dist/esm/interaction/AttributeDataDecoder.js.map +1 -1
  62. package/dist/esm/interaction/DecodedDataReport.d.ts +1 -0
  63. package/dist/esm/interaction/DecodedDataReport.d.ts.map +1 -1
  64. package/dist/esm/interaction/DecodedDataReport.js.map +1 -1
  65. package/dist/esm/interaction/InteractionClient.d.ts +12 -2
  66. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  67. package/dist/esm/interaction/InteractionClient.js +96 -68
  68. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  69. package/dist/esm/interaction/InteractionMessenger.d.ts +10 -90
  70. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  71. package/dist/esm/interaction/InteractionMessenger.js +98 -22
  72. package/dist/esm/interaction/InteractionMessenger.js.map +2 -2
  73. package/dist/esm/interaction/SubscriptionClient.d.ts +2 -2
  74. package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -1
  75. package/dist/esm/interaction/SubscriptionClient.js +1 -1
  76. package/dist/esm/interaction/SubscriptionClient.js.map +1 -1
  77. package/dist/esm/mdns/MdnsScanner.d.ts.map +1 -1
  78. package/dist/esm/mdns/MdnsScanner.js +1 -2
  79. package/dist/esm/mdns/MdnsScanner.js.map +1 -1
  80. package/dist/esm/mdns/MdnsServer.js +1 -1
  81. package/dist/esm/mdns/MdnsServer.js.map +1 -1
  82. package/dist/esm/peer/PeerAddressStore.d.ts +3 -1
  83. package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
  84. package/dist/esm/peer/PeerAddressStore.js.map +1 -1
  85. package/dist/esm/session/SessionManager.d.ts.map +1 -1
  86. package/dist/esm/session/SessionManager.js +19 -10
  87. package/dist/esm/session/SessionManager.js.map +1 -1
  88. package/dist/esm/session/pase/PaseServer.d.ts.map +1 -1
  89. package/dist/esm/session/pase/PaseServer.js +2 -1
  90. package/dist/esm/session/pase/PaseServer.js.map +1 -1
  91. package/package.json +6 -6
  92. package/src/action/client/ReadScope.ts +7 -0
  93. package/src/fabric/Fabric.ts +1 -1
  94. package/src/fabric/FabricAuthority.ts +2 -2
  95. package/src/fabric/TestFabric.ts +2 -1
  96. package/src/interaction/AttributeDataDecoder.ts +4 -1
  97. package/src/interaction/DecodedDataReport.ts +1 -0
  98. package/src/interaction/InteractionClient.ts +152 -77
  99. package/src/interaction/InteractionMessenger.ts +126 -22
  100. package/src/interaction/SubscriptionClient.ts +6 -5
  101. package/src/mdns/MdnsScanner.ts +1 -2
  102. package/src/mdns/MdnsServer.ts +2 -2
  103. package/src/peer/PeerAddressStore.ts +3 -1
  104. package/src/session/SessionManager.ts +35 -24
  105. 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 { DataReport, InteractionClientMessenger, ReadRequest } from "./InteractionMessenger.js";
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
- const { isFabricFiltered = true } = options;
377
- return await this.processReadRequest(messenger, {
378
- attributeRequests,
379
- dataVersionFilters: dataVersionFilters?.map(({ endpointId, clusterId, dataVersion }) => ({
380
- path: { endpointId, clusterId },
381
- dataVersion,
382
- })),
383
- eventRequests,
384
- eventFilters,
385
- isFabricFiltered,
386
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
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 { endpointId, clusterId, attribute, requestFromRemote, isFabricFiltered, executeQueued } = options;
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 response = await messenger.readAggregateDataReport();
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 normalizedResult = DecodedDataReport(response);
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(`attributes ${attributeReports.map(({ path }) => resolveAttributeName(path)).join(", ")}`);
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 normalizedResult;
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: DataReport;
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: DataReport) => {
777
- updateReceived?.();
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([attributeReports[0]]);
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: DataReport) {
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: DataReport;
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: DataReport) => {
886
- updateReceived?.();
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: DataReport;
1108
+ report: DecodedDataReport;
1034
1109
  maximumPeerResponseTimeMs: number;
1035
1110
  }>(async messenger => {
1036
- await messenger.sendSubscribeRequest({
1037
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
1038
- attributeRequests,
1039
- eventRequests,
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
- if (attributeReports !== undefined) {
1096
- await this.processAttributeUpdates(attributeReports, attributeListener);
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(DecodedDataReport(dataReport)),
1172
+ onData: dataReport => subscriptionListener(dataReport),
1110
1173
 
1111
1174
  onTimeout: updateTimeoutHandler,
1112
1175
  },
1113
- seedReport,
1176
+ report,
1114
1177
  );
1178
+ processNewAttributeChangesInListener = true;
1115
1179
 
1116
1180
  if (dataVersionFilters !== undefined && dataVersionFilters.length > 0 && enrichCachedAttributeData) {
1117
- this.#enrichCachedAttributeData(seedReport.attributeReports ?? [], dataVersionFilters);
1181
+ this.#enrichCachedAttributeData(report.attributeReports, dataVersionFilters);
1118
1182
  }
1119
1183
 
1120
1184
  return {
1121
- ...seedReport,
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
- })} = ${Diagnostic.json(value)} (version=${version})`,
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
- async readAggregateDataReport(expectedSubscriptionIds?: number[]): Promise<DataReport> {
800
- let result: DataReport | undefined;
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
- if (!result) {
824
- result = report;
825
- } else {
826
- if (Array.isArray(report.attributeReports)) {
827
- if (!result.attributeReports) {
828
- result.attributeReports = report.attributeReports;
829
- } else {
830
- result.attributeReports.push(...report.attributeReports);
831
- }
832
- }
833
- if (Array.isArray(report.eventReports)) {
834
- if (!result.eventReports) {
835
- result.eventReports = report.eventReports;
836
- } else {
837
- result.eventReports.push(...report.eventReports);
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) {