@matter/nodejs-shell 0.17.9 → 0.18.0-alpha.0-20260812-4d7f2790e

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 (52) hide show
  1. package/README.md +11 -1
  2. package/dist/esm/MatterNode.js +237 -134
  3. package/dist/esm/MatterNode.js.map +1 -1
  4. package/dist/esm/app.js +13 -7
  5. package/dist/esm/app.js.map +1 -1
  6. package/dist/esm/shell/cmd_cluster-attributes.js +64 -45
  7. package/dist/esm/shell/cmd_cluster-attributes.js.map +2 -2
  8. package/dist/esm/shell/cmd_cluster-commands.js +11 -9
  9. package/dist/esm/shell/cmd_cluster-commands.js.map +1 -1
  10. package/dist/esm/shell/cmd_cluster-events.js +36 -9
  11. package/dist/esm/shell/cmd_cluster-events.js.map +1 -1
  12. package/dist/esm/shell/cmd_commission.js +119 -93
  13. package/dist/esm/shell/cmd_commission.js.map +1 -1
  14. package/dist/esm/shell/cmd_discover.js +23 -21
  15. package/dist/esm/shell/cmd_discover.js.map +1 -1
  16. package/dist/esm/shell/cmd_icd.js +46 -30
  17. package/dist/esm/shell/cmd_icd.js.map +1 -1
  18. package/dist/esm/shell/cmd_identify.js +11 -7
  19. package/dist/esm/shell/cmd_identify.js.map +1 -1
  20. package/dist/esm/shell/cmd_nodes.js +189 -168
  21. package/dist/esm/shell/cmd_nodes.js.map +1 -1
  22. package/dist/esm/shell/cmd_session.js +3 -4
  23. package/dist/esm/shell/cmd_session.js.map +1 -1
  24. package/dist/esm/shell/cmd_subscribe.js +26 -12
  25. package/dist/esm/shell/cmd_subscribe.js.map +1 -1
  26. package/dist/esm/util/ClusterEndpoint.js +61 -0
  27. package/dist/esm/util/ClusterEndpoint.js.map +6 -0
  28. package/dist/esm/util/awaitSeeded.js +31 -0
  29. package/dist/esm/util/awaitSeeded.js.map +6 -0
  30. package/dist/esm/util/diagnosticLogging.js +99 -0
  31. package/dist/esm/util/diagnosticLogging.js.map +6 -0
  32. package/dist/esm/util/legacyStorageMigration.js +188 -0
  33. package/dist/esm/util/legacyStorageMigration.js.map +6 -0
  34. package/package.json +10 -11
  35. package/src/MatterNode.ts +298 -159
  36. package/src/app.ts +17 -9
  37. package/src/shell/cmd_cluster-attributes.ts +77 -53
  38. package/src/shell/cmd_cluster-commands.ts +12 -13
  39. package/src/shell/cmd_cluster-events.ts +38 -11
  40. package/src/shell/cmd_commission.ts +136 -113
  41. package/src/shell/cmd_discover.ts +36 -22
  42. package/src/shell/cmd_icd.ts +52 -32
  43. package/src/shell/cmd_identify.ts +11 -7
  44. package/src/shell/cmd_nodes.ts +226 -199
  45. package/src/shell/cmd_session.ts +3 -5
  46. package/src/shell/cmd_subscribe.ts +37 -14
  47. package/src/shell/webassets/index.html +1 -1
  48. package/src/tsconfig.json +0 -3
  49. package/src/util/ClusterEndpoint.ts +107 -0
  50. package/src/util/awaitSeeded.ts +43 -0
  51. package/src/util/diagnosticLogging.ts +117 -0
  52. package/src/util/legacyStorageMigration.ts +275 -0
@@ -4,12 +4,17 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { Diagnostic } from "@matter/general";
7
+ import { Diagnostic, ImplementationError } from "@matter/general";
8
8
  import { AttributeModel, ClusterModel, Matter } from "@matter/model";
9
- import { AttributeId, ClusterId, EndpointNumber, ValidationError } from "@matter/types";
10
- import { SupportedAttributeClient } from "@project-chip/matter.js/cluster";
9
+ import { AttributeId, ClusterId, ClusterLookup, EndpointNumber, ValidationError } from "@matter/types";
11
10
  import type { Argv } from "yargs";
12
11
  import { MatterNode } from "../MatterNode.js";
