@matter/node 0.16.0-alpha.0-20251016-b56cf5683 → 0.16.0-alpha.0-20251020-3f6e46245

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 (162) hide show
  1. package/dist/cjs/behavior/Behavior.d.ts +1 -1
  2. package/dist/cjs/behavior/Behavior.d.ts.map +1 -1
  3. package/dist/cjs/behavior/Behavior.js +29 -3
  4. package/dist/cjs/behavior/Behavior.js.map +1 -1
  5. package/dist/cjs/behavior/cluster/ClusterBehavior.d.ts +1 -1
  6. package/dist/cjs/behavior/cluster/ClusterBehavior.d.ts.map +1 -1
  7. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.d.ts.map +1 -1
  8. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.js +2 -4
  9. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.js.map +1 -1
  10. package/dist/cjs/behavior/supervision/RootSupervisor.d.ts.map +1 -1
  11. package/dist/cjs/behavior/supervision/RootSupervisor.js +2 -1
  12. package/dist/cjs/behavior/supervision/RootSupervisor.js.map +1 -1
  13. package/dist/cjs/behavior/system/commissioning/CommissioningClient.d.ts +35 -11
  14. package/dist/cjs/behavior/system/commissioning/CommissioningClient.d.ts.map +1 -1
  15. package/dist/cjs/behavior/system/commissioning/CommissioningClient.js +147 -138
  16. package/dist/cjs/behavior/system/commissioning/CommissioningClient.js.map +2 -2
  17. package/dist/cjs/behavior/system/commissioning/CommissioningServer.d.ts +0 -7
  18. package/dist/cjs/behavior/system/commissioning/CommissioningServer.d.ts.map +1 -1
  19. package/dist/cjs/behavior/system/commissioning/CommissioningServer.js +23 -27
  20. package/dist/cjs/behavior/system/commissioning/CommissioningServer.js.map +1 -1
  21. package/dist/cjs/behavior/system/commissioning/RemoteDescriptor.js +4 -4
  22. package/dist/cjs/behavior/system/commissioning/RemoteDescriptor.js.map +1 -1
  23. package/dist/cjs/behavior/system/network/NetworkClient.d.ts.map +1 -1
  24. package/dist/cjs/behavior/system/network/NetworkClient.js +7 -2
  25. package/dist/cjs/behavior/system/network/NetworkClient.js.map +1 -1
  26. package/dist/cjs/behavior/system/network/NetworkServer.js +1 -1
  27. package/dist/cjs/behavior/system/network/NetworkServer.js.map +1 -1
  28. package/dist/cjs/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
  29. package/dist/cjs/behaviors/access-control/AccessControlServer.js +2 -9
  30. package/dist/cjs/behaviors/access-control/AccessControlServer.js.map +1 -1
  31. package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.js +1 -1
  32. package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.js.map +1 -1
  33. package/dist/cjs/node/ClientGroup.d.ts +18 -0
  34. package/dist/cjs/node/ClientGroup.d.ts.map +1 -0
  35. package/dist/cjs/node/ClientGroup.js +53 -0
  36. package/dist/cjs/node/ClientGroup.js.map +6 -0
  37. package/dist/cjs/node/ClientNode.d.ts +3 -0
  38. package/dist/cjs/node/ClientNode.d.ts.map +1 -1
  39. package/dist/cjs/node/ClientNode.js +8 -2
  40. package/dist/cjs/node/ClientNode.js.map +1 -1
  41. package/dist/cjs/node/client/ClientBehavior.d.ts.map +1 -1
  42. package/dist/cjs/node/client/ClientBehavior.js +13 -12
  43. package/dist/cjs/node/client/ClientBehavior.js.map +1 -1
  44. package/dist/cjs/node/client/ClientGroupInteraction.d.ts +22 -0
  45. package/dist/cjs/node/client/ClientGroupInteraction.d.ts.map +1 -0
  46. package/dist/cjs/node/client/ClientGroupInteraction.js +67 -0
  47. package/dist/cjs/node/client/ClientGroupInteraction.js.map +6 -0
  48. package/dist/cjs/node/client/ClientNodeFactory.d.ts +2 -1
  49. package/dist/cjs/node/client/ClientNodeFactory.d.ts.map +1 -1
  50. package/dist/cjs/node/client/ClientNodeFactory.js.map +1 -1
  51. package/dist/cjs/node/client/ClientNodeInteraction.d.ts +16 -2
  52. package/dist/cjs/node/client/ClientNodeInteraction.d.ts.map +1 -1
  53. package/dist/cjs/node/client/ClientNodeInteraction.js +14 -0
  54. package/dist/cjs/node/client/ClientNodeInteraction.js.map +1 -1
  55. package/dist/cjs/node/client/Peers.d.ts.map +1 -1
  56. package/dist/cjs/node/client/Peers.js +21 -8
  57. package/dist/cjs/node/client/Peers.js.map +1 -1
  58. package/dist/cjs/node/integration/ProtocolService.d.ts.map +1 -1
  59. package/dist/cjs/node/integration/ProtocolService.js +10 -16
  60. package/dist/cjs/node/integration/ProtocolService.js.map +1 -1
  61. package/dist/cjs/node/server/InteractionServer.d.ts.map +1 -1
  62. package/dist/cjs/node/server/InteractionServer.js +87 -39
  63. package/dist/cjs/node/server/InteractionServer.js.map +1 -1
  64. package/dist/cjs/node/server/OnlineServerInteraction.js +1 -1
  65. package/dist/cjs/node/server/OnlineServerInteraction.js.map +1 -1
  66. package/dist/cjs/storage/client/ClientNodeStores.d.ts +2 -0
  67. package/dist/cjs/storage/client/ClientNodeStores.d.ts.map +1 -1
  68. package/dist/cjs/storage/client/ClientNodeStores.js +19 -0
  69. package/dist/cjs/storage/client/ClientNodeStores.js.map +1 -1
  70. package/dist/esm/behavior/Behavior.d.ts +1 -1
  71. package/dist/esm/behavior/Behavior.d.ts.map +1 -1
  72. package/dist/esm/behavior/Behavior.js +29 -3
  73. package/dist/esm/behavior/Behavior.js.map +1 -1
  74. package/dist/esm/behavior/cluster/ClusterBehavior.d.ts +1 -1
  75. package/dist/esm/behavior/cluster/ClusterBehavior.d.ts.map +1 -1
  76. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.d.ts.map +1 -1
  77. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.js +3 -4
  78. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.js.map +1 -1
  79. package/dist/esm/behavior/supervision/RootSupervisor.d.ts.map +1 -1
  80. package/dist/esm/behavior/supervision/RootSupervisor.js +3 -1
  81. package/dist/esm/behavior/supervision/RootSupervisor.js.map +1 -1
  82. package/dist/esm/behavior/system/commissioning/CommissioningClient.d.ts +35 -11
  83. package/dist/esm/behavior/system/commissioning/CommissioningClient.d.ts.map +1 -1
  84. package/dist/esm/behavior/system/commissioning/CommissioningClient.js +167 -141
  85. package/dist/esm/behavior/system/commissioning/CommissioningClient.js.map +2 -2
  86. package/dist/esm/behavior/system/commissioning/CommissioningServer.d.ts +0 -7
  87. package/dist/esm/behavior/system/commissioning/CommissioningServer.d.ts.map +1 -1
  88. package/dist/esm/behavior/system/commissioning/CommissioningServer.js +23 -27
  89. package/dist/esm/behavior/system/commissioning/CommissioningServer.js.map +1 -1
  90. package/dist/esm/behavior/system/commissioning/RemoteDescriptor.js +4 -4
  91. package/dist/esm/behavior/system/commissioning/RemoteDescriptor.js.map +1 -1
  92. package/dist/esm/behavior/system/network/NetworkClient.d.ts.map +1 -1
  93. package/dist/esm/behavior/system/network/NetworkClient.js +7 -2
  94. package/dist/esm/behavior/system/network/NetworkClient.js.map +1 -1
  95. package/dist/esm/behavior/system/network/NetworkServer.js +1 -1
  96. package/dist/esm/behavior/system/network/NetworkServer.js.map +1 -1
  97. package/dist/esm/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
  98. package/dist/esm/behaviors/access-control/AccessControlServer.js +2 -9
  99. package/dist/esm/behaviors/access-control/AccessControlServer.js.map +1 -1
  100. package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.js +1 -1
  101. package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.js.map +1 -1
  102. package/dist/esm/node/ClientGroup.d.ts +18 -0
  103. package/dist/esm/node/ClientGroup.d.ts.map +1 -0
  104. package/dist/esm/node/ClientGroup.js +33 -0
  105. package/dist/esm/node/ClientGroup.js.map +6 -0
  106. package/dist/esm/node/ClientNode.d.ts +3 -0
  107. package/dist/esm/node/ClientNode.d.ts.map +1 -1
  108. package/dist/esm/node/ClientNode.js +8 -2
  109. package/dist/esm/node/ClientNode.js.map +1 -1
  110. package/dist/esm/node/client/ClientBehavior.d.ts.map +1 -1
  111. package/dist/esm/node/client/ClientBehavior.js +13 -12
  112. package/dist/esm/node/client/ClientBehavior.js.map +1 -1
  113. package/dist/esm/node/client/ClientGroupInteraction.d.ts +22 -0
  114. package/dist/esm/node/client/ClientGroupInteraction.d.ts.map +1 -0
  115. package/dist/esm/node/client/ClientGroupInteraction.js +47 -0
  116. package/dist/esm/node/client/ClientGroupInteraction.js.map +6 -0
  117. package/dist/esm/node/client/ClientNodeFactory.d.ts +2 -1
  118. package/dist/esm/node/client/ClientNodeFactory.d.ts.map +1 -1
  119. package/dist/esm/node/client/ClientNodeFactory.js.map +1 -1
  120. package/dist/esm/node/client/ClientNodeInteraction.d.ts +16 -2
  121. package/dist/esm/node/client/ClientNodeInteraction.d.ts.map +1 -1
  122. package/dist/esm/node/client/ClientNodeInteraction.js +17 -1
  123. package/dist/esm/node/client/ClientNodeInteraction.js.map +1 -1
  124. package/dist/esm/node/client/Peers.d.ts.map +1 -1
  125. package/dist/esm/node/client/Peers.js +21 -8
  126. package/dist/esm/node/client/Peers.js.map +1 -1
  127. package/dist/esm/node/integration/ProtocolService.d.ts.map +1 -1
  128. package/dist/esm/node/integration/ProtocolService.js +16 -17
  129. package/dist/esm/node/integration/ProtocolService.js.map +1 -1
  130. package/dist/esm/node/server/InteractionServer.d.ts.map +1 -1
  131. package/dist/esm/node/server/InteractionServer.js +88 -39
  132. package/dist/esm/node/server/InteractionServer.js.map +1 -1
  133. package/dist/esm/node/server/OnlineServerInteraction.js +1 -1
  134. package/dist/esm/node/server/OnlineServerInteraction.js.map +1 -1
  135. package/dist/esm/storage/client/ClientNodeStores.d.ts +2 -0
  136. package/dist/esm/storage/client/ClientNodeStores.d.ts.map +1 -1
  137. package/dist/esm/storage/client/ClientNodeStores.js +20 -1
  138. package/dist/esm/storage/client/ClientNodeStores.js.map +1 -1
  139. package/package.json +7 -7
  140. package/src/behavior/Behavior.ts +42 -4
  141. package/src/behavior/cluster/ClusterBehavior.ts +1 -1
  142. package/src/behavior/cluster/ClusterBehaviorUtil.ts +7 -7
  143. package/src/behavior/supervision/RootSupervisor.ts +4 -2
  144. package/src/behavior/system/commissioning/CommissioningClient.ts +113 -73
  145. package/src/behavior/system/commissioning/CommissioningServer.ts +25 -30
  146. package/src/behavior/system/commissioning/RemoteDescriptor.ts +4 -4
  147. package/src/behavior/system/network/NetworkClient.ts +8 -2
  148. package/src/behavior/system/network/NetworkServer.ts +1 -1
  149. package/src/behaviors/access-control/AccessControlServer.ts +1 -8
  150. package/src/behaviors/general-commissioning/GeneralCommissioningServer.ts +1 -1
  151. package/src/node/ClientGroup.ts +36 -0
  152. package/src/node/ClientNode.ts +10 -2
  153. package/src/node/client/ClientBehavior.ts +17 -17
  154. package/src/node/client/ClientEndpointInitializer.ts +1 -1
  155. package/src/node/client/ClientGroupInteraction.ts +70 -0
  156. package/src/node/client/ClientNodeFactory.ts +2 -1
  157. package/src/node/client/ClientNodeInteraction.ts +19 -5
  158. package/src/node/client/Peers.ts +23 -8
  159. package/src/node/integration/ProtocolService.ts +18 -18
  160. package/src/node/server/InteractionServer.ts +108 -57
  161. package/src/node/server/OnlineServerInteraction.ts +1 -1
  162. package/src/storage/client/ClientNodeStores.ts +25 -1
