@project-chip/matter.js 0.13.1-alpha.0-20250520-d699cd56d → 0.14.0-alpha.0-20250524-51a7e1721

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/CommissioningController.d.ts.map +1 -1
  2. package/dist/cjs/CommissioningController.js +18 -5
  3. package/dist/cjs/CommissioningController.js.map +1 -1
  4. package/dist/cjs/cluster/export.d.ts +1 -2
  5. package/dist/cjs/cluster/export.d.ts.map +1 -1
  6. package/dist/cjs/cluster/export.js +1 -2
  7. package/dist/cjs/cluster/export.js.map +1 -1
  8. package/dist/cjs/cluster/server/AttributeServer.d.ts +298 -0
  9. package/dist/cjs/cluster/server/AttributeServer.d.ts.map +1 -0
  10. package/dist/cjs/cluster/server/AttributeServer.js +652 -0
  11. package/dist/cjs/cluster/server/AttributeServer.js.map +6 -0
  12. package/dist/cjs/cluster/server/ClusterDatasource.d.ts +15 -0
  13. package/dist/cjs/cluster/server/ClusterDatasource.d.ts.map +1 -0
  14. package/dist/cjs/cluster/server/ClusterDatasource.js +22 -0
  15. package/dist/cjs/cluster/server/ClusterDatasource.js.map +6 -0
  16. package/dist/cjs/cluster/server/ClusterServer.d.ts +2 -2
  17. package/dist/cjs/cluster/server/ClusterServer.d.ts.map +1 -1
  18. package/dist/cjs/cluster/server/ClusterServer.js +6 -4
  19. package/dist/cjs/cluster/server/ClusterServer.js.map +1 -1
  20. package/dist/cjs/cluster/server/ClusterServerTypes.d.ts +4 -1
  21. package/dist/cjs/cluster/server/ClusterServerTypes.d.ts.map +1 -1
  22. package/dist/cjs/cluster/server/ClusterServerTypes.js.map +1 -1
  23. package/dist/cjs/cluster/server/CommandServer.d.ts +33 -0
  24. package/dist/cjs/cluster/server/CommandServer.d.ts.map +1 -0
  25. package/dist/cjs/cluster/server/CommandServer.js +76 -0
  26. package/dist/cjs/cluster/server/CommandServer.js.map +6 -0
  27. package/dist/cjs/cluster/server/EventServer.d.ts +39 -0
  28. package/dist/cjs/cluster/server/EventServer.d.ts.map +1 -0
  29. package/dist/cjs/cluster/server/EventServer.js +152 -0
  30. package/dist/cjs/cluster/server/EventServer.js.map +6 -0
  31. package/dist/cjs/cluster/server/index.d.ts +11 -0
  32. package/dist/cjs/cluster/server/index.d.ts.map +1 -0
  33. package/dist/cjs/cluster/server/index.js +28 -0
  34. package/dist/cjs/cluster/server/index.js.map +6 -0
  35. package/dist/cjs/device/Endpoint.d.ts +9 -5
  36. package/dist/cjs/device/Endpoint.d.ts.map +1 -1
  37. package/dist/cjs/device/Endpoint.js +115 -29
  38. package/dist/cjs/device/Endpoint.js.map +1 -1
  39. package/dist/cjs/device/PairedNode.d.ts +5 -4
  40. package/dist/cjs/device/PairedNode.d.ts.map +1 -1
  41. package/dist/cjs/device/PairedNode.js +26 -3
  42. package/dist/cjs/device/PairedNode.js.map +1 -1
  43. package/dist/cjs/device/export.d.ts +0 -1
  44. package/dist/cjs/device/export.d.ts.map +1 -1
  45. package/dist/cjs/device/export.js +0 -8
  46. package/dist/cjs/device/export.js.map +1 -1
  47. package/dist/esm/CommissioningController.d.ts.map +1 -1
  48. package/dist/esm/CommissioningController.js +18 -5
  49. package/dist/esm/CommissioningController.js.map +1 -1
  50. package/dist/esm/cluster/export.d.ts +1 -2
  51. package/dist/esm/cluster/export.d.ts.map +1 -1
  52. package/dist/esm/cluster/export.js +1 -2
  53. package/dist/esm/cluster/export.js.map +1 -1
  54. package/dist/esm/cluster/server/AttributeServer.d.ts +298 -0
  55. package/dist/esm/cluster/server/AttributeServer.d.ts.map +1 -0
  56. package/dist/esm/cluster/server/AttributeServer.js +636 -0
  57. package/dist/esm/cluster/server/AttributeServer.js.map +6 -0
  58. package/dist/esm/cluster/server/ClusterDatasource.d.ts +15 -0
  59. package/dist/esm/cluster/server/ClusterDatasource.d.ts.map +1 -0
  60. package/dist/esm/cluster/server/ClusterDatasource.js +6 -0
  61. package/dist/esm/cluster/server/ClusterDatasource.js.map +6 -0
  62. package/dist/esm/cluster/server/ClusterServer.d.ts +2 -2
  63. package/dist/esm/cluster/server/ClusterServer.d.ts.map +1 -1
  64. package/dist/esm/cluster/server/ClusterServer.js +3 -5
  65. package/dist/esm/cluster/server/ClusterServer.js.map +1 -1
  66. package/dist/esm/cluster/server/ClusterServerTypes.d.ts +4 -1
  67. package/dist/esm/cluster/server/ClusterServerTypes.d.ts.map +1 -1
  68. package/dist/esm/cluster/server/ClusterServerTypes.js.map +1 -1
  69. package/dist/esm/cluster/server/CommandServer.d.ts +33 -0
  70. package/dist/esm/cluster/server/CommandServer.d.ts.map +1 -0
  71. package/dist/esm/cluster/server/CommandServer.js +56 -0
  72. package/dist/esm/cluster/server/CommandServer.js.map +6 -0
  73. package/dist/esm/cluster/server/EventServer.d.ts +39 -0
  74. package/dist/esm/cluster/server/EventServer.d.ts.map +1 -0
  75. package/dist/esm/cluster/server/EventServer.js +141 -0
  76. package/dist/esm/cluster/server/EventServer.js.map +6 -0
  77. package/dist/esm/cluster/server/index.d.ts +11 -0
  78. package/dist/esm/cluster/server/index.d.ts.map +1 -0
  79. package/dist/esm/cluster/server/index.js +11 -0
  80. package/dist/esm/cluster/server/index.js.map +6 -0
  81. package/dist/esm/device/Endpoint.d.ts +9 -5
  82. package/dist/esm/device/Endpoint.d.ts.map +1 -1
  83. package/dist/esm/device/Endpoint.js +116 -31
  84. package/dist/esm/device/Endpoint.js.map +1 -1
  85. package/dist/esm/device/PairedNode.d.ts +5 -4
  86. package/dist/esm/device/PairedNode.d.ts.map +1 -1
  87. package/dist/esm/device/PairedNode.js +26 -4
  88. package/dist/esm/device/PairedNode.js.map +1 -1
  89. package/dist/esm/device/export.d.ts +0 -1
  90. package/dist/esm/device/export.d.ts.map +1 -1
  91. package/dist/esm/device/export.js +0 -4
  92. package/dist/esm/device/export.js.map +1 -1
  93. package/package.json +8 -8
  94. package/src/CommissioningController.ts +24 -6
  95. package/src/cluster/export.ts +1 -2
  96. package/src/cluster/server/AttributeServer.ts +867 -0
  97. package/src/cluster/server/ClusterDatasource.ts +16 -0
  98. package/src/cluster/server/ClusterServer.ts +6 -9
  99. package/src/cluster/server/ClusterServerTypes.ts +4 -11
  100. package/src/cluster/server/CommandServer.ts +87 -0
  101. package/src/cluster/server/EventServer.ts +198 -0
  102. package/src/cluster/server/index.ts +11 -0
  103. package/src/device/Endpoint.ts +135 -39
  104. package/src/device/PairedNode.ts +30 -7
  105. package/src/device/export.ts +0 -3
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { SupportedStorageTypes } from "#general";
8
+ import { Fabric, OccurrenceManager } from "#protocol";
9
+
10
+ export interface ClusterDatasource {
11
+ readonly version: number;
12
+ readonly eventHandler?: OccurrenceManager;
13
+ readonly fabrics: Fabric[];
14
+ increaseVersion(): number;
15
+ changed(key: string, value: SupportedStorageTypes): void;
16
+ }
@@ -6,14 +6,7 @@
6
6
 