12
+ import {
13
+ elementKnownUnsupported,
14
+ readAttributesRemote,
15
+ resolveClusterEndpoint,
16
+ ResolvedClusterEndpoint,
17
+ } from "../util/ClusterEndpoint.js";
13
18
  import { convertJsonDataWithModel } from "../util/Json.js";
14
19
 
15
20
  function generateAllAttributeHandlersForCluster(yargs: Argv, theNode: MatterNode) {
@@ -55,31 +60,32 @@ function generateAllAttributeHandlersForCluster(yargs: Argv, theNode: MatterNode
55
60
  const node = (await theNode.connectAndGetNodes(nodeId))[0];
56
61
 
57
62
  try {
58
- const interactionClient = await node.getInteractionClient();
59
- const result = await interactionClient.getMultipleAttributes({
60
- attributes: [
63
+ const values = await readAttributesRemote(
64
+ node,
65
+ [
61
66
  {
62
67
  endpointId: EndpointNumber(endpointId),
63
68
  clusterId: ClusterId(clusterId),
64
69
  attributeId: attributeId !== undefined ? AttributeId(attributeId) : undefined,
65
70
  },
66
71
  ],
67
- isFabricFiltered: fabricFiltered,
68
- });
72
+ fabricFiltered,
73
+ );
69
74
  console.log(
70
- `Attribute values for cluster ${node.nodeId.toString()}/${endpointId}/${clusterId}/${attributeId}:`,
75
+ `Attribute values for cluster ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${attributeId}:`,
71
76
  );
72
77
  for (const {
73
- path: { attributeId, attributeName },
78
+ path: { attributeId: id },
74
79
  value,
75
- } of result) {
80
+ } of values) {
81
+ const attributeName = ClusterLookup.attributeName(ClusterId(clusterId), id, node.matter);
76
82
  console.log(
77
- ` ${Diagnostic.hex(attributeId)}${attributeName !== undefined ? ` (${attributeName})` : ""}: ${Diagnostic.json(value)}`,
83
+ ` ${Diagnostic.hex(id)}${attributeName !== undefined ? ` (${attributeName})` : ""}: ${Diagnostic.json(value)}`,
78
84
  );
79
85
  }
80
86
  } catch (error) {
81
87
  console.log(
82
- `ERROR: Could not get attribute ${node.nodeId.toString()}/${endpointId}/${clusterId}/${attributeId}: ${error}`,
88
+ `ERROR: Could not get attribute ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${attributeId}: ${error}`,
83
89
  );
84
90
  }
85
91
  },
@@ -132,35 +138,31 @@ function generateClusterAttributeHandlers(yargs: Argv, cluster: ClusterModel, th
132
138
  });
133
139
  },
134
140
  async argv => {
135
- const clusterId = cluster.id;
136
141
  const { nodeId, endpointId, remote, fabricFiltered } = argv;
137
- const requestRemote = remote ? true : undefined;
138
- const node = (await theNode.connectAndGetNodes(nodeId))[0];
139
-
140
- const clusterClient = node
141
- .getDeviceById(endpointId)
142
- ?.getClusterClientById(ClusterId(clusterId!));
143
- if (clusterClient === undefined) {
144
- console.log(
145
- `ERROR: Cluster ${node.nodeId.toString()}/${endpointId}/${clusterId} not found.`,
146
- );
142
+ const resolved = await resolveClusterEndpoint(theNode, nodeId, endpointId, clusterId!);
143
+ if (resolved === undefined) {
147
144
  return;
148
145
  }
146
+ const { node, endpoint, behaviorType } = resolved;
147
+
149
148
  console.log(
150
- `Attribute values for cluster ${cluster.name} (${node.nodeId.toString()}/${endpointId}/${clusterId}):`,
149
+ `Attribute values for cluster ${cluster.name} (${node.peerAddress?.nodeId}/${endpointId}/${clusterId}):`,
151
150
  );
152
151
  for (const attribute of cluster.attributes) {
153
152
  const attributeName = attribute.propertyName;
154
- const attributeClient = clusterClient.attributes[attributeName];
155
153
  if (
156
- attributeClient === undefined ||
157
- (!remote && !(attributeClient instanceof SupportedAttributeClient))
154
+ !remote &&
155
+ elementKnownUnsupported(endpoint, behaviorType, "attributes", attributeName)
158
156
  ) {
159
157
  continue;
160
158
  }
161
- console.log(
162
- ` ${attributeName} (${attribute.id}): ${Diagnostic.json(await attributeClient.get(requestRemote, fabricFiltered))}`,
163
- );
159
+ try {
160
+ console.log(
161
+ ` ${attributeName} (${attribute.id}): ${Diagnostic.json(await getAttributeValue(resolved, clusterId!, attribute, remote, fabricFiltered))}`,
162
+ );
163
+ } catch (error) {
164
+ console.log(` ${attributeName} (${attribute.id}): ERROR: ${error}`);
165
+ }
164
166
  }
165
167
  },
166
168
  );