@@ -77,21 +77,14 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
77
77
  // Handle Backward compatibility to Matter.js before 0.9.1 and add the missing ACL entry if no entry was set
78
78
  // so far by the controller
79
79
  const fallbackAcl: AccessControlTypes.AccessControlEntry = {
80
- fabricIndex: fabric.fabricIndex,
81
80
  privilege: AccessControlTypes.AccessControlEntryPrivilege.Administer,
82
81
  authMode: AccessControlTypes.AccessControlEntryAuthMode.Case,
83
82
  subjects: [fabric.rootNodeId],
84
83
  targets: null, // entire node
84
+ fabricIndex: fabric.fabricIndex,
85
85
  };
86
86
  this.state.acl.push(fallbackAcl);
87
87
  fabricAcls.push(fallbackAcl);
88
- logger.warn(
89
- "Added missing ACL entry for fabric",
90
- fabric.fabricIndex,
91
- "for Node ID",
92
- fabric.rootNodeId,
93
- ". This should only happen once after upgrading to matter.js 0.9.1",
94
- );
95
88
  }
96
89
  fabric.accessControl.aclList = fabricAcls;
97
90
  fabric.accessControl.extensionEntryAccessCheck = this.extensionEntryAccessCheck.bind(this);
@@ -99,7 +99,7 @@ export class GeneralCommissioningServer extends GeneralCommissioningBehavior {
99
99
  sessions: this.env.get(SessionManager),
100
100
  expiryLength: Seconds(expiryLengthSeconds),
101
101
  maxCumulativeFailsafe: Seconds(this.state.basicCommissioningInfo.maxCumulativeFailsafeSeconds),
102
- associatedFabric: session.fabric,
102
+ session,
103
103
  });
