@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
@@ -3,7 +3,8 @@
3
3
  * Copyright 2022-2026 Matter.js Authors
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { Identify } from "@matter/types/clusters";
6
+ import { IdentifyClient } from "@matter/node/behaviors/identify";
7
+ import { awaitSeeded } from "../util/awaitSeeded.js";
7
8
  function commands(theNode) {
8
9
  return {
9
10
  command: "identify [time] [node-id] [endpoint-id]",
@@ -25,17 +26,20 @@ function commands(theNode) {
25
26
  },
26
27
  handler: async (argv) => {
27
28
  const { nodeId, time = 10, endpointId } = argv;
29
+ const nodes = await theNode.connectAndGetNodes(nodeId);
30
+ for (const node of nodes) {
31
+ await awaitSeeded(node);
32
+ }
28
33
  await theNode.iterateNodeDevices(
29
- await theNode.connectAndGetNodes(nodeId),
34
+ nodes,
30
35
  async (device, node) => {
31
- const identifyCluster = device.getClusterClient(Identify);
32
- if (identifyCluster === void 0) {
36
+ if (device.number === 0 || !device.behaviors.has(IdentifyClient)) {
33
37
  return;
34
38
  }
35
- console.log("Invoke Identify for", node.nodeId.toString());
36
- await identifyCluster.identify({ identifyTime: time });
39
+ console.log("Invoke Identify for", node.peerAddress?.nodeId.toString());
40
+ await device.act((agent) => agent.get(IdentifyClient).identify({ identifyTime: time }));
37
41
  },
38
- endpointId
42
+ endpointId !== void 0 ? [endpointId] : void 0
39
43
  );
40
44
  }
41
45
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/shell/cmd_identify.ts"],
4
- "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAS,gBAAgB;AAIV,SAAR,SAA0B,SAAqB;AAClD,SAAO;AAAA,IACH,SAAS;AAAA,IACT,UACI;AAAA,IACJ,SAAS,CAAC,UAAgB;AACtB,aAAO,MACF,WAAW,QAAQ;AAAA,QAChB,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,MACV,CAAC,EACA,WAAW,WAAW;AAAA,QACnB,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,MACV,CAAC,EACA,WAAW,eAAe;AAAA,QACvB,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,MACV,CAAC;AAAA,IACT;AAAA,IAEA,SAAS,OAAO,SAAc;AAC1B,YAAM,EAAE,QAAQ,OAAO,IAAI,WAAW,IAAI;AAC1C,YAAM,QAAQ;AAAA,QACV,MAAM,QAAQ,mBAAmB,MAAM;AAAA,QACvC,OAAO,QAAQ,SAAS;AACpB,gBAAM,kBAAkB,OAAO,iBAAiB,QAAQ;AACxD,cAAI,oBAAoB,QAAW;AAC/B;AAAA,UACJ;AACA,kBAAQ,IAAI,uBAAuB,KAAK,OAAO,SAAS,CAAC;AACzD,gBAAM,gBAAgB,SAAS,EAAE,cAAc,KAAK,CAAC;AAAA,QACzD;AAAA,QACA;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACJ;",
4
+ "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAS,sBAAsB;AAG/B,SAAS,mBAAmB;AAEb,SAAR,SAA0B,SAAqB;AAClD,SAAO;AAAA,IACH,SAAS;AAAA,IACT,UACI;AAAA,IACJ,SAAS,CAAC,UAAgB;AACtB,aAAO,MACF,WAAW,QAAQ;AAAA,QAChB,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,MACV,CAAC,EACA,WAAW,WAAW;AAAA,QACnB,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,MACV,CAAC,EACA,WAAW,eAAe;AAAA,QACvB,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,MACV,CAAC;AAAA,IACT;AAAA,IAEA,SAAS,OAAO,SAAc;AAC1B,YAAM,EAAE,QAAQ,OAAO,IAAI,WAAW,IAAI;AAC1C,YAAM,QAAQ,MAAM,QAAQ,mBAAmB,MAAM;AACrD,iBAAW,QAAQ,OAAO;AACtB,cAAM,YAAY,IAAI;AAAA,MAC1B;AACA,YAAM,QAAQ;AAAA,QACV;AAAA,QACA,OAAO,QAAQ,SAAS;AACpB,cAAI,OAAO,WAAW,KAAK,CAAC,OAAO,UAAU,IAAI,cAAc,GAAG;AAC9D;AAAA,UACJ;AACA,kBAAQ,IAAI,uBAAuB,KAAK,aAAa,OAAO,SAAS,CAAC;AACtE,gBAAM,OAAO,IAAI,WAAS,MAAM,IAAI,cAAc,EAAE,SAAS,EAAE,cAAc,KAAK,CAAC,CAAC;AAAA,QACxF;AAAA,QACA,eAAe,SAAY,CAAC,UAAU,IAAI;AAAA,MAC9C;AAAA,IACJ;AAAA,EACJ;AACJ;",
5
5
  "names": []
6
6
  }
@@ -3,17 +3,38 @@
3
3
  * Copyright 2022-2026 Matter.js Authors
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { capitalize, ChannelType, decamelize, Diagnostic, ServerAddress } from "@matter/general";
7
- import { CommissioningClient, NetworkClient, SoftwareUpdateManager } from "@matter/node";
8
- import { PeerAddress, PeerSet } from "@matter/protocol";
6
+ import {
7
+ capitalize,
8
+ ChannelType,
9
+ decamelize,
10
+ Diagnostic,
11
+ ImplementationError,
12
+ InternalError,
13
+ Logger,
14
+ Millis,
15
+ ServerAddress
16
+ } from "@matter/general";
17
+ import {
18
+ CommissioningClient,
19
+ NetworkClient,
20
+ NodeConnectionState,
21
+ SoftwareUpdateManager
22
+ } from "@matter/node";
23
+ import { BasicInformationClient } from "@matter/node/behaviors/basic-information";
24
+ import { FabricAuthority, PeerAddress, PeerSet } from "@matter/protocol";
9
25
  import { FabricIndex, NodeId } from "@matter/types";
10
- import { NodeStateInformation } from "@project-chip/matter.js/device";
11
- import { createDiagnosticCallbacks } from "../MatterNode.js";
26
+ import { awaitSeeded } from "../util/awaitSeeded.js";
12
27
  import { resolveDclMode, withDclModeOption } from "./ota-dcl-mode.js";
28
+ const logger = Logger.get("cmd_nodes");
29
+ function findCommissionedNode(theNode, nodeId) {
30
+ return theNode.node.peers.commissioned.find((peer) => peer.peerAddress?.nodeId === nodeId);
31
+ }
13
32
  function parseFallbackAddress(input) {
14
33
  const match = /^(udp|tcp):\/\/(.+)$/i.exec(input);
15
34
  if (!match) {
16
- throw new Error(`Invalid address "${input}". Expected udp://<host>:<port> or tcp://<host>:<port>`);
35
+ throw new ImplementationError(
36
+ `Invalid address "${input}". Expected udp://<host>:<port> or tcp://<host>:<port>`
37
+ );
17
38
  }
18
39
  const type = match[1].toLowerCase() === "tcp" ? "tcp" : "udp";
19
40
  const rest = match[2];
@@ -22,21 +43,23 @@ function parseFallbackAddress(input) {
22
43
  if (rest.startsWith("[")) {
23
44
  const end = rest.indexOf("]");
24
45
  if (end === -1 || rest[end + 1] !== ":") {
25
- throw new Error(`Invalid IPv6 address "${input}". Expected ${type}://[<ipv6>]:<port>`);
46
+ throw new ImplementationError(`Invalid IPv6 address "${input}". Expected ${type}://[<ipv6>]:<port>`);
26
47
  }
27
48
  ip = rest.slice(1, end);
28
49
  portStr = rest.slice(end + 2);
29
50
  } else {
30
51
  const idx = rest.lastIndexOf(":");
31
52
  if (idx === -1) {
32
- throw new Error(`Missing port in "${input}". Expected ${type}://<host>:<port>`);
53
+ throw new ImplementationError(`Missing port in "${input}". Expected ${type}://<host>:<port>`);
33
54
  }
34
55
  ip = rest.slice(0, idx);
35
56
  portStr = rest.slice(idx + 1);
36
57
  }
37
58
  const port = Number(portStr);
38
59
  if (!ip.length || !Number.isInteger(port) || port < 1 || port > 65535) {
39
- throw new Error(`Invalid host/port in "${input}". Expected ${type}://<host>:<port> with port 1-65535`);
60
+ throw new ImplementationError(
61
+ `Invalid host/port in "${input}". Expected ${type}://<host>:<port> with port 1-65535`
62
+ );
40
63
  }
41
64
  return { ip, port, type };
42
65
  }
@@ -58,23 +81,23 @@ function commands(theNode) {
58
81
  async (argv) => {
59
82
  const { status } = argv;
60
83
  await theNode.start();
61
- if (theNode.commissioningController === void 0) {
62
- throw new Error("CommissioningController not initialized");
63
- }
84
+ const peers = theNode.node.peers.commissioned;
64
85
  switch (status) {
65
86
  case "commissioned": {
66
- const details = theNode.commissioningController.getCommissionedNodesDetails();
67
- details.map((detail) => ({
68
- ...detail,
69
- nodeId: detail.nodeId.toString()
70
- })).forEach((detail) => {
71
- console.log(detail);
72
- });
87
+ for (const peer of peers) {
88
+ const { addresses, deviceName } = peer.state.commissioning;
89
+ console.log({
90
+ nodeId: peer.peerAddress?.nodeId.toString(),
91
+ operationalAddress: addresses?.length ? ServerAddress.urlFor(addresses[0]) : void 0,
92
+ advertisedName: deviceName,
93
+ basicInformation: peer.maybeStateOf(BasicInformationClient)
94
+ });
95
+ }
73
96
  break;
74
97
  }
75
98
  case "connected": {
76
- const nodeIds = theNode.commissioningController.getCommissionedNodes().filter((nodeId) => !!theNode.commissioningController?.getPairedNode(nodeId));
77
- console.log(nodeIds.map((nodeId) => nodeId.toString()));
99
+ const nodeIds = peers.filter((peer) => peer.lifecycle.isConnected).map((peer) => peer.peerAddress?.nodeId);
100
+ console.log(nodeIds.map((nodeId) => nodeId?.toString()));
78
101
  break;
79
102
  }
80
103
  }
@@ -92,8 +115,11 @@ function commands(theNode) {
92
115
  async (argv) => {
93
116
  const { nodeId } = argv;
94
117
  const node = (await theNode.connectAndGetNodes(nodeId))[0];
95
- console.log("Logging structure of Node ", node.nodeId.toString());
96
- node.logStructure();
118
+ if (!await awaitSeeded(node)) {
119
+ return;
120
+ }
121
+ console.log("Logging structure of Node ", node.peerAddress?.nodeId.toString());
122
+ logger.info(node);
97
123
  }
98
124
  ).command(
99
125
  "descriptor <node-id>",
@@ -108,11 +134,12 @@ function commands(theNode) {
108
134
  async (argv) => {
109
135
  const { nodeId: nodeIdStr } = argv;
110
136
  await theNode.start();
111
- if (theNode.commissioningController === void 0) {
112
- throw new Error("CommissioningController not initialized");
113
- }
114
137
  const nodeId = NodeId(BigInt(nodeIdStr));
115
- const peerAddress = theNode.commissioningController.fabric.addressOf(nodeId);
138
+ const peerAddress = findCommissionedNode(theNode, nodeId)?.peerAddress;
139
+ if (peerAddress === void 0) {
140
+ console.log(`Node ${nodeIdStr} not commissioned`);
141
+ return;
142
+ }
116
143
  const peerSet = theNode.node.env.get(PeerSet);
117
144
  const peer = peerSet.for(peerAddress);
118
145
  if (!peer) {
@@ -181,28 +208,12 @@ Peer Descriptor for node ${nodeIdStr}:`);
181
208
  },
182
209
  async (argv) => {
183
210
  const { nodeId: nodeIdStr, maxSubscriptionInterval, minSubscriptionInterval } = argv;
184
- await theNode.start();
185
- if (theNode.commissioningController === void 0) {
186
- throw new Error("CommissioningController not initialized");
187
- }
188
- let nodeIds = theNode.commissioningController.getCommissionedNodes();
189
- if (nodeIdStr !== "all") {
190
- const cmdNodeId = NodeId(BigInt(nodeIdStr));
191
- nodeIds = nodeIds.filter((nodeId) => nodeId === cmdNodeId);
192
- if (!nodeIds.length) {
193
- throw new Error(`Node ${nodeIdStr} not commissioned`);
194
- }
195
- }
196
211
  const autoSubscribe = minSubscriptionInterval !== void 0;
197
- for (const nodeIdToProcess of nodeIds) {
198
- const node = await theNode.commissioningController.getNode(nodeIdToProcess);
199
- node.connect({
200
- autoSubscribe,
201
- subscribeMinIntervalFloorSeconds: autoSubscribe ? minSubscriptionInterval : void 0,
202
- subscribeMaxIntervalCeilingSeconds: autoSubscribe ? maxSubscriptionInterval : void 0,
203
- ...createDiagnosticCallbacks()
204
- });
205
- }
212
+ await theNode.connectAndGetNodes(nodeIdStr !== "all" ? nodeIdStr : void 0, {
213
+ autoSubscribe,
214
+ subscribeMinIntervalFloorSeconds: autoSubscribe ? minSubscriptionInterval : void 0,
215
+ subscribeMaxIntervalCeilingSeconds: autoSubscribe ? maxSubscriptionInterval : void 0
216
+ });
206
217
  }
207
218
  ).command(
208
219
  "disconnect [node-id]",
@@ -216,25 +227,17 @@ Peer Descriptor for node ${nodeIdStr}:`);
216
227
  },
217
228
  async (argv) => {
218
229
  const { nodeId: nodeIdStr } = argv;
219
- if (theNode.commissioningController === void 0) {
220
- console.log("Controller not initialized, nothing to disconnect.");
221
- return;
222
- }
223
- let nodeIds = theNode.commissioningController.getCommissionedNodes();
230
+ await theNode.start();
231
+ let nodes = theNode.node.peers.commissioned;
224
232
  if (nodeIdStr !== "all") {
225
233
  const cmdNodeId = NodeId(BigInt(nodeIdStr));
226
- nodeIds = nodeIds.filter((nodeId) => nodeId === cmdNodeId);
227
- if (!nodeIds.length) {
228
- throw new Error(`Node ${nodeIdStr} not commissioned`);
234
+ nodes = nodes.filter((node) => node.peerAddress?.nodeId === cmdNodeId);
235
+ if (!nodes.length) {
236
+ throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
229
237
  }
230
238
  }
231
- for (const nodeIdToProcess of nodeIds) {
232
- const node = theNode.commissioningController.getPairedNode(nodeIdToProcess);
233
- if (node === void 0) {
234
- console.log(`Node ${nodeIdToProcess} not connected`);
235
- continue;
236
- }
237
- await node.disconnect();
239
+ for (const node of nodes) {
240
+ await node.stop();
238
241
  }
239
242
  }
240
243
  ).command(
@@ -250,31 +253,24 @@ Peer Descriptor for node ${nodeIdStr}:`);
250
253
  async (argv) => {
251
254
  const { nodeIds: nodeIdStr } = argv;
252
255
  await theNode.start();
253
- if (theNode.commissioningController === void 0) {
254
- throw new Error("CommissioningController not initialized");
255
- }
256
- let nodeIds = theNode.commissioningController.getCommissionedNodes();
256
+ let nodes = theNode.node.peers.commissioned;
257
257
  if (nodeIdStr !== "all") {
258
258
  const nodeIdList = nodeIdStr.split(",").map((nodeId) => NodeId(BigInt(nodeId)));
259
- nodeIds = nodeIds.filter((nodeId) => nodeIdList.includes(nodeId));
260
- if (!nodeIds.length) {
261
- throw new Error(`Node ${nodeIdStr} not commissioned`);
259
+ nodes = nodes.filter(
260
+ (node) => node.peerAddress !== void 0 && nodeIdList.includes(node.peerAddress.nodeId)
261
+ );
262
+ if (!nodes.length) {
263
+ throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
262
264
  }
263
265
  }
264
- const nodeDetails = theNode.commissioningController.getCommissionedNodesDetails();
265
- for (const nodeIdToProcess of nodeIds) {
266
- const node = theNode.commissioningController.getPairedNode(nodeIdToProcess);
267
- if (node === void 0) {
268
- const details = nodeDetails.find((nd) => nd.nodeId === nodeIdToProcess);
269
- console.log(
270
- `Node ${nodeIdToProcess}: Not initialized${details?.deviceData?.basicInformation !== void 0 ? ` (${details.deviceData.basicInformation.vendorName} ${details.deviceData.basicInformation.productName})` : ""}`
271
- );
272
- } else {
273
- const basicInfo = node.basicInformation;
274
- console.log(
275
- `Node ${nodeIdToProcess}: Node Status: ${capitalize(decamelize(NodeStateInformation[node.connectionState], " "))}${basicInfo !== void 0 ? ` (${basicInfo.vendorName} ${basicInfo.productName})` : ""}`
276
- );
277
- }
266
+ for (const node of nodes) {
267
+ const basicInfo = node.maybeStateOf(BasicInformationClient);
268
+ const status = capitalize(
269
+ decamelize(NodeConnectionState[node.lifecycle.connectionState], " ")
270
+ );
271
+ console.log(
272
+ `Node ${node.peerAddress?.nodeId}: Node Status: ${status}${basicInfo !== void 0 ? ` (${basicInfo.vendorName} ${basicInfo.productName})` : ""}`
273
+ );
278
274
  }
279
275
  }
280
276
  ).command(
@@ -295,14 +291,18 @@ Peer Descriptor for node ${nodeIdStr}:`);
295
291
  async (argv) => {
296
292
  const { nodeId: nodeIdStr, preference } = argv;
297
293
  await theNode.start();
298
- if (theNode.commissioningController === void 0) {
299
- throw new Error("CommissioningController not initialized");
300
- }
301
294
  const nodeId = NodeId(BigInt(nodeIdStr));
302
- const node = await theNode.commissioningController.getNode(nodeId);
295
+ const node = findCommissionedNode(theNode, nodeId);
296
+ if (node === void 0) {
297
+ throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
298
+ }
299
+ const peerAddress = node.peerAddress;
300
+ if (peerAddress === void 0) {
301
+ throw new ImplementationError(`Node ${nodeIdStr} has no peer address`);
302
+ }
303
303
  const pref = preference === "on" ? "tcp" : preference === "off" ? "udp" : void 0;
304
- await node.node.setStateOf(NetworkClient, { transportPreference: pref });
305
- const peer = theNode.node.env.get(PeerSet).for(theNode.commissioningController.fabric.addressOf(nodeId));
304
+ await node.setStateOf(NetworkClient, { transportPreference: pref });
305
+ const peer = theNode.node.env.get(PeerSet).for(peerAddress);
306
306
  if (peer) {
307
307
  peer.transportPreference = pref === "tcp" ? ChannelType.TCP : void 0;
308
308
  }
@@ -326,12 +326,12 @@ Peer Descriptor for node ${nodeIdStr}:`);
326
326
  async (argv) => {
327
327
  const { nodeId: nodeIdStr } = argv;
328
328
  await theNode.start();
329
- if (theNode.commissioningController === void 0) {
330
- throw new Error("CommissioningController not initialized");
331
- }
332
329
  const nodeId = NodeId(BigInt(nodeIdStr));
333
- const node = await theNode.commissioningController.getNode(nodeId);
334
- const addresses = node.node.state.commissioning.addresses;
330
+ const node = findCommissionedNode(theNode, nodeId);
331
+ if (node === void 0) {
332
+ throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
333
+ }
334
+ const addresses = node.state.commissioning.addresses;
335
335
  if (!addresses?.length) {
336
336
  console.log(`Node ${nodeIdStr} has no fallback addresses stored`);
337
337
  return;
@@ -358,18 +358,18 @@ Peer Descriptor for node ${nodeIdStr}:`);
358
358
  async (argv) => {
359
359
  const { nodeId: nodeIdStr, address: addressStr } = argv;
360
360
  await theNode.start();
361
- if (theNode.commissioningController === void 0) {
362
- throw new Error("CommissioningController not initialized");
363
- }
364
361
  const nodeId = NodeId(BigInt(nodeIdStr));
365
- const node = await theNode.commissioningController.getNode(nodeId);
362
+ const node = findCommissionedNode(theNode, nodeId);
363
+ if (node === void 0) {
364
+ throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
365
+ }
366
366
  if (addressStr === "drop") {
367
- await node.node.setStateOf(CommissioningClient, { addresses: void 0 });
367
+ await node.setStateOf(CommissioningClient, { addresses: void 0 });
368
368
  console.log(`Fallback address for node ${nodeIdStr} dropped`);
369
369
  return;
370
370
  }
371
371
  const address = parseFallbackAddress(addressStr);
372
- await node.node.setStateOf(CommissioningClient, { addresses: [address] });
372
+ await node.setStateOf(CommissioningClient, { addresses: [address] });
373
373
  console.log(
374
374
  `Fallback address for node ${nodeIdStr} set to ${ServerAddress.urlFor(address)}`
375
375
  );
@@ -398,25 +398,20 @@ Peer Descriptor for node ${nodeIdStr}:`);
398
398
  async (argv) => {
399
399
  const { nodeId: nodeIdStr, maxSubscriptionInterval, minSubscriptionInterval } = argv;
400
400
  await theNode.start();
401
- if (theNode.commissioningController === void 0) {
402
- throw new Error("CommissioningController not initialized");
403
- }
404
- let nodeIds = theNode.commissioningController.getCommissionedNodes();
405
401
  const cmdNodeId = NodeId(BigInt(nodeIdStr));
406
- nodeIds = nodeIds.filter((nodeId) => nodeId === cmdNodeId);
407
- if (nodeIds.length) {
408
- throw new Error(`Node ${nodeIdStr} already known`);
402
+ if (findCommissionedNode(theNode, cmdNodeId) !== void 0) {
403
+ throw new ImplementationError(`Node ${nodeIdStr} already known`);
409
404
  }
410
- await theNode.commissioningController.node.peers.forAddress(
411
- theNode.commissioningController.fabric.addressOf(cmdNodeId)
412
- );
405
+ const fabric = theNode.node.env.get(FabricAuthority).fabrics[0];
406
+ if (fabric === void 0) {
407
+ throw new InternalError("No controller fabric present after start");
408
+ }
409
+ await theNode.node.peers.forAddress(fabric.addressOf(cmdNodeId));
413
410
  const autoSubscribe = minSubscriptionInterval !== void 0;
414
- const node = await theNode.commissioningController.getNode(cmdNodeId);
415
- node.connect({
411
+ await theNode.connectAndGetNodes(nodeIdStr, {
416
412
  autoSubscribe,
417
413
  subscribeMinIntervalFloorSeconds: autoSubscribe ? minSubscriptionInterval : void 0,
418
- subscribeMaxIntervalCeilingSeconds: autoSubscribe ? maxSubscriptionInterval : void 0,
419
- ...createDiagnosticCallbacks()
414
+ subscribeMaxIntervalCeilingSeconds: autoSubscribe ? maxSubscriptionInterval : void 0
420
415
  });
421
416
  }
422
417
  ).command(
@@ -439,15 +434,15 @@ Peer Descriptor for node ${nodeIdStr}:`);
439
434
  async (argv) => {
440
435
  const { nodeId: nodeIdStr, local } = argv;
441
436
  await theNode.start();
442
- if (theNode.commissioningController === void 0) {
443
- throw new Error("CommissioningController not initialized");
444
- }
445
437
  let peerToCheck = void 0;
446
438
  if (nodeIdStr !== void 0) {
447
439
  const nodeId = NodeId(BigInt(nodeIdStr));
448
- peerToCheck = (await theNode.commissioningController.getNode(nodeId))?.node;
440
+ peerToCheck = findCommissionedNode(theNode, nodeId);
441
+ if (peerToCheck === void 0) {
442
+ throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
443
+ }
449
444
  }
450
- const updatesAvailable = await theNode.commissioningController.otaProvider.act(
445
+ const updatesAvailable = await theNode.otaProviderEndpoint.act(
451
446
  (agent) => agent.get(SoftwareUpdateManager).queryUpdates({ peerToCheck, includeStoredUpdates: local })
452
447
  );
453
448
  if (updatesAvailable.length) {
@@ -482,24 +477,24 @@ Peer Descriptor for node ${nodeIdStr}:`);
482
477
  const { nodeId: nodeIdStr, mode, local } = argv;
483
478
  const { label: dclMode, isProduction } = resolveDclMode(theNode, mode);
484
479
  await theNode.start();
485
- if (theNode.commissioningController === void 0) {
486
- throw new Error("CommissioningController not initialized");
487
- }
488
480
  const nodeId = NodeId(BigInt(nodeIdStr));
489
- const nodeDetails = theNode.commissioningController.getCommissionedNodesDetails().find((nd) => nd.nodeId === nodeId);
490
- const basicInfo = nodeDetails?.deviceData?.basicInformation;
491
- if (!basicInfo) {
492
- throw new Error(`Node ${nodeIdStr} has no basic information available`);
481
+ const node = findCommissionedNode(theNode, nodeId);
482
+ if (node === void 0) {
483
+ throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
484
+ }
485
+ const basicInfo = node.maybeStateOf(BasicInformationClient);
486
+ if (basicInfo === void 0) {
487
+ throw new ImplementationError(
488
+ `Node ${nodeIdStr} has no basic information available`
489
+ );
493
490
  }
494
491
  if (basicInfo.vendorId === void 0 || basicInfo.productId === void 0 || basicInfo.softwareVersion === void 0) {
495
- throw new Error(
496
- `Node ${nodeIdStr} is missing required basic information for OTA check`
492
+ throw new ImplementationError(
493
+ `Node ${nodeIdStr} BasicInformation is incomplete; connect the node first`
497
494
  );
498
495
  }
499
496
  console.log(`Checking for OTA updates for node ${nodeIdStr}...`);
500
- console.log(
501
- ` Vendor ID: ${Diagnostic.hex(basicInfo.vendorId, 4).toUpperCase()}`
502
- );
497
+ console.log(` Vendor ID: ${Diagnostic.hex(basicInfo.vendorId, 4).toUpperCase()}`);
503
498
  console.log(
504
499
  ` Product ID: ${Diagnostic.hex(basicInfo.productId, 4).toUpperCase()}`
505
500
  );
@@ -561,24 +556,24 @@ Run "nodes ota download ${nodeIdStr}${mode === "auto" ? "" : ` --mode ${mode}`}"
561
556
  const { label: dclMode, isProduction } = resolveDclMode(theNode, mode);
562
557
  const forceDownload = force === true;
563
558
  await theNode.start();
564
- if (theNode.commissioningController === void 0) {
565
- throw new Error("CommissioningController not initialized");
566
- }
567
559
  const nodeId = NodeId(BigInt(nodeIdStr));
568
- const nodeDetails = theNode.commissioningController.getCommissionedNodesDetails().find((nd) => nd.nodeId === nodeId);
569
- const basicInfo = nodeDetails?.deviceData?.basicInformation;
570
- if (!basicInfo) {
571
- throw new Error(`Node ${nodeIdStr} has no basic information available`);
560
+ const node = findCommissionedNode(theNode, nodeId);
561
+ if (node === void 0) {
562
+ throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
563
+ }
564
+ const basicInfo = node.maybeStateOf(BasicInformationClient);
565
+ if (basicInfo === void 0) {
566
+ throw new ImplementationError(
567
+ `Node ${nodeIdStr} has no basic information available`
568
+ );
572
569
  }
573
570
  if (basicInfo.vendorId === void 0 || basicInfo.productId === void 0 || basicInfo.softwareVersion === void 0) {
574
- throw new Error(
575
- `Node ${nodeIdStr} is missing required basic information for OTA check`
571
+ throw new ImplementationError(
572
+ `Node ${nodeIdStr} BasicInformation is incomplete; connect the node first`
576
573
  );
577
574
  }
578
575
  console.log(`Checking for OTA updates for node ${nodeIdStr}...`);
579
- console.log(
580
- ` Vendor ID: ${Diagnostic.hex(basicInfo.vendorId, 4).toUpperCase()}`
581
- );
576
+ console.log(` Vendor ID: ${Diagnostic.hex(basicInfo.vendorId, 4).toUpperCase()}`);
582
577
  console.log(
583
578
  ` Product ID: ${Diagnostic.hex(basicInfo.productId, 4).toUpperCase()}`
584
579
  );
@@ -634,31 +629,47 @@ You can now apply this update to the device using your device's OTA mechanism.`
634
629
  describe: "Apply update from local file",
635
630
  type: "boolean",
636
631
  default: false
632
+ }).option("max-block-size", {
633
+ describe: "Cap the BDX block size for this transfer, in bytes (default: whatever the device requests)",
634
+ type: "number"
635
+ }).option("mrp-margin", {
636
+ describe: "Additive MRP retransmission margin for this transfer, in milliseconds (default: derived from the device's medium)",
637
+ type: "number"
638
+ }).check((argv) => {
639
+ const blockSize = argv.maxBlockSize;
640
+ const margin = argv.mrpMargin;
641
+ if (blockSize !== void 0 && (!Number.isInteger(blockSize) || blockSize <= 0)) {
642
+ throw new Error("--max-block-size must be a positive integer");
643
+ }
644
+ if (margin !== void 0 && (!Number.isFinite(margin) || margin < 0)) {
645
+ throw new Error("--mrp-margin must be a non-negative number of ms");
646
+ }
647
+ return true;
637
648
  });
638
649
  },
639
650
  async (argv) => {
640
- const { nodeId: nodeIdStr, mode, force, local } = argv;
651
+ const { nodeId: nodeIdStr, mode, force, local, maxBlockSize, mrpMargin } = argv;
641
652
  const { label: dclMode, isProduction } = resolveDclMode(theNode, mode);
642
653
  const forceDownload = force === true;
643
654
  await theNode.start();
644
- if (theNode.commissioningController === void 0) {
645
- throw new Error("CommissioningController not initialized");
646
- }
647
655
  const nodeId = NodeId(BigInt(nodeIdStr));
648
- const nodeDetails = theNode.commissioningController.getCommissionedNodesDetails().find((nd) => nd.nodeId === nodeId);
649
- const basicInfo = nodeDetails?.deviceData?.basicInformation;
650
- if (!basicInfo) {
651
- throw new Error(`Node ${nodeIdStr} has no basic information available`);
656
+ const node = findCommissionedNode(theNode, nodeId);
657
+ if (node === void 0) {
658
+ throw new ImplementationError(`Node ${nodeIdStr} not commissioned`);
659
+ }
660
+ const basicInfo = node.maybeStateOf(BasicInformationClient);
661
+ if (basicInfo === void 0) {
662
+ throw new ImplementationError(
663
+ `Node ${nodeIdStr} has no basic information available`
664
+ );
652
665
  }
653
666
  if (basicInfo.vendorId === void 0 || basicInfo.productId === void 0 || basicInfo.softwareVersion === void 0) {
654
- throw new Error(
655
- `Node ${nodeIdStr} is missing required basic information for OTA check`
667
+ throw new ImplementationError(
668
+ `Node ${nodeIdStr} BasicInformation is incomplete; connect the node first`
656
669
  );
657
670
  }
658
671
  console.log(`Checking for OTA updates for node ${nodeIdStr}...`);
659
- console.log(
660
- ` Vendor ID: ${Diagnostic.hex(basicInfo.vendorId, 4).toUpperCase()}`
661
- );
672
+ console.log(` Vendor ID: ${Diagnostic.hex(basicInfo.vendorId, 4).toUpperCase()}`);
662
673
  console.log(
663
674
  ` Product ID: ${Diagnostic.hex(basicInfo.productId, 4).toUpperCase()}`
664
675
  );
@@ -709,17 +720,27 @@ You can now apply this update to the device using your device's OTA mechanism.`
709
720
  `Update to version ${updateVersion} (${localUpdates[0].softwareVersionString}) found in local storage: ${localUpdates[0].filename}`
710
721
  );
711
722
  }
712
- const node = theNode.commissioningController.getPairedNode(nodeId);
713
- if (node === void 0) {
714
- throw new Error(`Node ${nodeIdStr} not connected`);
715
- }
716
- await theNode.commissioningController.otaProvider.act((agent) => {
717
- return agent.get(SoftwareUpdateManager).forceUpdate(
718
- PeerAddress({ nodeId, fabricIndex: FabricIndex(1) }),
719
- basicInfo.vendorId,
720
- basicInfo.productId,
721
- updateVersion
722
- );
723
+ if (!node.lifecycle.isConnected) {
724
+ throw new ImplementationError(`Node ${nodeIdStr} not connected`);
725
+ }
726
+ const peerAddress = node.peerAddress;
727
+ if (peerAddress === void 0) {
728
+ throw new ImplementationError(`Node ${nodeIdStr} has no peer address`);
729
+ }
730
+ if (maxBlockSize !== void 0) {
731
+ console.log(`Capping BDX block size to ${maxBlockSize} bytes`);
732
+ }
733
+ if (mrpMargin !== void 0) {
734
+ console.log(`Using an MRP retransmission margin of ${mrpMargin}ms`);
735
+ }
736
+ await theNode.otaProviderEndpoint.act((agent) => {
737
+ return agent.get(SoftwareUpdateManager).forceUpdate(PeerAddress({ nodeId, fabricIndex: FabricIndex(1) }), {
738
+ vendorId: basicInfo.vendorId,
739
+ productId: basicInfo.productId,
740
+ targetSoftwareVersion: updateVersion,
741
+ maxBdxBlockSize: maxBlockSize,
742
+ bdxAdditionalMrpDelay: mrpMargin === void 0 ? void 0 : Millis(mrpMargin)
743
+ });
723
744
  });
724
745
  }
725
746
  ).demandCommand(1, "Please specify an OTA subcommand"),