7
7
  import { capitalize, Diagnostic, ImplementationError, InternalError, Logger, MaybePromise } from "#general";
8
8
  import { AccessLevel } from "#model";
9
- import {
10
- ClusterServer as BaseClusterServer,
11
- ClusterDatasource,
12
- CommandServer,
13
- createAttributeServer,
14
- createEventServer,
15
- Fabric,
16
- } from "#protocol";
9
+ import { Fabric } from "#protocol";
17
10
  import {
18
11
  AttributeId,
19
12
  BitSchema,
@@ -26,6 +19,8 @@ import {
26
19
  TypeFromPartialBitSchema,
27
20
  } from "#types";
28
21
  import { Endpoint } from "../../device/Endpoint.js";
22
+ import { createAttributeServer } from "./AttributeServer.js";
23
+ import { ClusterDatasource } from "./ClusterDatasource.js";
29
24
  import {
30
25
  AttributeInitialValues,
31
26
  AttributeServers,
@@ -35,6 +30,8 @@ import {
35
30
  EventServers,
36
31
  SupportedEventsList,
37
32
  } from "./ClusterServerTypes.js";
33
+ import { CommandServer } from "./CommandServer.js";
34
+ import { createEventServer } from "./EventServer.js";
38
35
 
39
36
  const logger = Logger.get("ClusterServer");
40
37
 
@@ -53,7 +50,7 @@ function isConditionMatching<F extends BitSchema, SF extends TypeFromPartialBitS
53
50
  /**
54
51
  * A collection of servers for a cluster's attributes, commands and events.
55
52
  */
56
- export interface ClusterServer<T extends ClusterType = ClusterType> extends BaseClusterServer {
53
+ export interface ClusterServer<T extends ClusterType = ClusterType> {
57
54
  /**
58
55
  * Cluster ID
59
56
  */
@@ -5,17 +5,7 @@
5
5
  */
6
6
 
7
7
  import { Merge } from "#general";
8
- import {
9
- AnyEventServer,
10
- AttributeServer,
11
- ClusterClientObj,
12
- CommandServer,
13
- Fabric,
14
- FabricScopedAttributeServer,
15
- FixedAttributeServer,
16
- Message,
17
- Session,
18
- } from "#protocol";
8
+ import { ClusterClientObj, Fabric, Message, Session } from "#protocol";
19
9
  import {
20
10
  Attribute,
21
11
  AttributeId,
@@ -48,7 +38,10 @@ import {
48
38
  WritableFabricScopedAttribute,
49
39
  } from "#types";
50
40
  import { Endpoint } from "../../device/Endpoint.js";
41
+ import { AttributeServer, FabricScopedAttributeServer, FixedAttributeServer } from "./AttributeServer.js";
51
42
  import { type ClusterServer } from "./ClusterServer.js";
43
+ import { CommandServer } from "./CommandServer.js";
44
+ import { AnyEventServer } from "./EventServer.js";
52
45
 
53
46
  /** Cluster attributes accessible on the cluster server */
54
47
  type MandatoryAttributeServers<A extends Attributes> = Omit<
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Endpoint } from "#device/Endpoint.js";
8
+ import { Diagnostic, Logger } from "#general";
9
+ import { AccessLevel, FabricIndex } from "#model";
10
+ import { Message, SecureSession, Session } from "#protocol";
11
+ import { CommandId, StatusCode, TlvSchema, TlvStream } from "#types";
12
+
13
+ const logger = Logger.get("CommandServer");
14
+
15
+ export class CommandServer<RequestT = any, ResponseT = any> {
16
+ readonly #invokeAcl: AccessLevel;
17
+
18
+ constructor(
19
+ readonly invokeId: CommandId,
20
+ readonly responseId: CommandId,
21
+ readonly name: string,
22
+ readonly requestSchema: TlvSchema<RequestT>,
23
+ readonly responseSchema: TlvSchema<ResponseT>,
24
+ readonly requiresTimedInteraction: boolean,
25
+ invokeAcl: AccessLevel,
26
+ protected readonly handler: (
27
+ request: RequestT,
28
+ session: Session,
29
+ message: Message,
30
+ endpoint: Endpoint,
31
+ ) => Promise<ResponseT> | ResponseT,
32
+ ) {
33
+ this.#invokeAcl = invokeAcl;
34
+ }
35
+
36
+ async invoke(
37
+ session: Session,
38
+ args: TlvStream,
39
+ message: Message,
40
+ endpoint: Endpoint,
41
+ ): Promise<{
42
+ /** Primary StatusCode of the invoke request as defined by Interaction proptocol. */
43
+ code: StatusCode;
44
+
45
+ /** Cluster specific StatusCode of the invoke request if required */
46
+ clusterCode?: number;
47
+
48
+ /** ID of the response */
49
+ responseId: CommandId;
50
+
51
+ /** Response data */
52
+ response: TlvStream;
53
+ }> {
54
+ let request = this.requestSchema.decodeTlv(args);
55
+
56
+ // Inject fabric index into structures in general if undefined, if set it will be used
57
+ if (session.isSecure) {
58
+ const fabric = (session as SecureSession).fabric;
59
+ if (fabric) {
60
+ request = this.requestSchema.injectField(
61
+ request,
62
+ <number>FabricIndex.id,
63
+ session.associatedFabric.fabricIndex,
64
+ () => true, // Noone should send any index and if we simply SHALL ignore it
65
+ );
66
+ }
67
+ }
68
+
69
+ this.requestSchema.validate(request);
70
+ this.debug(`Invoke ${this.name} with data ${Diagnostic.json(request)}`);
71
+ const response = await this.handler(request, session, message, endpoint);
72
+ this.debug(`Invoke ${this.name} response : ${Diagnostic.json(response)}`);
73
+ return {
74
+ code: StatusCode.Success,
75
+ responseId: this.responseId,
76
+ response: this.responseSchema.encodeTlv(response),
77
+ };
78
+ }
79
+
80
+ debug(message: string) {
81
+ logger.debug(message);
82
+ }
83
+
84
+ get invokeAcl() {
85
+ return this.#invokeAcl ?? AccessLevel.Operate; // or View?? Re "Read/Invoke" in Access table
86
+ }
87
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Endpoint } from "#device/Endpoint.js";
8
+ import {
9
+ ImplementationError,
10
+ InternalError,
11
+ isObject,
12
+ MatterAggregateError,
13
+ MaybePromise,
14
+ Storage,
15
+ StorageOperationResult,
16
+ Time,
17
+ } from "#general";
18
+ import { AccessLevel, ClusterModel, EventModel, MatterModel } from "#model";
19
+ import { Message, NumberedOccurrence, Occurrence, OccurrenceManager, SecureSession, Session } from "#protocol";
20
+ import {
21
+ Attributes,
22
+ BitSchema,
23
+ Cluster,
24
+ ClusterId,
25
+ Commands,
26
+ Event,
27
+ EventId,
28
+ EventPriority,
29
+ Events,
30
+ FabricIndex,
31
+ TlvEventFilter,
32
+ TlvSchema,
33
+ TypeFromPartialBitSchema,
34
+ TypeFromSchema,
35
+ } from "#types";
36
+
37
+ export type AnyEventServer<T = any, S extends Storage = any> = EventServer<T, S> | FabricSensitiveEventServer<T, S>;
38
+
39
+ export function createEventServer<
40
+ T,
41
+ F extends BitSchema,
42
+ SF extends TypeFromPartialBitSchema<F>,
43
+ A extends Attributes,
44
+ C extends Commands,
45
+ E extends Events,
46
+ S extends Storage,
47
+ >(
48
+ clusterDef: Cluster<F, SF, A, C, E>,
49
+ eventDef: Event<T, F>,
50
+ eventName: string,
51
+ schema: TlvSchema<T>,
52
+ priority: EventPriority,
53
+ readAcl: AccessLevel | undefined,
54
+ ): EventServer<T, S> {
55
+ let fabricSensitive = false;
56
+ const clusterFromModel = MatterModel.standard.get(ClusterModel, clusterDef.id);
57
+ if (clusterFromModel !== undefined) {
58
+ const eventModel = clusterFromModel.get(EventModel, eventDef.id);
59
+ if (eventModel !== undefined) {
60
+ fabricSensitive = eventModel.fabricSensitive;
61
+ }
62
+ }
63
+ if (fabricSensitive) {
64
+ return new FabricSensitiveEventServer(eventDef.id, clusterDef.id, eventName, schema, priority, readAcl);
65
+ }
66
+ return new EventServer(eventDef.id, clusterDef.id, eventName, schema, priority, readAcl);
67
+ }
68
+
69
+ export class EventServer<T = any, S extends Storage = any> {
70
+ private eventList = new Array<Occurrence>();
71
+ private readonly listeners = new Array<(event: NumberedOccurrence) => void>();
72
+ protected endpoint?: Endpoint;
73
+ protected eventHandler?: OccurrenceManager;
74
+ #readAcl: AccessLevel | undefined;
75
+ hasFabricSensitiveData = false;
76
+
77
+ constructor(
78
+ readonly id: EventId,
79
+ readonly clusterId: ClusterId,
80
+ readonly name: string,
81
+ readonly schema: TlvSchema<T>,
82
+ readonly priority: EventPriority,
83
+ readAcl: AccessLevel | undefined,
84
+ ) {
85
+ this.#readAcl = readAcl;
86
+ }
87
+
88
+ get readAcl() {
89
+ return this.#readAcl ?? AccessLevel.View; // ???
90
+ }
91
+
92
+ assignToEndpoint(endpoint: Endpoint) {
93
+ this.endpoint = endpoint;
94
+ }
95
+
96
+ // TODO Try to get rid of that late binding and simply things again
97
+ // potentially with refactoring out MatterDevice and MatterController
98
+ bindToEventHandler(eventHandler: OccurrenceManager) {
99
+ this.eventHandler = eventHandler;
100
+ // Send all stored events to the new listener
101
+ const promises = new Array<PromiseLike<void>>();
102
+ for (const event of this.eventList) {
103
+ const finalEvent = this.eventHandler.add(event);
104
+ if (finalEvent !== undefined && MaybePromise.is(finalEvent)) {
105
+ promises.push(finalEvent.then(e => this.listeners.forEach(listener => listener(e))));
106
+ } else {
107
+ this.listeners.forEach(listener => listener(finalEvent));
108
+ }
109
+ }
110
+ this.eventList = [];
111
+ if (promises.length > 0) {
112
+ return MatterAggregateError.allSettled(promises, "Error binding events to the event handlers").then(
113
+ () => {},
114
+ ) as StorageOperationResult<S>;
115
+ }
116
+ return undefined as StorageOperationResult<S>;
117
+ }
118
+
119
+ triggerEvent(data: T) {
120
+ if (this.endpoint === undefined || this.endpoint.number === undefined) {
121
+ throw new InternalError("Endpoint not assigned");
122
+ }
123
+ const occurrence: Occurrence = {
124
+ eventId: this.id,
125
+ clusterId: this.clusterId,
126
+ endpointId: this.endpoint.number,
127
+ epochTimestamp: Time.nowMs(),
128
+ priority: this.priority,
129
+ payload: data,
130
+ };
131
+ if (this.eventHandler === undefined) {
132
+ // As long as we have no occurrence manager, we store the events
133
+ this.eventList.push(occurrence);
134
+ } else {
135
+ const finalEvent = this.eventHandler.add(occurrence);
136
+ return MaybePromise.then(finalEvent, e => {
137
+ this.listeners.forEach(listener => listener(e));
138
+ }) as StorageOperationResult<S>;
139
+ }
140
+ return undefined as StorageOperationResult<S>;
141
+ }
142
+
143
+ addListener(listener: (event: NumberedOccurrence) => void) {
144
+ this.listeners.push(listener);
145
+ }
146
+
147
+ removeListener(listener: (event: NumberedOccurrence) => void) {
148
+ const entryIndex = this.listeners.indexOf(listener);
149
+ if (entryIndex !== -1) {
150
+ this.listeners.splice(entryIndex, 1);
151
+ }
152
+ }
153
+
154
+ get(
155
+ session: Session,
156
+ isFabricFiltered: boolean,
157
+ _message?: Message,
158
+ filters?: TypeFromSchema<typeof TlvEventFilter>[],
159
+ ) {
160
+ if (this.eventHandler === undefined) {
161
+ throw new InternalError("EventServer not bound to OccurrenceManager");
162
+ }
163
+ if (this.endpoint === undefined) {
164
+ throw new InternalError("EventServer not bound to Endpoint");
165
+ }
166
+
167
+ return this.eventHandler.query(
168
+ { endpointId: this.endpoint.number, clusterId: this.clusterId, eventId: this.id },
169
+ filters,
170
+ // When request is fabric filtered or the event is Fabric sensitive then filter the events for the fabrics
171
+ isFabricFiltered ? ((session as SecureSession).fabric?.fabricIndex ?? FabricIndex.NO_FABRIC) : undefined,
172
+ );
173
+ }
174
+ }
175
+
176
+ export class FabricSensitiveEventServer<T, S extends Storage> extends EventServer<T, S> {
177
+ override hasFabricSensitiveData = true;
178
+
179
+ override get(
180
+ session: Session,
181
+ _isFabricFiltered: boolean,
182
+ message?: Message,
183
+ filters?: TypeFromSchema<typeof TlvEventFilter>[],
184
+ ) {
185
+ // because the event is fabric sensitive it is always filtered out when another fabric tries to access it
186
+ return super.get(session, true, message, filters);
187
+ }
188
+
189
+ override triggerEvent(data: T) {
190
+ if (!isObject(data) || data === null) {
191
+ throw new ImplementationError("FabricSensitive events need to have an object as data.");
192
+ }
193
+ if (!("fabricIndex" in data)) {
194
+ throw new InternalError("FabricSensitive events requires fabricIndex in data.");
195
+ }
196
+ return super.triggerEvent(data);
197
+ }
198
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ export * from "./AttributeServer.js";
8
+ export * from "./ClusterDatasource.js";
9
+ export * from "./ClusterServer.js";
10
+ export * from "./CommandServer.js";
11
+ export * from "./EventServer.js";
@@ -7,12 +7,11 @@
7
7
  import {
8
8
  BasicInformationCluster,
9
9
  BridgedDeviceBasicInformationCluster,
10
- DescriptorCluster,
11
10
  FixedLabelCluster,
12
11
  UserLabelCluster,
13
12
  } from "#clusters";
14
- import { AtLeastOne, ImplementationError, InternalError, NotImplementedError } from "#general";
15
- import { ClusterClientObj, EndpointInterface } from "#protocol";
13
+ import { AtLeastOne, Diagnostic, ImplementationError, InternalError, NotImplementedError } from "#general";
14
+ import { ClusterClientObj, SupportedAttributeClient, UnknownSupportedAttributeClient } from "#protocol";
16
15
  import {
17
16
  Attributes,
18
17
  BitSchema,
@@ -35,7 +34,7 @@ export interface EndpointOptions {
35
34
  uniqueStorageKey?: string;
36
35
  }
37
36
 
38
- export class Endpoint implements EndpointInterface {
37
+ export class Endpoint {
39
38
  private readonly clusterServers = new Map<ClusterId, ClusterServerObj>();
40
39
  private readonly clusterClients = new Map<ClusterId, ClusterClientObj>();
41
40
  private readonly childEndpoints: Endpoint[] = [];
@@ -46,8 +45,6 @@ export class Endpoint implements EndpointInterface {
46
45
  /** noop until officially set **/
47
46
  };
48
47
 
49
- private descriptorCluster: ClusterServerObj<typeof DescriptorCluster>;
50
-
51
48
  /**
52
49
  * Create a new Endpoint instance.
53
50
  *
@@ -58,20 +55,6 @@ export class Endpoint implements EndpointInterface {
58
55
  protected deviceTypes: AtLeastOne<DeviceTypeDefinition>,
59
56
  options: EndpointOptions = {},
60
57
  ) {
61
- this.descriptorCluster = ClusterServer(
62
- DescriptorCluster,
63
- {
64
- deviceTypeList: deviceTypes.map(deviceType => ({
65
- deviceType: deviceType.code,
66
- revision: deviceType.revision,
67
- })),
68
- serverList: [],
69
- clientList: [],
70
- partsList: [],
71
- },
72
- {},
73
- );
74
- this.addClusterServer(this.descriptorCluster);
75
58
  this.setDeviceTypes(deviceTypes);
76
59
 
77
60
  if (options.endpointId !== undefined) {
@@ -158,9 +141,6 @@ export class Endpoint implements EndpointInterface {
158
141
  asClusterServerInternal(currentCluster)._close();
159
142
  }
160
143
  asClusterServerInternal(cluster)._assignToEndpoint(this);
161
- if (cluster.id === DescriptorCluster.id) {
162
- this.descriptorCluster = cluster as unknown as ClusterServerObj<typeof DescriptorCluster>;
163
- }
164
144
 
165
145
  // In ts4 the cast to "any" here was unnecessary. In TS5 the fact that
166
146
  // the string index signature in Attributes and Events doesn't allow
@@ -174,15 +154,10 @@ export class Endpoint implements EndpointInterface {
174
154
  // assertions everywhere those interfaces are used. I'm treating that
175
155
  // as low priority for now
176
156
  this.clusterServers.set(cluster.id, cluster as any);
177
-
178
- this.descriptorCluster.attributes.serverList.init(Array.from(this.clusterServers.keys()).sort((a, b) => a - b));
179
- this.structureChangedCallback(); // Inform parent about structure change
180
157
  }
181
158
 
182
159
  addClusterClient(cluster: ClusterClientObj) {
183
160
  this.clusterClients.set(cluster.id, cluster);
184
- this.descriptorCluster.attributes.clientList.init(Array.from(this.clusterClients.keys()).sort((a, b) => a - b));
185
- this.structureChangedCallback(); // Inform parent about structure change
186
161
  }
187
162
 
188
163
  // TODO cleanup with id number vs ClusterId
@@ -238,17 +213,9 @@ export class Endpoint implements EndpointInterface {
238
213
  deviceTypes.forEach(deviceType => deviceTypeList.set(deviceType.code, deviceType));
239
214
  this.deviceTypes = Array.from(deviceTypeList.values()) as AtLeastOne<DeviceTypeDefinition>;
240
215
  this.name = deviceTypes[0].name;
241
-
242
- // Update descriptor cluster
243
- this.descriptorCluster.attributes.deviceTypeList.init(
244
- this.deviceTypes.map(deviceType => ({
245
- deviceType: deviceType.code,
246
- revision: deviceType.revision,
247
- })),
248
- );
249
216
  }
250
217
 
251
- addChildEndpoint(endpoint: EndpointInterface): void {
218
+ addChildEndpoint(endpoint: Endpoint): void {
252
219
  if (!(endpoint instanceof Endpoint)) {
253
220
  throw new Error("Only supported EndpointInterface implementation is Endpoint");
254
221
  }
@@ -360,8 +327,137 @@ export class Endpoint implements EndpointInterface {
360
327
  newPartsList.push(...childPartsList);
361
328
  }
362
329
 
363
- this.descriptorCluster.attributes.partsList.setLocal(newPartsList);
364
-
365
330
  return newPartsList;
366
331
  }
332
+
333
+ /**
334
+ * Hierarchical diagnostics of endpoint and children.
335
+ */
336
+ get [Diagnostic.value](): unknown[] {
337
+ return [
338
+ Diagnostic.strong(this.name),
339
+ Diagnostic.dict({
340
+ "endpoint#": this.number,
341
+ type: this.deviceTypes.map(
342
+ ({ name, code, revision }) => `${name} (0x${code.toString(16)}, ${revision})`,
343
+ ),
344
+ }),
345
+ Diagnostic.list([...this.#clusterDiagnostics(), ...this.getChildEndpoints()]),
346
+ ];
347
+ }
348
+
349
+ #clusterDiagnostics(): unknown[] {
350
+ const clusterDiagnostics = new Array<unknown>();
351
+
352
+ const clients = this.getAllClusterClients();
353
+ if (clients.length) {
354
+ clusterDiagnostics.push([
355
+ Diagnostic.strong("clients"),
356
+ Diagnostic.list(clients.map(client => this.#clusterClientDiagnostics(client))),
357
+ ]);
358
+ }
359
+
360
+ const servers = this.getAllClusterServers();
361
+ if (servers.length) {
362
+ clusterDiagnostics.push([
363
+ Diagnostic.strong("servers"),
364
+ Diagnostic.list(servers.map(server => this.#clusterServerDiagnostics(server))),
365
+ ]);
366
+ }
367
+
368
+ const childs = this.getChildEndpoints();
369
+ if (childs.length) {
370
+ clusterDiagnostics.push([Diagnostic.strong("childs"), Diagnostic.list([])]);
371
+ }
372
+
373
+ return clusterDiagnostics;
374
+ }
375
+
376
+ #clusterClientDiagnostics(client: ClusterClientObj) {
377
+ const result = [
378
+ Diagnostic.strong(client.name),
379
+ Diagnostic.dict({
380
+ id: Diagnostic.hex(client.id),
381
+ rev: client.revision,
382
+ flags: Diagnostic.asFlags({
383
+ unknown: client.isUnknown,
384
+ }),
385
+ }),
386
+ ];
387
+ const elementDiagnostic = Array<unknown>();
388
+
389
+ const { supportedFeatures: features } = client;
390
+ const supportedFeatures = new Array<string>();
391
+ for (const featureName in features) {
392
+ if (features[featureName] === true) supportedFeatures.push(featureName);
393
+ }
394
+ if (supportedFeatures.length) {
395
+ elementDiagnostic.push([Diagnostic.strong("features"), supportedFeatures]);
396
+ }
397
+
398
+ if (Object.keys(client.attributes).length) {
399
+ const clusterData = new Array<unknown>();
400
+ for (const attributeName in client.attributes) {
401
+ const attribute = client.attributes[attributeName];
402
+ if (attribute === undefined || !(attribute instanceof SupportedAttributeClient)) continue;
403
+
404
+ clusterData.push([
405
+ attribute.name,
406
+ Diagnostic.dict({
407
+ id: Diagnostic.hex(attribute.id),
408
+ val: attribute.getLocal(),
409
+ flags: Diagnostic.asFlags({
410
+ unknown: attribute instanceof UnknownSupportedAttributeClient,
411
+ fabricScoped: attribute.fabricScoped,
412
+ }),
413
+ }),
414
+ ]);
415
+ }
416
+ if (clusterData.length) {
417
+ elementDiagnostic.push([Diagnostic.strong("attributes"), Diagnostic.list(clusterData)]);
418
+ }
419
+ }
420
+
421
+ if (Object.keys(client.commands).length) {
422
+ const clusterData = new Array<unknown>();
423
+ for (const commandName in client.commands) {
424
+ if (commandName.match(/^\d+$/)) continue;
425
+ const command = client.commands[commandName];
426
+ if (command === undefined || !client.isCommandSupportedByName(commandName)) continue;
427
+
428
+ clusterData.push([commandName]);
429
+ }
430
+ if (clusterData.length) {
431
+ elementDiagnostic.push([Diagnostic.strong("commands"), Diagnostic.list(clusterData)]);
432
+ }
433
+ }
434
+
435
+ if (Object.keys(client.events).length) {
436
+ const clusterData = new Array<unknown>();
437
+ for (const eventName in client.events) {
438
+ const event = client.events[eventName];
439
+ if (event === undefined) continue;
440
+
441
+ clusterData.push([
442
+ event.name,
443
+ Diagnostic.dict({
444
+ id: Diagnostic.hex(event.id),
445
+ }),
446
+ ]);
447
+ }
448
+ if (clusterData.length) {
449
+ elementDiagnostic.push([Diagnostic.strong("events"), Diagnostic.list(clusterData)]);
450
+ }
451
+ }
452
+
453
+ if (elementDiagnostic.length) {
454
+ result.push(Diagnostic.list(elementDiagnostic));
455
+ }
456
+
457
+ return result;
458
+ }
459
+
460
+ #clusterServerDiagnostics(server: ClusterServerObj) {
461
+ return [Diagnostic.strong(server.name)];
462
+ }
367
463
  }