104
104
 
105
105
  // Note - this used to be async and wait for construction internally. However that leads to race
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import type { ActionContext } from "#behavior/context/ActionContext.js";
7
+ import { Interactable } from "#protocol";
8
+ import { ServerNodeStore } from "#storage/index.js";
9
+ import { ClientNode } from "./ClientNode.js";
10
+ import { ClientGroupInteraction } from "./client/ClientGroupInteraction.js";
11
+
12
+ export class ClientGroup extends ClientNode {
13
+ #interaction?: ClientGroupInteraction;
14
+
15
+ override get isGroup() {
16
+ return true;
17
+ }
18
+
19
+ override get interaction(): Interactable<ActionContext> {
20
+ if (this.#interaction === undefined) {
21
+ this.#interaction = new ClientGroupInteraction(this);
22
+ }
23
+
24
+ return this.#interaction;
25
+ }
26
+
27
+ protected override get store() {
28
+ return this.env.get(ServerNodeStore).clientStores.storeForGroup(this);
29
+ }
30
+ }
31
+
32
+ export namespace ClientGroup {
33
+ export function is(value: unknown): value is ClientGroup {
34
+ return value instanceof ClientGroup;
35
+ }
36
+ }
@@ -53,6 +53,10 @@ export class ClientNode extends Node<ClientNode.RootEndpoint> {
53
53
  this.#matter = options.matter ?? Matter;
54
54
  }