@@ -204,6 +206,35 @@ function generateClusterAttributeHandlers(yargs: Argv, cluster: ClusterModel, th
204
206
  return yargs;
205
207
  }
206
208
 
209
+ /** Fetch a single attribute's value: cached behavior state, or a live interaction read when `requestRemote`. */
210
+ async function getAttributeValue(
211
+ resolved: ResolvedClusterEndpoint,
212
+ clusterId: number,
213
+ attribute: AttributeModel,
214
+ requestRemote: boolean,
215
+ fabricFiltered: boolean,
216
+ ): Promise<unknown> {
217
+ const { node, endpoint, behaviorType } = resolved;
218
+ if (requestRemote) {
219
+ const values = await readAttributesRemote(
220
+ node,
221
+ [
222
+ {
223
+ endpointId: endpoint.number,
224
+ clusterId: ClusterId(clusterId),
225
+ attributeId: AttributeId(attribute.id),
226
+ },
227
+ ],
228
+ fabricFiltered,
229
+ );
230
+ if (values.length === 0) {
231
+ throw new ImplementationError(`No value returned for attribute ${attribute.propertyName}`);
232
+ }
233
+ return values[0].value;
234
+ }
235
+ return endpoint.stateOf(behaviorType.id)[attribute.propertyName];
236
+ }
237
+
207
238
  function generateAttributeReadHandler(
208
239
  yargs: Argv,
209
240
  clusterId: number,
@@ -242,24 +273,21 @@ function generateAttributeReadHandler(
242
273
  }),
