@matter/protocol 0.15.3 → 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 +15 -5
  21. package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
  22. package/dist/cjs/interaction/InteractionClient.js +117 -102
  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 +15 -5
  66. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  67. package/dist/esm/interaction/InteractionClient.js +119 -103
  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 +183 -122
  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,59 +1156,71 @@ export class InteractionClient {
1092
1156
  await this.#nodeStore?.updateLastEventNumber(maxEventNumber);
1093
1157
  }
1094
1158
 
1095
- if (attributeReports !== undefined) {
1096
- for (const data of attributeReports) {
1097
- const {
1098
- path: { endpointId, clusterId, attributeId },
1099
- value,
1100
- version,
1101
- } = data;
1102
- logger.debug(
1103
- `Received attribute update: ${resolveAttributeName({
1104
- endpointId,
1105
- clusterId,
1106
- attributeId,
1107
- })} = ${Diagnostic.json(value)} (version=${version})`,
1108
- );
1109
- if (value === undefined) throw new MatterFlowError("Received empty subscription result value.");
1110
- const { value: oldValue } =
1111
- this.#nodeStore?.retrieveAttribute(endpointId, clusterId, attributeId) ?? {};
1112
- const changed = oldValue !== undefined ? !isDeepEqual(oldValue, value) : undefined;
1113
- if (changed !== false) {
1114
- await this.#nodeStore?.persistAttributes([data]);
1115
- }
1116
-
1117
- attributeListener?.(data, changed, oldValue);
1118
- }
1159
+ // Initial Data reports during seeding are handled directly
1160
+ if (processNewAttributeChangesInListener && attributeReports !== undefined) {
1161
+ await this.processAttributeUpdates(scope, attributeReports, attributeListener);
1119
1162
  }
1120
1163
  updateReceived?.();
1121
1164
  };
1122
1165
 
1123
- const seedReport = DecodedDataReport(report);
1124
-
1125
1166
  await this.#registerSubscription(
1126
1167
  {
1127
1168
  id: subscriptionId,
1128
1169
  maximumPeerResponseTimeMs,
1129
1170
  maxIntervalS: maxInterval,
1130
1171
 
1131
- onData: dataReport => subscriptionListener(DecodedDataReport(dataReport)),
1172
+ onData: dataReport => subscriptionListener(dataReport),
1132
1173
 
1133
1174
  onTimeout: updateTimeoutHandler,
1134
1175
  },
1135
- seedReport,
1176
+ report,
1136
1177
  );
1178
+ processNewAttributeChangesInListener = true;
1137
1179
 
1138
1180
  if (dataVersionFilters !== undefined && dataVersionFilters.length > 0 && enrichCachedAttributeData) {
1139
- this.#enrichCachedAttributeData(seedReport.attributeReports ?? [], dataVersionFilters);
1181
+ this.#enrichCachedAttributeData(report.attributeReports, dataVersionFilters);
1140
1182
  }
1141
1183
 
1142
1184
  return {
1143
- ...seedReport,
1185
+ ...report,
1144
1186
  maxInterval,
1145
1187
  };
1146
1188
  }
1147
1189
 
1190
+ /**
1191
+ * Process changed attributes, detect changes and persist them to the node store
1192
+ */
1193
+ async processAttributeUpdates(
1194
+ scope: ReadScope,
1195
+ attributeReports: DecodedAttributeReportValue<any>[],
1196
+ attributeListener?: (data: DecodedAttributeReportValue<any>, valueChanged?: boolean, oldValue?: any) => void,
1197
+ ) {
1198
+ for (const data of attributeReports) {
1199
+ const {
1200
+ path: { endpointId, clusterId, attributeId },
1201
+ value,
1202
+ version,
1203
+ } = data;
1204
+
1205
+ if (value === undefined) throw new MatterFlowError("Received empty subscription result value.");
1206
+ const { value: oldValue, version: oldVersion } =
1207
+ this.#nodeStore?.retrieveAttribute(endpointId, clusterId, attributeId) ?? {};
1208
+ const changed = oldValue !== undefined ? !isDeepEqual(oldValue, value) : undefined;
1209
+ if (changed !== false || version !== oldVersion) {
1210
+ await this.#nodeStore?.persistAttributes([data], scope);
1211
+ }
1212
+ logger.debug(
1213
+ `Received attribute update${changed ? "(value changed)" : ""}: ${resolveAttributeName({
1214
+ endpointId,
1215
+ clusterId,
1216
+ attributeId,
1217
+ })} = ${serialize(value)} (version=${version})`,
1218
+ );
1219
+
1220
+ attributeListener?.(data, changed, oldValue);
1221
+ }
1222
+ }
1223
+
1148
1224
  async invoke<C extends Command<any, any, any>>(options: {
1149
1225
  endpointId?: EndpointNumber;
1150
1226
  clusterId: ClusterId;
@@ -1439,31 +1515,6 @@ export class InteractionClient {
1439
1515
  }
1440
1516
  }
1441
1517
 
1442
- /**
1443
- * Allows to add the data received by e.g. a Read request to the cache
1444
- */
1445
- async addAttributesToCache(attributeReports: DecodedAttributeReportValue<any>[]) {
1446
- for (const data of attributeReports) {
1447
- const {
1448
- path: { endpointId, clusterId, attributeId },
1449
- value,
1450
- version,
1451
- } = data;
1452
- if (value === undefined) continue;
1453
- const { value: oldValue, version: oldVersion } =
1454
- this.#nodeStore?.retrieveAttribute(endpointId, clusterId, attributeId) ?? {};
1455
- const changed =
1456
- oldVersion !== undefined
1457
- ? oldVersion !== version
1458
- : oldValue !== undefined
1459
- ? !isDeepEqual(oldValue, value)
1460
- : undefined;
1461
- if (changed !== false) {
1462
- await this.#nodeStore?.persistAttributes([data]);
1463
- }
1464
- }
1465
- }
1466
-
1467
1518
  /**
1468
1519
  * Returns the list (optionally filtered by endpointId and/or clusterId) of the dataVersions of the currently cached
1469
1520
  * values to use them as knownDataVersion for read or subscription requests.
@@ -1482,4 +1533,14 @@ export class InteractionClient {
1482
1533
  get maxKnownEventNumber() {
1483
1534
  return this.#nodeStore?.maxEventNumber;
1484
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
+ }
1485
1546
  }