@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.
- package/README.md +11 -1
- package/dist/esm/MatterNode.js +237 -134
- package/dist/esm/MatterNode.js.map +1 -1
- package/dist/esm/app.js +13 -7
- package/dist/esm/app.js.map +1 -1
- package/dist/esm/shell/cmd_cluster-attributes.js +64 -45
- package/dist/esm/shell/cmd_cluster-attributes.js.map +2 -2
- package/dist/esm/shell/cmd_cluster-commands.js +11 -9
- package/dist/esm/shell/cmd_cluster-commands.js.map +1 -1
- package/dist/esm/shell/cmd_cluster-events.js +36 -9
- package/dist/esm/shell/cmd_cluster-events.js.map +1 -1
- package/dist/esm/shell/cmd_commission.js +119 -93
- package/dist/esm/shell/cmd_commission.js.map +1 -1
- package/dist/esm/shell/cmd_discover.js +23 -21
- package/dist/esm/shell/cmd_discover.js.map +1 -1
- package/dist/esm/shell/cmd_icd.js +46 -30
- package/dist/esm/shell/cmd_icd.js.map +1 -1
- package/dist/esm/shell/cmd_identify.js +11 -7
- package/dist/esm/shell/cmd_identify.js.map +1 -1
- package/dist/esm/shell/cmd_nodes.js +189 -168
- package/dist/esm/shell/cmd_nodes.js.map +1 -1
- package/dist/esm/shell/cmd_session.js +3 -4
- package/dist/esm/shell/cmd_session.js.map +1 -1
- package/dist/esm/shell/cmd_subscribe.js +26 -12
- package/dist/esm/shell/cmd_subscribe.js.map +1 -1
- package/dist/esm/util/ClusterEndpoint.js +61 -0
- package/dist/esm/util/ClusterEndpoint.js.map +6 -0
- package/dist/esm/util/awaitSeeded.js +31 -0
- package/dist/esm/util/awaitSeeded.js.map +6 -0
- package/dist/esm/util/diagnosticLogging.js +99 -0
- package/dist/esm/util/diagnosticLogging.js.map +6 -0
- package/dist/esm/util/legacyStorageMigration.js +188 -0
- package/dist/esm/util/legacyStorageMigration.js.map +6 -0
- package/package.json +10 -11
- package/src/MatterNode.ts +298 -159
- package/src/app.ts +17 -9
- package/src/shell/cmd_cluster-attributes.ts +77 -53
- package/src/shell/cmd_cluster-commands.ts +12 -13
- package/src/shell/cmd_cluster-events.ts +38 -11
- package/src/shell/cmd_commission.ts +136 -113
- package/src/shell/cmd_discover.ts +36 -22
- package/src/shell/cmd_icd.ts +52 -32
- package/src/shell/cmd_identify.ts +11 -7
- package/src/shell/cmd_nodes.ts +226 -199
- package/src/shell/cmd_session.ts +3 -5
- package/src/shell/cmd_subscribe.ts +37 -14
- package/src/shell/webassets/index.html +1 -1
- package/src/tsconfig.json +0 -3
- package/src/util/ClusterEndpoint.ts +107 -0
- package/src/util/awaitSeeded.ts +43 -0
- package/src/util/diagnosticLogging.ts +117 -0
- 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
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
68
|
-
|
|
72
|
+
fabricFiltered,
|
|
73
|
+
);
|
|
69
74
|
console.log(
|
|
70
|
-
`Attribute values for cluster ${node.nodeId
|
|
75
|
+
`Attribute values for cluster ${node.peerAddress?.nodeId}/${endpointId}/${clusterId}/${attributeId}:`,
|
|
71
76
|
);
|
|
72
77
|
for (const {
|
|
73
|
-
path: { attributeId
|
|
78
|
+
path: { attributeId: id },
|
|
74
79
|
value,
|
|
75
|
-
} of
|
|
80
|
+
} of values) {
|
|
81
|
+
const attributeName = ClusterLookup.attributeName(ClusterId(clusterId), id, node.matter);
|
|
76
82
|
console.log(
|
|
77
|
-
` ${Diagnostic.hex(
|
|
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
|
|
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
|
|
138
|
-
|
|
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
|
|
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
|
-
|
|
157
|
-
(
|
|
154
|
+
!remote &&
|
|
155
|
+
elementKnownUnsupported(endpoint, behaviorType, "attributes", attributeName)
|
|
158
156
|
) {
|
|
159
157
|
continue;
|
|
160
158
|
}
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
246
|
-
|
|
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
|
|
254
|
-
|
|
280
|
+
const { node, endpoint, behaviorType } = resolved;
|
|
281
|
+
|
|
282
|
+
if (!remote && elementKnownUnsupported(endpoint, behaviorType, "attributes", attributeName)) {
|
|
255
283
|
console.log(
|
|
256
|
-
`ERROR: Attribute ${node.nodeId
|
|
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
|
|
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
|
|
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
|
|
332
|
-
|
|
354
|
+
const { node, endpoint, behaviorType } = resolved;
|
|
355
|
+
|
|
356
|
+
if (!force && elementKnownUnsupported(endpoint, behaviorType, "attributes", attributeName)) {
|
|
333
357
|
console.log(
|
|
334
|
-
`ERROR: Attribute ${node.nodeId
|
|
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
|
|
366
|
+
await endpoint.setStateOf(behaviorType.id, { [attributeName]: parsedValue });
|
|
343
367
|
console.log(
|
|
344
|
-
`Attribute ${attributeName} ${node.nodeId
|
|
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 {
|
|
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
|
|
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
|
-
|
|
137
|
-
|
|
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
|
|
144
|
+
const result = await endpoint.commandsOf(behaviorType.id)[commandName](requestData);
|
|
146
145
|
console.log(
|
|
147
|
-
`Command ${command.name} ${node.nodeId
|
|
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
|
|
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
|
|
77
|
-
|
|
78
|
-
|
|
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
|
}
|