55
55
 
56
+ get isGroup() {
57
+ return false;
58
+ }
59
+
56
60
  /**
57
61
  * Model of Matter semantics understood by this node.
58
62
  *
@@ -66,8 +70,12 @@ export class ClientNode extends Node<ClientNode.RootEndpoint> {
66
70
  return new ClientNodeEndpoints(this);
67
71
  }
68
72
 
73
+ protected get store() {
74
+ return this.env.get(ServerNodeStore).clientStores.storeForNode(this);
75
+ }
76
+
69
77
  override initialize() {
70
- const store = this.env.get(ServerNodeStore).clientStores.storeForNode(this);
78
+ const store = this.store;
71
79
 
72
80
  this.env.set(ClientNodeStore, store);
73
81
 
@@ -202,7 +210,7 @@ export class ClientNode extends Node<ClientNode.RootEndpoint> {
202
210
 
203
211
  // During early initialization commissioning state may not be loaded, so check directly in storage too
204
212
  if (!address) {
205
- address = this.env.get(ClientNodeStore).storeForEndpoint(this).peerAddress as PeerAddress | undefined;
213
+ address = this.store.storeForEndpoint(this).peerAddress as PeerAddress | undefined;
206
214
  }
207
215
 
208
216
  // Use the peer address as a log identifier if present
@@ -10,14 +10,13 @@ import { camelize, capitalize, InternalError } from "#general";
10
10
  import { AttributeModel, ClusterModel, CommandModel, FeatureBitmap, Matter } from "#model";
11
11
  import type { ClientNode } from "#node/ClientNode.js";
12
12
  import { Node } from "#node/Node.js";
13
- import { Invoke } from "#protocol";
13
+ import { ClientInteraction, Invoke } from "#protocol";
14
14
  import {
15
15
  Attribute,
16
16
  AttributeId,
17
17
  ClusterComposer,
18
18
  ClusterId,
19
19
  ClusterRegistry,
20
- ClusterType,
21
20
  Command,
22
21
  CommandId,
23
22
  MutableCluster,
@@ -137,8 +136,7 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
137
136
  // Add command implementations
138
137
  for (const id of analysis.shape.commands) {
139
138
  const name = schema.get(CommandModel, id)?.name ?? createUnknownName("command", id);
140
- const command = cluster.commands[camelize(name)];
141
- type.prototype[camelize(name, false)] = implementCommand(command);
139
+ type.prototype[camelize(name, false)] = implementCommand(camelize(name));
142
140
  }
143
141
 
144
142
  return type;
@@ -152,33 +150,35 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
152
150
  isCloned = true;
153
151
  }
154
152
 
155
- function implementCommand(command: ClusterType.Command) {
153
+ function implementCommand(command: string) {
156
154
  return async function (this: ClusterBehavior, fields?: {}) {
157
155
  const node = this.env.get(Node) as ClientNode;
158
156
 
159
- const chunks = node.interaction.invoke(
160
- Invoke(
161
- Invoke.Command<any>({
162
- endpoint: this.endpoint,
163
- cluster,
164
- command,
165
- fields,
166
- }),
167
- ),
157
+ const chunks = (node.interaction as ClientInteraction).invoke(
158
+ Invoke({
159
+ commands: [
160
+ Invoke.ConcreteCommandRequest<any>({
161
+ endpoint: this.endpoint,
162
+ cluster,
163
+ command,
164
+ fields,
165
+ }),
166
+ ],
167
+ }),
168
168
  );
169
169
 
170
170
  for await (const chunk of chunks) {
171
171
  for (const entry of chunk) {
172
- // TODO - do we need to support multiple data chunks?
172
+ // We send only one command, so we only get one response back
173
173
  switch (entry.kind) {
174
174
  case "cmd-status":
175
175
  if (entry.status !== Status.Success) {
176
176
  throw StatusResponseError.create(entry.status, undefined, entry.clusterStatus);
177
177
  }
178
- break;
178
+ return;
179
179
 
180
180
  case "cmd-response":
181
- return command.responseSchema.decodeTlv(entry.data);
181
+ return entry.data;
182
182
  }
183
183
  }
184
184
  }
@@ -49,7 +49,7 @@ export class ClientEndpointInitializer extends EndpointInitializer {
49
49
  }
50
50
 
51
51
  override createBacking(endpoint: Endpoint, type: Behavior.Type): BehaviorBacking {
52
- // Non-cluster behaviors are local operation the same server behaviors
52
+ // Non-cluster behaviors are local, operating the same server behaviors
53
53
  if ((type as ClusterBehavior.Type).cluster === undefined) {
54
54
  const store = this.structure.storeForLocal(endpoint, type);
55
55
  return new ServerBehaviorBacking(endpoint, type, store, endpoint.behaviors.optionsFor(type));
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import type { ActionContext } from "#behavior/index.js";
8
+ import { ImplementationError } from "#general";
9
+ import {
10
+ ClientInvoke,
11
+ DecodedInvokeResult,
12
+ Read,
13
+ ReadResult,
14
+ Subscribe,
15
+ SubscribeResult,
16
+ Write,
17
+ WriteResult,
18
+ } from "#protocol";
19
+ import { ClientNodeInteraction } from "./ClientNodeInteraction.js";
20
+
21
+ export class InvalidGroupOperationError extends ImplementationError {}
22
+
23
+ export class ClientGroupInteraction extends ClientNodeInteraction {
24
+ /** Groups do not support reading or subscribing to attributes */
25
+ override read(_request: Read, _context?: ActionContext): ReadResult {
26
+ throw new InvalidGroupOperationError("Groups do not support reading attributes");
27
+ }
28
+
29
+ /** Groups do not support reading or subscribing to attributes */
30
+ override async subscribe(_request: Subscribe, _context?: ActionContext): SubscribeResult {
31
+ throw new InvalidGroupOperationError("Groups do not support subscribing to attributes");
32
+ }
33
+
34
+ override async write<T extends Write>(
35
+ request: T,
36
+ context?: ActionContext,
37
+ ): WriteResult<T & { suppressResponse: true }> {
38
+ if (request.timedRequest) {
39
+ throw new InvalidGroupOperationError("Timed requests are not supported for group address writes.");
40
+ }
41
+
42
+ if (request.suppressResponse === false) {
43
+ // If flag was explicitly set to false, we cannot comply
44
+ throw new InvalidGroupOperationError("Writing attributes on a group address can not return a response.");
45
+ }
46
+
47
+ if (
48
+ request.writeRequests.some(
49
+ ({ path: { endpointId, clusterId, attributeId } }) =>
50
+ endpointId !== undefined || clusterId === undefined || attributeId === undefined,
51
+ )
52
+ ) {
53
+ throw new InvalidGroupOperationError("Not all attribute write paths are valid for group address writes.");
54
+ }
55
+
56
+ // Writing to a group does not yield a response
57
+ return super.write({ ...request, suppressResponse: true }, context);
58
+ }
59
+
60
+ override invoke(request: ClientInvoke, context?: ActionContext): DecodedInvokeResult {
61
+ if (request.invokeRequests.some(({ commandPath: { endpointId } }) => endpointId !== undefined)) {
62
+ throw new InvalidGroupOperationError("Invoking a concrete command on a group address is not supported.");
63
+ }
64
+ if (request.timedRequest) {
65
+ throw new InvalidGroupOperationError("Timed requests are not supported for group address invokes.");
66
+ }
67
+
68
+ return super.invoke(request, context);
69
+ }
70
+ }
@@ -7,6 +7,7 @@
7
7
  import type { RemoteDescriptor } from "#behavior/system/commissioning/RemoteDescriptor.js";