243
274
  async argv => {
244
275
  const { nodeId, endpointId, remote, fabricFiltered } = argv;
245
- const requestRemote = remote ? true : undefined;
246
- const node = (await theNode.connectAndGetNodes(nodeId))[0];
247
-
248
- const clusterClient = node.getDeviceById(endpointId)?.getClusterClientById(ClusterId(clusterId));
249
- if (clusterClient === undefined) {
250
- console.log(`ERROR: Cluster ${node.nodeId.toString()}/${endpointId}/${clusterId} not found.`);
276
+ const resolved = await resolveClusterEndpoint(theNode, nodeId, endpointId, clusterId);
277
+ if (resolved === undefined) {
251
278
  return;
252
279
  }
253
- const attributeClient = clusterClient.attributes[attributeName];
254
- if (attributeClient === undefined || (!remote && !(attributeClient instanceof SupportedAttributeClient))) {
280
+ const { node, endpoint, behaviorType } = resolved;
281
+
282
+ if (!remote && elementKnownUnsupported(endpoint, behaviorType, "attributes", attributeName)) {
255
283
  console.log(
256
- `ERROR: Attribute ${node.nodeId.toString()}/${endpointId}/${clusterId}/${attribute.id} not supported by the device.`,
284
+ `ERROR: Attribute ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${attribute.id} not supported by the device.`,
257
285
  );
258
286
  return;
259
287
  }
260
288
  try {
261
289
  console.log(
262
- `Attribute value for ${attributeName} ${node.nodeId.toString()}/${endpointId}/${clusterId}/${attribute.id}: ${Diagnostic.json(await attributeClient.get(requestRemote, fabricFiltered))}`,
290
+ `Attribute value for ${attributeName} ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${attribute.id}: ${Diagnostic.json(await getAttributeValue(resolved, clusterId, attribute, remote, fabricFiltered))}`,
263
291
  );
264
292
  } catch (error) {
265
293
  console.log(`ERROR: Could not get attribute ${attribute.name}: ${error}`);
@@ -275,8 +303,6 @@ function generateAttributeWriteHandler(
275
303
  attribute: AttributeModel,
276
304
  theNode: MatterNode,
277
305
  ) {
278
- //console.log("Generating attribute handler for ", attribute.name, attribute);
279
- //console.log(attribute.definingModel);
280
306
  const attributeName = attribute.propertyName;
281
307
  const typeHint = `${attribute.type}${attribute.definingModel === undefined ? "" : " as JSON string"}`;
282
308
  return yargs.command(
@@ -321,17 +347,15 @@ function generateAttributeWriteHandler(
321
347
  }
322
348
  }
323
349
 
324
- const node = (await theNode.connectAndGetNodes(nodeId))[0];
325
-
326
- const clusterClient = node.getDeviceById(endpointId)?.getClusterClientById(ClusterId(clusterId));
327
- if (clusterClient === undefined) {
328
- console.log(`ERROR: Cluster ${node.nodeId.toString()}/${endpointId}/${clusterId} not found.`);
350
+ const resolved = await resolveClusterEndpoint(theNode, nodeId, endpointId, clusterId);
351
+ if (resolved === undefined) {
329
352
  return;
330
353
  }
331
- const attributeClient = clusterClient.attributes[attributeName];
332
- if (!force && !(attributeClient instanceof SupportedAttributeClient)) {
354
+ const { node, endpoint, behaviorType } = resolved;
355
+
356
+ if (!force && elementKnownUnsupported(endpoint, behaviorType, "attributes", attributeName)) {
333
357
  console.log(
334
- `ERROR: Attribute ${node.nodeId.toString()}/${endpointId}/${clusterId}/${attribute.id} not supported by the device.`,
358
+ `ERROR: Attribute ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${attribute.id} not supported by the device.`,
335
359
  );
336
360
  return;
337
361
  }
@@ -339,9 +363,9 @@ function generateAttributeWriteHandler(
339
363
  try {
340
364
  parsedValue = convertJsonDataWithModel(attribute, parsedValue);
341
365
 
342
- await attributeClient.set(parsedValue);
366
+ await endpoint.setStateOf(behaviorType.id, { [attributeName]: parsedValue });
343
367
  console.log(
344
- `Attribute ${attributeName} ${node.nodeId.toString()}/${endpointId}/${clusterId}/${attribute.id} set to ${Diagnostic.json(value)}`,
368
+ `Attribute ${attributeName} ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${attribute.id} set to ${Diagnostic.json(value)}`,
345
369
  );
346
370
  } catch (error) {
347
371
  if (error instanceof ValidationError) {
@@ -6,9 +6,10 @@
6
6
 
7
7
  import { Diagnostic } from "@matter/general";
8
8
  import { ClusterModel, CommandModel, Matter } from "@matter/model";
9
- import { ClusterId, ValidationError } from "@matter/types";
9
+ import { ValidationError } from "@matter/types";
10
10
  import type { Argv } from "yargs";
11
11
  import { MatterNode } from "../MatterNode.js";
12
+ import { elementKnownUnsupported, resolveClusterEndpoint } from "../util/ClusterEndpoint.js";
12
13
  import { convertJsonDataWithModel } from "../util/Json.js";
13
14
 
14
15
  function generateAllCommandHandlersForCluster(yargs: Argv, theNode: MatterNode) {
@@ -48,9 +49,6 @@ function generateCommandHandler(
48
49
  command: CommandModel,
49
50
  theNode: MatterNode,
50
51
  ) {
51
- //console.log("Generating command handler for ", command.name, JSON.stringify(command));
52
- //console.log(command.definingModel);
53
-
54
52
  if (command.definingModel !== undefined) {
55
53
  // If command has parameters for the call
56
54
  return yargs.command(
@@ -126,15 +124,16 @@ async function executeCommand(
126
124
  ) {
127
125
  const commandName = command.propertyName;
128
126
 
129
- const node = (await theNode.connectAndGetNodes(nodeId))[0];
130
-
131
- const clusterClient = node.getDeviceById(endpointId)?.getClusterClientById(ClusterId(clusterId));
132
- if (clusterClient === undefined) {
133
- console.log(`ERROR: Cluster ${node.nodeId.toString()}/${endpointId}/${clusterId} not found.`);
127
+ const resolved = await resolveClusterEndpoint(theNode, nodeId, endpointId, clusterId);
128
+ if (resolved === undefined) {
134
129
  return;
135
130
  }
136
- if (clusterClient.commands[commandName] === undefined) {
137
- console.log(`ERROR: Command ${node.nodeId.toString()}/${endpointId}/${clusterId}/${command.id} not supported.`);
131
+ const { node, endpoint, behaviorType } = resolved;
132
+
133
+ if (elementKnownUnsupported(endpoint, behaviorType, "commands", commandName)) {
134
+ console.log(
135
+ `ERROR: Command ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${command.id} not supported.`,
136
+ );
138
137
  return;
139
138
  }
140
139
  try {
@@ -142,9 +141,9 @@ async function executeCommand(
142
141
  requestData = convertJsonDataWithModel(command, requestData);
143
142
  }
144
143
 
145
- const result = await clusterClient.commands[commandName](requestData);
144
+ const result = await endpoint.commandsOf(behaviorType.id)[commandName](requestData);
146
145
  console.log(
147
- `Command ${command.name} ${node.nodeId.toString()}/${endpointId}/${clusterId}/${command.id} invoked ${requestData ? `with ${Diagnostic.json(requestData)}` : ""}`,
146
+ `Command ${command.name} ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${command.id} invoked ${requestData ? `with ${Diagnostic.json(requestData)}` : ""}`,
148
147
  );
149
148
  if (result !== undefined) {
150
149
  console.log(`Result: ${Diagnostic.json(result)}`);
@@ -6,9 +6,10 @@
6
6
 
7
7
  import { Diagnostic } from "@matter/general";
8
8
  import { ClusterModel, EventModel, Matter } from "@matter/model";
9
- import { ClusterId } from "@matter/types";
9
+ import { ClusterId, EventId } from "@matter/types";
10
10
  import type { Argv } from "yargs";
11
11
  import { MatterNode } from "../MatterNode.js";
12
+ import { readEventsRemote, resolveClusterEndpoint } from "../util/ClusterEndpoint.js";
12
13
 
13
14
  function generateAllEventHandlersForCluster(yargs: Argv, theNode: MatterNode) {
14
15
  Matter.clusters.forEach(cluster => {
@@ -47,7 +48,6 @@ function generateEventHandler(
47
48
  event: EventModel,
48
49
  theNode: MatterNode,
49
50
  ) {
50
- //console.log("Generating event handler for ", event.name, JSON.stringify(event));
51
51
  const eventName = event.propertyName;
52
52
  return yargs.command(
53
53
  [`${event.name.toLowerCase()} <node-id> <endpoint-id>`, `0x${event.id.toString(16)}`],
@@ -66,17 +66,44 @@ function generateEventHandler(
66
66
  }),
67
67
  async argv => {
68
68
  const { nodeId, endpointId } = argv;
69
- const node = (await theNode.connectAndGetNodes(nodeId))[0];
70
-
71
- const clusterClient = node.getDeviceById(endpointId)?.getClusterClientById(ClusterId(clusterId));
72
- if (clusterClient === undefined) {
73
- console.log(`ERROR: Cluster ${node.nodeId.toString()}/${endpointId}/${clusterId} not found.`);
69
+ const resolved = await resolveClusterEndpoint(theNode, nodeId, endpointId, clusterId);
70
+ if (resolved === undefined) {
74
71
  return;
75
72
  }
76
- const eventClient = clusterClient.events[eventName];
77
- console.log(
78
- `Event value for ${eventName} ${node.nodeId.toString()}/${endpointId}/${clusterId}/${event.id}: ${Diagnostic.json(await eventClient.get())}`,
79
- );
73
+ const { node, endpoint } = resolved;
74
+
75
+ try {
76
+ // No local event cache; every read is a live interaction round trip, same as the legacy EventClient.
77
+ const reports = await readEventsRemote(
78
+ node,
79
+ [{ endpointId: endpoint.number, clusterId: ClusterId(clusterId), eventId: EventId(event.id) }],
80
+ true,
81
+ );
82
+ const events = reports.map(
83
+ ({
84
+ number,
85
+ priority,
86
+ epochTimestamp,
87
+ systemTimestamp,
88
+ deltaEpochTimestamp,
89
+ deltaSystemTimestamp,
90
+ value,
91
+ }) => ({
92
+ eventNumber: number,
93
+ priority,
94
+ epochTimestamp,
95
+ systemTimestamp,
96
+ deltaEpochTimestamp,
97
+ deltaSystemTimestamp,
98
+ data: value,
99
+ }),
100
+ );
101
+ console.log(
102
+ `Event value for ${eventName} ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${event.id}: ${Diagnostic.json(events)}`,
103
+ );
104
+ } catch (error) {
105
+ console.log(`ERROR: Could not get event ${event.name}: ${error}`);
106
+ }
80
107
  },
81
108
  );
82
109
  }