@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
package/src/shell/cmd_nodes.ts
CHANGED
|
@@ -4,20 +4,46 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import {
|
|
8
|
+
capitalize,
|
|
9
|
+
ChannelType,
|
|
10
|
+
decamelize,
|
|
11
|
+
Diagnostic,
|
|
12
|
+
ImplementationError,
|
|
13
|
+
InternalError,
|
|
14
|
+
Logger,
|
|
15
|
+
Millis,
|
|
16
|
+
ServerAddress,
|
|
17
|
+
} from "@matter/general";
|
|
18
|
+
import {
|
|
19
|
+
ClientNode,
|
|
20
|
+
CommissioningClient,
|
|
21
|
+
NetworkClient,
|
|
22
|
+
NodeConnectionState,
|
|
23
|
+
SoftwareUpdateManager,
|
|
24
|
+
} from "@matter/node";
|
|
25
|
+
import { BasicInformationClient } from "@matter/node/behaviors/basic-information";
|
|
26
|
+
import { FabricAuthority, PeerAddress, PeerSet } from "@matter/protocol";
|
|
10
27
|
import { FabricIndex, NodeId, VendorId } from "@matter/types";
|
|
11
|
-
import { NodeStateInformation } from "@project-chip/matter.js/device";
|
|
12
28
|
import type { Argv } from "yargs";
|
|
13
|
-
import {
|
|
29
|
+
import { MatterNode } from "../MatterNode.js";
|
|
30
|
+
import { awaitSeeded } from "../util/awaitSeeded.js";
|
|
14
31
|
import { resolveDclMode, withDclModeOption } from "./ota-dcl-mode.js";
|
|
15
32
|
|
|
33
|
+
const logger = Logger.get("cmd_nodes");
|
|
34
|
+
|
|
35
|
+
/** Look up a commissioned peer by node id across the controller's default admin fabric. */
|
|
36
|
+
function findCommissionedNode(theNode: MatterNode, nodeId: NodeId): ClientNode | undefined {
|
|
37
|
+
return theNode.node.peers.commissioned.find(peer => peer.peerAddress?.nodeId === nodeId);
|
|
38
|
+
}
|
|
39
|
+
|
|
16
40
|
/** Parse a `udp://host:port` / `tcp://host:port` URL (IPv6 host in brackets) into a {@link ServerAddress}. */
|
|
17
41
|
function parseFallbackAddress(input: string): ServerAddress {
|
|
18
42
|
const match = /^(udp|tcp):\/\/(.+)$/i.exec(input);
|
|
19
43
|
if (!match) {
|
|
20
|
-
throw new
|
|
44
|
+
throw new ImplementationError(
|
|
45
|
+
`Invalid address "${input}". Expected udp://<host>:<port> or tcp://<host>:<port>`,
|
|
46
|
+
);
|
|
21
47
|
}
|
|
22
48
|
const type = match[1].toLowerCase() === "tcp" ? "tcp" : "udp";
|
|
23
49
|
const rest = match[2];
|
|
@@ -27,14 +53,14 @@ function parseFallbackAddress(input: string): ServerAddress {
|
|
|
27
53
|
if (rest.startsWith("[")) {
|
|
28
54
|
const end = rest.indexOf("]");
|
|
29
55
|
if (end === -1 || rest[end + 1] !== ":") {
|
|
30
|
-
throw new
|
|
56
|
+
throw new ImplementationError(`Invalid IPv6 address "${input}". Expected ${type}://[<ipv6>]:<port>`);
|
|
31
57
|
}
|
|
32
58
|
ip = rest.slice(1, end);
|
|
33
59
|
portStr = rest.slice(end + 2);
|
|
34
60
|
} else {
|
|
35
61
|
const idx = rest.lastIndexOf(":");
|
|
36
62
|
if (idx === -1) {
|
|
37
|
-
throw new
|
|
63
|
+
throw new ImplementationError(`Missing port in "${input}". Expected ${type}://<host>:<port>`);
|
|
38
64
|
}
|
|
39
65
|
ip = rest.slice(0, idx);
|
|
40
66
|
portStr = rest.slice(idx + 1);
|
|
@@ -42,7 +68,9 @@ function parseFallbackAddress(input: string): ServerAddress {
|
|
|
42
68
|
|
|
43
69
|
const port = Number(portStr);
|
|
44
70
|
if (!ip.length || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
45
|
-
throw new
|
|
71
|
+
throw new ImplementationError(
|
|
72
|
+
`Invalid host/port in "${input}". Expected ${type}://<host>:<port> with port 1-65535`,
|
|
73
|
+
);
|
|
46
74
|
}
|
|
47
75
|
|
|
48
76
|
return { ip, port, type };
|
|
@@ -68,27 +96,27 @@ export default function commands(theNode: MatterNode) {
|
|
|
68
96
|
async argv => {
|
|
69
97
|
const { status } = argv;
|
|
70
98
|
await theNode.start();
|
|
71
|
-
|
|
72
|
-
throw new Error("CommissioningController not initialized");
|
|
73
|
-
}
|
|
99
|
+
const peers = theNode.node.peers.commissioned;
|
|
74
100
|
switch (status) {
|
|
75
101
|
case "commissioned": {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
for (const peer of peers) {
|
|
103
|
+
const { addresses, deviceName } = peer.state.commissioning;
|
|
104
|
+
console.log({
|
|
105
|
+
nodeId: peer.peerAddress?.nodeId.toString(),
|
|
106
|
+
operationalAddress: addresses?.length
|
|
107
|
+
? ServerAddress.urlFor(addresses[0])
|
|
108
|
+
: undefined,
|
|
109
|
+
advertisedName: deviceName,
|
|
110
|
+
basicInformation: peer.maybeStateOf(BasicInformationClient),
|
|
84
111
|
});
|
|
112
|
+
}
|
|
85
113
|
break;
|
|
86
114
|
}
|
|
87
115
|
case "connected": {
|
|
88
|
-
const nodeIds =
|
|
89
|
-
.
|
|
90
|
-
.
|
|
91
|
-
console.log(nodeIds.map(nodeId => nodeId
|
|
116
|
+
const nodeIds = peers
|
|
117
|
+
.filter(peer => peer.lifecycle.isConnected)
|
|
118
|
+
.map(peer => peer.peerAddress?.nodeId);
|
|
119
|
+
console.log(nodeIds.map(nodeId => nodeId?.toString()));
|
|
92
120
|
break;
|
|
93
121
|
}
|
|
94
122
|
}
|
|
@@ -107,9 +135,12 @@ export default function commands(theNode: MatterNode) {
|
|
|
107
135
|
async argv => {
|
|
108
136
|
const { nodeId } = argv;
|
|
109
137
|
const node = (await theNode.connectAndGetNodes(nodeId))[0];
|
|
138
|
+
if (!(await awaitSeeded(node))) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
110
141
|
|
|
111
|
-
console.log("Logging structure of Node ", node.nodeId.toString());
|
|
112
|
-
|
|
142
|
+
console.log("Logging structure of Node ", node.peerAddress?.nodeId.toString());
|
|
143
|
+
logger.info(node);
|
|
113
144
|
},
|
|
114
145
|
)
|
|
115
146
|
.command(
|
|
@@ -125,12 +156,13 @@ export default function commands(theNode: MatterNode) {
|
|
|
125
156
|
async argv => {
|
|
126
157
|
const { nodeId: nodeIdStr } = argv;
|
|
127
158
|
await theNode.start();
|
|
128
|
-
if (theNode.commissioningController === undefined) {
|
|
129
|
-
throw new Error("CommissioningController not initialized");
|
|
130
|
-
}
|
|
131
159
|
|
|
132
160
|
const nodeId = NodeId(BigInt(nodeIdStr));
|
|
133
|
-
const peerAddress = theNode
|
|
161
|
+
const peerAddress = findCommissionedNode(theNode, nodeId)?.peerAddress;
|
|
162
|
+
if (peerAddress === undefined) {
|
|
163
|
+
console.log(`Node ${nodeIdStr} not commissioned`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
134
166
|
const peerSet = theNode.node.env.get(PeerSet);
|
|
135
167
|
const peer = peerSet.for(peerAddress);
|
|
136
168
|
|
|
@@ -209,30 +241,13 @@ export default function commands(theNode: MatterNode) {
|
|
|
209
241
|
},
|
|
210
242
|
async argv => {
|
|
211
243
|
const { nodeId: nodeIdStr, maxSubscriptionInterval, minSubscriptionInterval } = argv;
|
|
212
|
-
await theNode.start();
|
|
213
|
-
if (theNode.commissioningController === undefined) {
|
|
214
|
-
throw new Error("CommissioningController not initialized");
|
|
215
|
-
}
|
|
216
|
-
let nodeIds = theNode.commissioningController.getCommissionedNodes();
|
|
217
|
-
if (nodeIdStr !== "all") {
|
|
218
|
-
const cmdNodeId = NodeId(BigInt(nodeIdStr));
|
|
219
|
-
nodeIds = nodeIds.filter(nodeId => nodeId === cmdNodeId);
|
|
220
|
-
if (!nodeIds.length) {
|
|
221
|
-
throw new Error(`Node ${nodeIdStr} not commissioned`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
244
|
const autoSubscribe = minSubscriptionInterval !== undefined;
|
|
226
245
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
subscribeMaxIntervalCeilingSeconds: autoSubscribe ? maxSubscriptionInterval : undefined,
|
|
233
|
-
...createDiagnosticCallbacks(),
|
|
234
|
-
});
|
|
235
|
-
}
|
|
246
|
+
await theNode.connectAndGetNodes(nodeIdStr !== "all" ? nodeIdStr : undefined, {
|
|
247
|
+
autoSubscribe,
|
|
248
|
+
subscribeMinIntervalFloorSeconds: autoSubscribe ? minSubscriptionInterval : undefined,
|
|
249
|
+
subscribeMaxIntervalCeilingSeconds: autoSubscribe ? maxSubscriptionInterval : undefined,
|
|
250
|
+
});
|
|
236
251
|
},
|
|
237
252
|
)
|
|
238
253
|
.command(
|
|
@@ -247,27 +262,21 @@ export default function commands(theNode: MatterNode) {
|
|
|
247
262
|
},
|
|
248
263
|
async argv => {
|
|
249
264
|
const { nodeId: nodeIdStr } = argv;
|
|
250
|
-
|
|
251
|
-
console.log("Controller not initialized, nothing to disconnect.");
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
265
|
+
await theNode.start();
|
|
254
266
|
|
|
255
|
-
let
|
|
267
|
+
let nodes = theNode.node.peers.commissioned;
|
|
256
268
|
if (nodeIdStr !== "all") {
|
|
257
269
|
const cmdNodeId = NodeId(BigInt(nodeIdStr));
|
|
258
|
-
|
|
259
|
-
if (!
|
|
260
|
-
throw new
|
|
270
|
+
nodes = nodes.filter(node => node.peerAddress?.nodeId === cmdNodeId);
|
|
271
|
+
if (!nodes.length) {
|
|
272
|
+
throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
|
|
261
273
|
}
|
|
262
274
|
}
|
|
263
275
|
|
|
264
|
-
for (const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
await node.disconnect();
|
|
276
|
+
for (const node of nodes) {
|
|
277
|
+
// Stop (not disable): peers stay enabled for on-demand reconnect, and the sweep being off
|
|
278
|
+
// keeps them disconnected across restarts anyway.
|
|
279
|
+
await node.stop();
|
|
271
280
|
}
|
|
272
281
|
},
|
|
273
282
|
)
|
|
@@ -285,33 +294,25 @@ export default function commands(theNode: MatterNode) {
|
|
|
285
294
|
async argv => {
|
|
286
295
|
const { nodeIds: nodeIdStr } = argv;
|
|
287
296
|
await theNode.start();
|
|
288
|
-
|
|
289
|
-
throw new Error("CommissioningController not initialized");
|
|
290
|
-
}
|
|
291
|
-
let nodeIds = theNode.commissioningController.getCommissionedNodes();
|
|
297
|
+
let nodes = theNode.node.peers.commissioned;
|
|
292
298
|
if (nodeIdStr !== "all") {
|
|
293
299
|
const nodeIdList = nodeIdStr.split(",").map(nodeId => NodeId(BigInt(nodeId)));
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
300
|
+
nodes = nodes.filter(
|
|
301
|
+
node => node.peerAddress !== undefined && nodeIdList.includes(node.peerAddress.nodeId),
|
|
302
|
+
);
|
|
303
|
+
if (!nodes.length) {
|
|
304
|
+
throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
|
|
297
305
|
}
|
|
298
306
|
}
|
|
299
307
|
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
);
|
|
309
|
-
} else {
|
|
310
|
-
const basicInfo = node.basicInformation;
|
|
311
|
-
console.log(
|
|
312
|
-
`Node ${nodeIdToProcess}: Node Status: ${capitalize(decamelize(NodeStateInformation[node.connectionState], " "))}${basicInfo !== undefined ? ` (${basicInfo.vendorName} ${basicInfo.productName})` : ""}`,
|
|
313
|
-
);
|
|
314
|
-
}
|
|
308
|
+
for (const node of nodes) {
|
|
309
|
+
const basicInfo = node.maybeStateOf(BasicInformationClient);
|
|
310
|
+
const status = capitalize(
|
|
311
|
+
decamelize(NodeConnectionState[node.lifecycle.connectionState], " "),
|
|
312
|
+
);
|
|
313
|
+
console.log(
|
|
314
|
+
`Node ${node.peerAddress?.nodeId}: Node Status: ${status}${basicInfo !== undefined ? ` (${basicInfo.vendorName} ${basicInfo.productName})` : ""}`,
|
|
315
|
+
);
|
|
315
316
|
}
|
|
316
317
|
},
|
|
317
318
|
)
|
|
@@ -336,20 +337,22 @@ export default function commands(theNode: MatterNode) {
|
|
|
336
337
|
async argv => {
|
|
337
338
|
const { nodeId: nodeIdStr, preference } = argv;
|
|
338
339
|
await theNode.start();
|
|
339
|
-
if (theNode.commissioningController === undefined) {
|
|
340
|
-
throw new Error("CommissioningController not initialized");
|
|
341
|
-
}
|
|
342
340
|
|
|
343
341
|
const nodeId = NodeId(BigInt(nodeIdStr));
|
|
344
|
-
const node =
|
|
342
|
+
const node = findCommissionedNode(theNode, nodeId);
|
|
343
|
+
if (node === undefined) {
|
|
344
|
+
throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
|
|
345
|
+
}
|
|
346
|
+
const peerAddress = node.peerAddress;
|
|
347
|
+
if (peerAddress === undefined) {
|
|
348
|
+
throw new ImplementationError(`Node ${nodeIdStr} has no peer address`);
|
|
349
|
+
}
|
|
345
350
|
|
|
346
351
|
const pref = preference === "on" ? "tcp" : preference === "off" ? "udp" : undefined;
|
|
347
|
-
await node.
|
|
352
|
+
await node.setStateOf(NetworkClient, { transportPreference: pref });
|
|
348
353
|
|
|
349
354
|
// Also update the protocol-level peer preference
|
|
350
|
-
const peer = theNode.node.env
|
|
351
|
-
.get(PeerSet)
|
|
352
|
-
.for(theNode.commissioningController.fabric.addressOf(nodeId));
|
|
355
|
+
const peer = theNode.node.env.get(PeerSet).for(peerAddress);
|
|
353
356
|
if (peer) {
|
|
354
357
|
peer.transportPreference = pref === "tcp" ? ChannelType.TCP : undefined;
|
|
355
358
|
}
|
|
@@ -377,13 +380,13 @@ export default function commands(theNode: MatterNode) {
|
|
|
377
380
|
async argv => {
|
|
378
381
|
const { nodeId: nodeIdStr } = argv;
|
|
379
382
|
await theNode.start();
|
|
380
|
-
if (theNode.commissioningController === undefined) {
|
|
381
|
-
throw new Error("CommissioningController not initialized");
|
|
382
|
-
}
|
|
383
383
|
|
|
384
384
|
const nodeId = NodeId(BigInt(nodeIdStr));
|
|
385
|
-
const node =
|
|
386
|
-
|
|
385
|
+
const node = findCommissionedNode(theNode, nodeId);
|
|
386
|
+
if (node === undefined) {
|
|
387
|
+
throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
|
|
388
|
+
}
|
|
389
|
+
const addresses = node.state.commissioning.addresses;
|
|
387
390
|
|
|
388
391
|
if (!addresses?.length) {
|
|
389
392
|
console.log(`Node ${nodeIdStr} has no fallback addresses stored`);
|
|
@@ -415,21 +418,21 @@ export default function commands(theNode: MatterNode) {
|
|
|
415
418
|
async argv => {
|
|
416
419
|
const { nodeId: nodeIdStr, address: addressStr } = argv;
|
|
417
420
|
await theNode.start();
|
|
418
|
-
if (theNode.commissioningController === undefined) {
|
|
419
|
-
throw new Error("CommissioningController not initialized");
|
|
420
|
-
}
|
|
421
421
|
|
|
422
422
|
const nodeId = NodeId(BigInt(nodeIdStr));
|
|
423
|
-
const node =
|
|
423
|
+
const node = findCommissionedNode(theNode, nodeId);
|
|
424
|
+
if (node === undefined) {
|
|
425
|
+
throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
|
|
426
|
+
}
|
|
424
427
|
|
|
425
428
|
if (addressStr === "drop") {
|
|
426
|
-
await node.
|
|
429
|
+
await node.setStateOf(CommissioningClient, { addresses: undefined });
|
|
427
430
|
console.log(`Fallback address for node ${nodeIdStr} dropped`);
|
|
428
431
|
return;
|
|
429
432
|
}
|
|
430
433
|
|
|
431
434
|
const address = parseFallbackAddress(addressStr);
|
|
432
|
-
await node.
|
|
435
|
+
await node.setStateOf(CommissioningClient, { addresses: [address] });
|
|
433
436
|
console.log(
|
|
434
437
|
`Fallback address for node ${nodeIdStr} set to ${ServerAddress.urlFor(address)}`,
|
|
435
438
|
);
|
|
@@ -465,29 +468,25 @@ export default function commands(theNode: MatterNode) {
|
|
|
465
468
|
async argv => {
|
|
466
469
|
const { nodeId: nodeIdStr, maxSubscriptionInterval, minSubscriptionInterval } = argv;
|
|
467
470
|
await theNode.start();
|
|
468
|
-
if (theNode.commissioningController === undefined) {
|
|
469
|
-
throw new Error("CommissioningController not initialized");
|
|
470
|
-
}
|
|
471
|
-
let nodeIds = theNode.commissioningController.getCommissionedNodes();
|
|
472
471
|
|
|
473
472
|
const cmdNodeId = NodeId(BigInt(nodeIdStr));
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
throw new Error(`Node ${nodeIdStr} already known`);
|
|
473
|
+
if (findCommissionedNode(theNode, cmdNodeId) !== undefined) {
|
|
474
|
+
throw new ImplementationError(`Node ${nodeIdStr} already known`);
|
|
477
475
|
}
|
|
478
476
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
)
|
|
477
|
+
// Single-fabric shell: the controller's own (first-owned) fabric is the one to address peers on.
|
|
478
|
+
const fabric = theNode.node.env.get(FabricAuthority).fabrics[0];
|
|
479
|
+
if (fabric === undefined) {
|
|
480
|
+
throw new InternalError("No controller fabric present after start");
|
|
481
|
+
}
|
|
482
|
+
await theNode.node.peers.forAddress(fabric.addressOf(cmdNodeId));
|
|
482
483
|
|
|
483
484
|
const autoSubscribe = minSubscriptionInterval !== undefined;
|
|
484
485
|
|
|
485
|
-
|
|
486
|
-
node.connect({
|
|
486
|
+
await theNode.connectAndGetNodes(nodeIdStr, {
|
|
487
487
|
autoSubscribe,
|
|
488
488
|
subscribeMinIntervalFloorSeconds: autoSubscribe ? minSubscriptionInterval : undefined,
|
|
489
489
|
subscribeMaxIntervalCeilingSeconds: autoSubscribe ? maxSubscriptionInterval : undefined,
|
|
490
|
-
...createDiagnosticCallbacks(),
|
|
491
490
|
});
|
|
492
491
|
},
|
|
493
492
|
)
|
|
@@ -516,21 +515,20 @@ export default function commands(theNode: MatterNode) {
|
|
|
516
515
|
const { nodeId: nodeIdStr, local } = argv;
|
|
517
516
|
|
|
518
517
|
await theNode.start();
|
|
519
|
-
if (theNode.commissioningController === undefined) {
|
|
520
|
-
throw new Error("CommissioningController not initialized");
|
|
521
|
-
}
|
|
522
518
|
|
|
523
519
|
let peerToCheck: ClientNode | undefined = undefined;
|
|
524
520
|
if (nodeIdStr !== undefined) {
|
|
525
521
|
const nodeId = NodeId(BigInt(nodeIdStr));
|
|
526
|
-
peerToCheck = (
|
|
522
|
+
peerToCheck = findCommissionedNode(theNode, nodeId);
|
|
523
|
+
if (peerToCheck === undefined) {
|
|
524
|
+
throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
|
|
525
|
+
}
|
|
527
526
|
}
|
|
528
527
|
|
|
529
|
-
const updatesAvailable = await theNode.
|
|
530
|
-
agent
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
.queryUpdates({ peerToCheck, includeStoredUpdates: local }),
|
|
528
|
+
const updatesAvailable = await theNode.otaProviderEndpoint.act(agent =>
|
|
529
|
+
agent
|
|
530
|
+
.get(SoftwareUpdateManager)
|
|
531
|
+
.queryUpdates({ peerToCheck, includeStoredUpdates: local }),
|
|
534
532
|
);
|
|
535
533
|
|
|
536
534
|
if (updatesAvailable.length) {
|
|
@@ -567,34 +565,32 @@ export default function commands(theNode: MatterNode) {
|
|
|
567
565
|
const { label: dclMode, isProduction } = resolveDclMode(theNode, mode);
|
|
568
566
|
|
|
569
567
|
await theNode.start();
|
|
570
|
-
if (theNode.commissioningController === undefined) {
|
|
571
|
-
throw new Error("CommissioningController not initialized");
|
|
572
|
-
}
|
|
573
568
|
|
|
574
569
|
const nodeId = NodeId(BigInt(nodeIdStr));
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
570
|
+
const node = findCommissionedNode(theNode, nodeId);
|
|
571
|
+
if (node === undefined) {
|
|
572
|
+
throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
|
|
573
|
+
}
|
|
574
|
+
const basicInfo = node.maybeStateOf(BasicInformationClient);
|
|
575
|
+
if (basicInfo === undefined) {
|
|
576
|
+
throw new ImplementationError(
|
|
577
|
+
`Node ${nodeIdStr} has no basic information available`,
|
|
578
|
+
);
|
|
581
579
|
}
|
|
582
580
|
if (
|
|
583
581
|
basicInfo.vendorId === undefined ||
|
|
584
582
|
basicInfo.productId === undefined ||
|
|
585
583
|
basicInfo.softwareVersion === undefined
|
|
586
584
|
) {
|
|
587
|
-
throw new
|
|
588
|
-
`Node ${nodeIdStr} is
|
|
585
|
+
throw new ImplementationError(
|
|
586
|
+
`Node ${nodeIdStr} BasicInformation is incomplete; connect the node first`,
|
|
589
587
|
);
|
|
590
588
|
}
|
|
591
589
|
|
|
592
590
|
console.log(`Checking for OTA updates for node ${nodeIdStr}...`);
|
|
591
|
+
console.log(` Vendor ID: ${Diagnostic.hex(basicInfo.vendorId, 4).toUpperCase()}`);
|
|
593
592
|
console.log(
|
|
594
|
-
`
|
|
595
|
-
);
|
|
596
|
-
console.log(
|
|
597
|
-
` Product ID: ${Diagnostic.hex(basicInfo.productId as number, 4).toUpperCase()}`,
|
|
593
|
+
` Product ID: ${Diagnostic.hex(basicInfo.productId, 4).toUpperCase()}`,
|
|
598
594
|
);
|
|
599
595
|
console.log(
|
|
600
596
|
` Current Software Version: ${basicInfo.softwareVersion} (${basicInfo.softwareVersionString})`,
|
|
@@ -604,9 +600,9 @@ export default function commands(theNode: MatterNode) {
|
|
|
604
600
|
const updateInfo = await (
|
|
605
601
|
await theNode.otaService()
|
|
606
602
|
).checkForUpdate({
|
|
607
|
-
vendorId: basicInfo.vendorId
|
|
608
|
-
productId: basicInfo.productId
|
|
609
|
-
currentSoftwareVersion: basicInfo.softwareVersion
|
|
603
|
+
vendorId: basicInfo.vendorId,
|
|
604
|
+
productId: basicInfo.productId,
|
|
605
|
+
currentSoftwareVersion: basicInfo.softwareVersion,
|
|
610
606
|
includeStoredUpdates: local,
|
|
611
607
|
isProduction,
|
|
612
608
|
});
|
|
@@ -660,34 +656,32 @@ export default function commands(theNode: MatterNode) {
|
|
|
660
656
|
const forceDownload = force === true;
|
|
661
657
|
|
|
662
658
|
await theNode.start();
|
|
663
|
-
if (theNode.commissioningController === undefined) {
|
|
664
|
-
throw new Error("CommissioningController not initialized");
|
|
665
|
-
}
|
|
666
659
|
|
|
667
660
|
const nodeId = NodeId(BigInt(nodeIdStr));
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
661
|
+
const node = findCommissionedNode(theNode, nodeId);
|
|
662
|
+
if (node === undefined) {
|
|
663
|
+
throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
|
|
664
|
+
}
|
|
665
|
+
const basicInfo = node.maybeStateOf(BasicInformationClient);
|
|
666
|
+
if (basicInfo === undefined) {
|
|
667
|
+
throw new ImplementationError(
|
|
668
|
+
`Node ${nodeIdStr} has no basic information available`,
|
|
669
|
+
);
|
|
674
670
|
}
|
|
675
671
|
if (
|
|
676
672
|
basicInfo.vendorId === undefined ||
|
|
677
673
|
basicInfo.productId === undefined ||
|
|
678
674
|
basicInfo.softwareVersion === undefined
|
|
679
675
|
) {
|
|
680
|
-
throw new
|
|
681
|
-
`Node ${nodeIdStr} is
|
|
676
|
+
throw new ImplementationError(
|
|
677
|
+
`Node ${nodeIdStr} BasicInformation is incomplete; connect the node first`,
|
|
682
678
|
);
|
|
683
679
|
}
|
|
684
680
|
|
|
685
681
|
console.log(`Checking for OTA updates for node ${nodeIdStr}...`);
|
|
682
|
+
console.log(` Vendor ID: ${Diagnostic.hex(basicInfo.vendorId, 4).toUpperCase()}`);
|
|
686
683
|
console.log(
|
|
687
|
-
`
|
|
688
|
-
);
|
|
689
|
-
console.log(
|
|
690
|
-
` Product ID: ${Diagnostic.hex(basicInfo.productId as number, 4).toUpperCase()}`,
|
|
684
|
+
` Product ID: ${Diagnostic.hex(basicInfo.productId, 4).toUpperCase()}`,
|
|
691
685
|
);
|
|
692
686
|
console.log(
|
|
693
687
|
` Current Software Version: ${basicInfo.softwareVersion} (${basicInfo.softwareVersionString})`,
|
|
@@ -697,9 +691,9 @@ export default function commands(theNode: MatterNode) {
|
|
|
697
691
|
const updateInfo = await (
|
|
698
692
|
await theNode.otaService()
|
|
699
693
|
).checkForUpdate({
|
|
700
|
-
vendorId: basicInfo.vendorId
|
|
701
|
-
productId: basicInfo.productId
|
|
702
|
-
currentSoftwareVersion: basicInfo.softwareVersion
|
|
694
|
+
vendorId: basicInfo.vendorId,
|
|
695
|
+
productId: basicInfo.productId,
|
|
696
|
+
currentSoftwareVersion: basicInfo.softwareVersion,
|
|
703
697
|
includeStoredUpdates: local,
|
|
704
698
|
isProduction,
|
|
705
699
|
});
|
|
@@ -751,43 +745,64 @@ export default function commands(theNode: MatterNode) {
|
|
|
751
745
|
describe: "Apply update from local file",
|
|
752
746
|
type: "boolean",
|
|
753
747
|
default: false,
|
|
748
|
+
})
|
|
749
|
+
.option("max-block-size", {
|
|
750
|
+
describe:
|
|
751
|
+
"Cap the BDX block size for this transfer, in bytes (default: whatever the device requests)",
|
|
752
|
+
type: "number",
|
|
753
|
+
})
|
|
754
|
+
.option("mrp-margin", {
|
|
755
|
+
describe:
|
|
756
|
+
"Additive MRP retransmission margin for this transfer, in milliseconds (default: derived from the device's medium)",
|
|
757
|
+
type: "number",
|
|
758
|
+
})
|
|
759
|
+
.check(argv => {
|
|
760
|
+
const blockSize = argv.maxBlockSize as number | undefined;
|
|
761
|
+
const margin = argv.mrpMargin as number | undefined;
|
|
762
|
+
if (
|
|
763
|
+
blockSize !== undefined &&
|
|
764
|
+
(!Number.isInteger(blockSize) || blockSize <= 0)
|
|
765
|
+
) {
|
|
766
|
+
throw new Error("--max-block-size must be a positive integer");
|
|
767
|
+
}
|
|
768
|
+
if (margin !== undefined && (!Number.isFinite(margin) || margin < 0)) {
|
|
769
|
+
throw new Error("--mrp-margin must be a non-negative number of ms");
|
|
770
|
+
}
|
|
771
|
+
return true;
|
|
754
772
|
});
|
|
755
773
|
},
|
|
756
774
|
async argv => {
|
|
757
|
-
const { nodeId: nodeIdStr, mode, force, local } = argv;
|
|
775
|
+
const { nodeId: nodeIdStr, mode, force, local, maxBlockSize, mrpMargin } = argv;
|
|
758
776
|
const { label: dclMode, isProduction } = resolveDclMode(theNode, mode);
|
|
759
777
|
const forceDownload = force === true;
|
|
760
778
|
|
|
761
779
|
await theNode.start();
|
|
762
780
|
|
|
763
|
-
if (theNode.commissioningController === undefined) {
|
|
764
|
-
throw new Error("CommissioningController not initialized");
|
|
765
|
-
}
|
|
766
|
-
|
|
767
781
|
const nodeId = NodeId(BigInt(nodeIdStr));
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
782
|
+
const node = findCommissionedNode(theNode, nodeId);
|
|
783
|
+
if (node === undefined) {
|
|
784
|
+
throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
|
|
785
|
+
}
|
|
786
|
+
const basicInfo = node.maybeStateOf(BasicInformationClient);
|
|
787
|
+
if (basicInfo === undefined) {
|
|
788
|
+
throw new ImplementationError(
|
|
789
|
+
`Node ${nodeIdStr} has no basic information available`,
|
|
790
|
+
);
|
|
774
791
|
}
|
|
775
792
|
if (
|
|
776
793
|
basicInfo.vendorId === undefined ||
|
|
777
794
|
basicInfo.productId === undefined ||
|
|
778
795
|
basicInfo.softwareVersion === undefined
|
|
779
796
|
) {
|
|
780
|
-
throw new
|
|
781
|
-
`Node ${nodeIdStr} is
|
|
797
|
+
throw new ImplementationError(
|
|
798
|
+
`Node ${nodeIdStr} BasicInformation is incomplete; connect the node first`,
|
|
782
799
|
);
|
|
783
800
|
}
|
|
784
801
|
|
|
785
802
|
console.log(`Checking for OTA updates for node ${nodeIdStr}...`);
|
|
803
|
+
console.log(` Vendor ID: ${Diagnostic.hex(basicInfo.vendorId, 4).toUpperCase()}`);
|
|
786
804
|
console.log(
|
|
787
|
-
`
|
|
788
|
-
);
|
|
789
|
-
console.log(
|
|
790
|
-
` Product ID: ${Diagnostic.hex(basicInfo.productId as number, 4).toUpperCase()}`,
|
|
805
|
+
` Product ID: ${Diagnostic.hex(basicInfo.productId, 4).toUpperCase()}`,
|
|
791
806
|
);
|
|
792
807
|
console.log(
|
|
793
808
|
` Current Software Version: ${basicInfo.softwareVersion} (${basicInfo.softwareVersionString})`,
|
|
@@ -797,9 +812,9 @@ export default function commands(theNode: MatterNode) {
|
|
|
797
812
|
const localUpdates = await (
|
|
798
813
|
await theNode.otaService()
|
|
799
814
|
).find({
|
|
800
|
-
vendorId: basicInfo.vendorId
|
|
801
|
-
productId: basicInfo.productId
|
|
802
|
-
currentVersion: basicInfo.softwareVersion
|
|
815
|
+
vendorId: basicInfo.vendorId,
|
|
816
|
+
productId: basicInfo.productId,
|
|
817
|
+
currentVersion: basicInfo.softwareVersion,
|
|
803
818
|
});
|
|
804
819
|
|
|
805
820
|
if (local && !localUpdates.length) {
|
|
@@ -810,9 +825,9 @@ export default function commands(theNode: MatterNode) {
|
|
|
810
825
|
const updateInfo = await (
|
|
811
826
|
await theNode.otaService()
|
|
812
827
|
).checkForUpdate({
|
|
813
|
-
vendorId: basicInfo.vendorId
|
|
814
|
-
productId: basicInfo.productId
|
|
815
|
-
currentSoftwareVersion: basicInfo.softwareVersion
|
|
828
|
+
vendorId: basicInfo.vendorId,
|
|
829
|
+
productId: basicInfo.productId,
|
|
830
|
+
currentSoftwareVersion: basicInfo.softwareVersion,
|
|
816
831
|
includeStoredUpdates: local,
|
|
817
832
|
isProduction,
|
|
818
833
|
});
|
|
@@ -849,20 +864,32 @@ export default function commands(theNode: MatterNode) {
|
|
|
849
864
|
);
|
|
850
865
|
}
|
|
851
866
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
867
|
+
if (!node.lifecycle.isConnected) {
|
|
868
|
+
throw new ImplementationError(`Node ${nodeIdStr} not connected`);
|
|
869
|
+
}
|
|
870
|
+
const peerAddress = node.peerAddress;
|
|
871
|
+
if (peerAddress === undefined) {
|
|
872
|
+
throw new ImplementationError(`Node ${nodeIdStr} has no peer address`);
|
|
855
873
|
}
|
|
856
874
|
|
|
857
|
-
|
|
875
|
+
if (maxBlockSize !== undefined) {
|
|
876
|
+
console.log(`Capping BDX block size to ${maxBlockSize} bytes`);
|
|
877
|
+
}
|
|
878
|
+
if (mrpMargin !== undefined) {
|
|
879
|
+
console.log(`Using an MRP retransmission margin of ${mrpMargin}ms`);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
await theNode.otaProviderEndpoint.act(agent => {
|
|
858
883
|
return agent
|
|
859
884
|
.get(SoftwareUpdateManager)
|
|
860
|
-
.forceUpdate(
|
|
861
|
-
|
|
862
|
-
basicInfo.
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
885
|
+
.forceUpdate(PeerAddress({ nodeId, fabricIndex: FabricIndex(1) }), {
|
|
886
|
+
vendorId: basicInfo.vendorId as VendorId,
|
|
887
|
+
productId: basicInfo.productId as number,
|
|
888
|
+
targetSoftwareVersion: updateVersion,
|
|
889
|
+
maxBdxBlockSize: maxBlockSize,
|
|
890
|
+
bdxAdditionalMrpDelay:
|
|
891
|
+
mrpMargin === undefined ? undefined : Millis(mrpMargin),
|
|
892
|
+
});
|
|
866
893
|
});
|
|
867
894
|
},
|
|
868
895
|
)
|