8
8
  import type { ImmutableSet } from "#general";
9
9
  import type { ClientNode } from "#node/ClientNode.js";
10
+ import type { PeerAddress } from "#protocol";
10
11
 
11
12
  /**
12
13
  * Create a new client node.
@@ -16,6 +17,6 @@ import type { ClientNode } from "#node/ClientNode.js";
16
17
  */
17
18
  export abstract class ClientNodeFactory {
18
19
  abstract find(descriptor: RemoteDescriptor): ClientNode | undefined;
19
- abstract create(options: ClientNode.Options): ClientNode;
20
+ abstract create(options: ClientNode.Options, peerAddress?: PeerAddress): ClientNode;
20
21
  abstract nodes: ImmutableSet<ClientNode>;
21
22
  }
@@ -7,10 +7,11 @@
7
7
  import type { ActionContext } from "#behavior/context/ActionContext.js";
8
8
  import { EndpointInitializer } from "#endpoint/properties/EndpointInitializer.js";
9
9
  import type { ClientNode } from "#node/ClientNode.js";
10
- import type {
10
+ import {
11
+ ClientInteraction,
12
+ ClientInvoke,
13
+ DecodedInvokeResult,
11
14
  Interactable,
12
- InvokeRequest,
13
- InvokeResult,
14
15
  Read,
15
16
  ReadResult,
16
17
  Subscribe,
@@ -18,7 +19,6 @@ import type {
18
19
  Write,
19
20
  WriteResult,
20
21
  } from "#protocol";
21
- import { ClientInteraction } from "#protocol";
22
22
  import { ClientEndpointInitializer } from "./ClientEndpointInitializer.js";
23
23
 
24
24
  /**
@@ -31,6 +31,12 @@ export class ClientNodeInteraction implements Interactable<ActionContext> {
31
31
  this.#node = node;
32
32
  }
33
33
 
34
+ /**
35
+ * Read chosen attributes remotely from the node. Known data versions are automatically injected into the request to
36
+ * optimize the read.
37
+ * Therefore, the returned data only contains attributes that have changed since the last read or subscription.
38
+ * TODO: Allow control of data version injection and enrich response with attribute data missing in response due to data versioning.
39
+ */
34
40
  async *read(request: Read, context?: ActionContext): ReadResult {
35
41
  request = this.structure.injectVersionFilters(request);
36
42
  const interaction = await this.#connect();
@@ -38,6 +44,10 @@ export class ClientNodeInteraction implements Interactable<ActionContext> {
38
44
  yield* this.structure.mutate(request, response);
39
45
  }
40
46
 
47
+ /**
48
+ * Subscribe to chosen attributes remotely from the node. Data are automatically updated in the storage and not
49
+ * returned. The subscription response message with the subscription id and the maxInterval is returned.
50
+ */
41
51
  async subscribe(request: Subscribe, context?: ActionContext): SubscribeResult {
42
52
  const intermediateRequest: Subscribe = {
43
53
  ...this.structure.injectVersionFilters(request),
@@ -67,11 +77,15 @@ export class ClientNodeInteraction implements Interactable<ActionContext> {
67
77
  this.#node.env.get(ClientInteraction).cancelSubscription(id);
68
78
  }
69
79
 
80
+ /**
81
+ * Write chosen attributes remotely to the node.
82
+ * The returned attribute write status information is returned.
83
+ */
70
84
  async write<T extends Write>(request: T, context?: ActionContext): WriteResult<T> {
71
85
  return (await this.#connect()).write(request, context);
72
86
  }
73
87
 
74
- async *invoke(request: InvokeRequest, context?: ActionContext): InvokeResult {
88
+ async *invoke(request: ClientInvoke, context?: ActionContext): DecodedInvokeResult {
75
89
  yield* (await this.#connect()).invoke(request, context);
76
90
  }
77
91
 
@@ -11,6 +11,7 @@ import { Discovery } from "#behavior/system/controller/discovery/Discovery.js";
11
11
  import { InstanceDiscovery } from "#behavior/system/controller/discovery/InstanceDiscovery.js";
12
12
  import { EndpointContainer } from "#endpoint/properties/EndpointContainer.js";
13
13
  import { CancelablePromise, Duration, ImplementationError, Logger, Minutes, Seconds, Time, Timestamp } from "#general";
14
+ import { ClientGroup } from "#node/ClientGroup.js";
14
15
  import { InteractionServer } from "#node/index.js";
15
16
  import { FabricManager, PeerAddress, PeerAddressStore } from "#protocol";
16
17
  import { ServerNodeStore } from "#storage/server/ServerNodeStore.js";
@@ -58,6 +59,7 @@ export class Peers extends EndpointContainer<ClientNode> {
58
59
  const factory = this.owner.env.get(ClientNodeFactory);
59
60
 
60
61
  const clientStores = this.owner.env.get(ServerNodeStore).clientStores;
62
+ // Group nodes have an in-memory only store, so all nodes restored here are ClientNode
61
63
  for (const id of clientStores.knownIds) {
62
64
  this.add(
63
65
  factory.create({
@@ -131,7 +133,7 @@ export class Peers extends EndpointContainer<ClientNode> {
131
133
  if (!node) {
132
134
  // We do not have that node till now, also not persisted, so create it
133
135
  const factory = this.owner.env.get(ClientNodeFactory);
134
- node = factory.create(options);
136
+ node = factory.create(options, peerAddress);
135
137
  await node.construction;
136
138
  this.add(node);
137
139
 
@@ -278,20 +280,33 @@ export class Peers extends EndpointContainer<ClientNode> {
278
280
 
279
281
  class Factory extends ClientNodeFactory {
280
282
  #owner: Peers;
283
+ #groupIdCounter = 0;
281
284
 
282
285
  constructor(owner: Peers) {
283
286
  super();
284
287
  this.#owner = owner;
285
288
  }
286
289
 
287
- create(options: ClientNode.Options) {
288
- if (options.id === undefined) {
289
- options.id = this.#owner.owner.env.get(ServerNodeStore).clientStores.allocateId();
290
+ create(options: ClientNode.Options, peerAddress?: PeerAddress) {
291
+ let node: ClientNode;
292
+ if (peerAddress !== undefined && PeerAddress.isGroup(peerAddress)) {
293
+ if (options.id === undefined) {
294
+ options.id = `group${++this.#groupIdCounter}`;
295
+ }
296
+ node = new ClientGroup({
297
+ ...options,
298
+ owner: this.#owner.owner,
299
+ });
300
+ } else {
301
+ if (options.id === undefined) {
302
+ options.id = this.#owner.owner.env.get(ServerNodeStore).clientStores.allocateId();
303
+ }
304
+ node = new ClientNode({
305
+ ...options,
306
+ owner: this.#owner.owner,
307
+ });
290
308
  }
291
- const node = new ClientNode({
292
- ...options,
293
- owner: this.#owner.owner,
294
- });
309
+
295
310
  node.construction.start();
296
311
  return node;
297
312
  }
@@ -35,7 +35,14 @@ import type {
35
35
  InteractionSession,
36
36
  NodeProtocol,
37
37
  } from "#protocol";
38
- import { EventTypeProtocol, FabricManager, hasRemoteActor, OccurrenceManager, Val } from "#protocol";
38
+ import {
39
+ EventTypeProtocol,
40
+ FabricManager,
41
+ hasRemoteActor,
42
+ OccurrenceManager,
43
+ toWildcardOrHexPath,
44
+ Val,
45
+ } from "#protocol";
39
46
  import {
40
47
  AttributeId,
41
48
  AttributePath,
@@ -554,7 +561,7 @@ function invokeCommand(
554
561
  const context = session as ActionContext;
555
562
 
556
563
  logger.info(
557
- "Invoke",
564
+ "Invoke «",
558
565
  Diagnostic.strong(`${path.toString()}.${command.name}`),
559
566
  session.transaction.via,
560
567
  requestDiagnostic,
@@ -629,13 +636,6 @@ function invokeCommand(
629
636
  return result as MaybePromise<Val.Struct | undefined>;
630
637
  }
631
638
 
632
- function toWildcardOrHex(name: string, value: number | bigint | undefined) {
633
- if (value === undefined) {
634
- return "*";
635
- }
636
- return `${name}:0x${value.toString(16)}`;
637
- }
638
-
639
639
  /**
640
640
  * Resolve a path into a human readable textual form for logging
641
641
  * TODO: Add a Diagnostic display formatter for this
@@ -656,35 +656,35 @@ function resolvePathForNode(node: NodeProtocol, path: AttributePath | EventPath
656
656
  : undefined;
657
657
 
658
658
  if (endpointId === undefined) {
659
- return `*.${toWildcardOrHex("", clusterId)}.${toWildcardOrHex("", elementId)}${postString}`;
659
+ return `*.${toWildcardOrHexPath("", clusterId)}.${toWildcardOrHexPath("", elementId)}${postString}`;
660
660
  }
661
661
 
662
662
  const endpoint = node[endpointId];
663
663
  if (endpoint === undefined) {
664
- return `${toWildcardOrHex("?", endpointId)}.${toWildcardOrHex("", clusterId)}.${toWildcardOrHex("", elementId)}${postString}`;
664
+ return `${toWildcardOrHexPath("?", endpointId)}.${toWildcardOrHexPath("", clusterId)}.${toWildcardOrHexPath("", elementId)}${postString}`;
665
665
  }
666
- const endpointName = toWildcardOrHex(endpoint.name, endpointId);
666
+ const endpointName = toWildcardOrHexPath(endpoint.name, endpointId);
667
667
 
668
668
  if (clusterId === undefined) {
669
- return `${endpointName}.*.${toWildcardOrHex("", elementId)}${postString}`;
669
+ return `${endpointName}.*.${toWildcardOrHexPath("", elementId)}${postString}`;
670
670
  }
671
671
 
672
672
  const cluster = endpoint[clusterId];
673
673
  if (cluster === undefined) {
674
- return `${endpointName}.${toWildcardOrHex("?", clusterId)}.${toWildcardOrHex("", elementId)}${postString}`;
674
+ return `${endpointName}.${toWildcardOrHexPath("?", clusterId)}.${toWildcardOrHexPath("", elementId)}${postString}`;
675
675
  }
676
- const clusterName = toWildcardOrHex(cluster.type.name, clusterId);
676
+ const clusterName = toWildcardOrHexPath(cluster.type.name, clusterId);
677
677
 
678
678
  if (elementId !== undefined) {
679
679
  if ("eventId" in path) {
680
680
  const event = cluster.type.events[elementId];
681
- return `${endpointName}.${clusterName}.${toWildcardOrHex(event?.name ?? "?", elementId)}${postString}`;
681
+ return `${endpointName}.${clusterName}.${toWildcardOrHexPath(event?.name ?? "?", elementId)}${postString}`;
682
682
  } else if ("attributeId" in path) {
683
683
  const attribute = cluster.type.attributes[elementId];
684
- return `${endpointName}.${clusterName}.${toWildcardOrHex(attribute?.name ?? "?", elementId)}${postString}`;
684
+ return `${endpointName}.${clusterName}.${toWildcardOrHexPath(attribute?.name ?? "?", elementId)}${postString}`;
685
685
  } else if ("commandId" in path) {
686
686
  const command = cluster.type.commands[elementId];
687
- return `${endpointName}.${clusterName}.${toWildcardOrHex(command?.name ?? "?", elementId)}${postString}`;
687
+ return `${endpointName}.${clusterName}.${toWildcardOrHexPath(command?.name ?? "?", elementId)}${postString}`;
688
688
  } else {
689
689
  throw new ImplementationError("Invalid path");
690
690
  }