@matter/nodejs-shell 0.11.0-alpha.0-20241007-547af42a8

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 (56) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +192 -0
  3. package/dist/cjs/MatterNode.js +115 -0
  4. package/dist/cjs/MatterNode.js.map +6 -0
  5. package/dist/cjs/app.js +162 -0
  6. package/dist/cjs/app.js.map +6 -0
  7. package/dist/cjs/package.json +7 -0
  8. package/dist/cjs/shell/Shell.js +195 -0
  9. package/dist/cjs/shell/Shell.js.map +6 -0
  10. package/dist/cjs/shell/cmd_cluster-attributes.js +310 -0
  11. package/dist/cjs/shell/cmd_cluster-attributes.js.map +6 -0
  12. package/dist/cjs/shell/cmd_cluster-commands.js +158 -0
  13. package/dist/cjs/shell/cmd_cluster-commands.js.map +6 -0
  14. package/dist/cjs/shell/cmd_cluster-events.js +104 -0
  15. package/dist/cjs/shell/cmd_cluster-events.js.map +6 -0
  16. package/dist/cjs/shell/cmd_commission.js +237 -0
  17. package/dist/cjs/shell/cmd_commission.js.map +6 -0
  18. package/dist/cjs/shell/cmd_config.js +315 -0
  19. package/dist/cjs/shell/cmd_config.js.map +6 -0
  20. package/dist/cjs/shell/cmd_discover.js +123 -0
  21. package/dist/cjs/shell/cmd_discover.js.map +6 -0
  22. package/dist/cjs/shell/cmd_identify.js +66 -0
  23. package/dist/cjs/shell/cmd_identify.js.map +6 -0
  24. package/dist/cjs/shell/cmd_nodes.js +244 -0
  25. package/dist/cjs/shell/cmd_nodes.js.map +6 -0
  26. package/dist/cjs/shell/cmd_session.js +43 -0
  27. package/dist/cjs/shell/cmd_session.js.map +6 -0
  28. package/dist/cjs/shell/cmd_subscribe.js +59 -0
  29. package/dist/cjs/shell/cmd_subscribe.js.map +6 -0
  30. package/dist/cjs/shell/cmd_tlv.js +175 -0
  31. package/dist/cjs/shell/cmd_tlv.js.map +6 -0
  32. package/dist/cjs/util/CommandlineParser.js +106 -0
  33. package/dist/cjs/util/CommandlineParser.js.map +6 -0
  34. package/dist/cjs/util/Json.js +66 -0
  35. package/dist/cjs/util/Json.js.map +6 -0
  36. package/dist/cjs/util/String.js +32 -0
  37. package/dist/cjs/util/String.js.map +6 -0
  38. package/package.json +58 -0
  39. package/src/MatterNode.ts +141 -0
  40. package/src/app.ts +158 -0
  41. package/src/shell/Shell.ts +184 -0
  42. package/src/shell/cmd_cluster-attributes.ts +340 -0
  43. package/src/shell/cmd_cluster-commands.ts +174 -0
  44. package/src/shell/cmd_cluster-events.ts +99 -0
  45. package/src/shell/cmd_commission.ts +273 -0
  46. package/src/shell/cmd_config.ts +366 -0
  47. package/src/shell/cmd_discover.ts +127 -0
  48. package/src/shell/cmd_identify.ts +51 -0
  49. package/src/shell/cmd_nodes.ts +249 -0
  50. package/src/shell/cmd_session.ts +23 -0
  51. package/src/shell/cmd_subscribe.ts +43 -0
  52. package/src/shell/cmd_tlv.ts +169 -0
  53. package/src/tsconfig.json +22 -0
  54. package/src/util/CommandlineParser.ts +123 -0
  55. package/src/util/Json.ts +50 -0
  56. package/src/util/String.ts +9 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { capitalize, decamelize, Logger } from "@matter/general";
8
+ import { NodeId } from "@project-chip/matter.js/datatype";
9
+ import { CommissioningControllerNodeOptions, NodeStateInformation } from "@project-chip/matter.js/device";
10
+ import type { Argv } from "yargs";
11
+ import { MatterNode } from "../MatterNode";
12
+
13
+ export function createDiagnosticCallbacks(): Partial<CommissioningControllerNodeOptions> {
14
+ return {
15
+ attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
16
+ console.log(
17
+ `attributeChangedCallback ${peerNodeId}: Attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(
18
+ value,
19
+ )}`,
20
+ ),
21
+ eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
22
+ console.log(
23
+ `eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(
24
+ events,
25
+ )}`,
26
+ ),
27
+ stateInformationCallback: (peerNodeId, info) => {
28
+ switch (info) {
29
+ case NodeStateInformation.Connected:
30
+ console.log(`stateInformationCallback Node ${peerNodeId} connected`);
31
+ break;
32
+ case NodeStateInformation.Disconnected:
33
+ console.log(`stateInformationCallback Node ${peerNodeId} disconnected`);
34
+ break;
35
+ case NodeStateInformation.Reconnecting:
36
+ console.log(`stateInformationCallback Node ${peerNodeId} reconnecting`);
37
+ break;
38
+ case NodeStateInformation.WaitingForDeviceDiscovery:
39
+ console.log(
40
+ `stateInformationCallback Node ${peerNodeId} waiting that device gets discovered again`,
41
+ );
42
+ break;
43
+ case NodeStateInformation.StructureChanged:
44
+ console.log(`stateInformationCallback Node ${peerNodeId} structure changed`);
45
+ break;
46
+ case NodeStateInformation.Decommissioned:
47
+ console.log(`stateInformationCallback Node ${peerNodeId} decommissioned`);
48
+ break;
49
+ }
50
+ },
51
+ };
52
+ }
53
+
54
+ export default function commands(theNode: MatterNode) {
55
+ return {
56
+ command: ["nodes", "node"],
57
+ describe: "Manage nodes",
58
+ builder: (yargs: Argv) =>
59
+ yargs
60
+ // Pair
61
+ .command(
62
+ ["*", "list [status]"],
63
+ "List all commissioned nodes",
64
+ yargs => {
65
+ return yargs.positional("status", {
66
+ describe: "status",
67
+ options: ["commissioned", "connected"] as const,
68
+ default: "commissioned",
69
+ type: "string",
70
+ });
71
+ },
72
+ async argv => {
73
+ const { status } = argv;
74
+ await theNode.start();
75
+ if (theNode.commissioningController === undefined) {
76
+ throw new Error("CommissioningController not initialized");
77
+ }
78
+ switch (status) {
79
+ case "commissioned": {
80
+ const details = theNode.commissioningController.getCommissionedNodesDetails();
81
+ details
82
+ .map(detail => ({
83
+ ...detail,
84
+ nodeId: detail.nodeId.toString(),
85
+ }))
86
+ .forEach(detail => {
87
+ console.log(detail);
88
+ });
89
+ break;
90
+ }
91
+ case "connected": {
92
+ const nodeIds = theNode.commissioningController
93
+ .getCommissionedNodes()
94
+ .filter(nodeId => !!theNode.commissioningController?.getPairedNode(nodeId));
95
+ console.log(nodeIds.map(nodeId => nodeId.toString()));
96
+ break;
97
+ }
98
+ }
99
+ },
100
+ )
101
+ .command(
102
+ "log [node-id]",
103
+ "Log the Structure of one node",
104
+ yargs => {
105
+ return yargs.positional("node-id", {
106
+ describe: "node id to log - if omitted the first node is logged.",
107
+ default: undefined,
108
+ type: "string",
109
+ });
110
+ },
111
+ async argv => {
112
+ const { nodeId } = argv;
113
+ const node = (await theNode.connectAndGetNodes(nodeId))[0];
114
+
115
+ console.log("Logging structure of Node ", node.nodeId.toString());
116
+ node.logStructure({});
117
+ },
118
+ )
119
+ .command(
120
+ "connect <node-id> [min-subscription-interval] [max-subscription-interval]",
121
+ "Connects to one or all commissioned nodes",
122
+ yargs => {
123
+ return yargs
124
+ .positional("node-id", {
125
+ describe: "node id to connect. Use 'all' to connect to all nodes.",
126
+ default: undefined,
127
+ type: "string",
128
+ demandOption: true,
129
+ })
130
+ .positional("min-subscription-interval", {
131
+ describe:
132
+ "Minimum subscription interval in seconds. If set then the node is subscribed to all attributes and events.",
133
+ type: "number",
134
+ })
135
+ .positional("max-subscription-interval", {
136
+ describe:
137
+ "Maximum subscription interval in seconds. If minimum interval is set and this not it will be determined automatically.",
138
+ type: "number",
139
+ });
140
+ },
141
+ async argv => {
142
+ const { nodeId: nodeIdStr, maxSubscriptionInterval, minSubscriptionInterval } = argv;
143
+ await theNode.start();
144
+ if (theNode.commissioningController === undefined) {
145
+ throw new Error("CommissioningController not initialized");
146
+ }
147
+ let nodeIds = theNode.commissioningController.getCommissionedNodes();
148
+ if (nodeIdStr !== "all") {
149
+ const cmdNodeId = NodeId(BigInt(nodeIdStr));
150
+ nodeIds = nodeIds.filter(nodeId => nodeId === cmdNodeId);
151
+ if (!nodeIds.length) {
152
+ throw new Error(`Node ${nodeIdStr} not commissioned`);
153
+ }
154
+ }
155
+
156
+ const autoSubscribe = minSubscriptionInterval !== undefined;
157
+
158
+ for (const nodeIdToProcess of nodeIds) {
159
+ await theNode.commissioningController.connectNode(nodeIdToProcess, {
160
+ autoSubscribe,
161
+ subscribeMinIntervalFloorSeconds: autoSubscribe ? minSubscriptionInterval : undefined,
162
+ subscribeMaxIntervalCeilingSeconds: autoSubscribe ? maxSubscriptionInterval : undefined,
163
+ ...createDiagnosticCallbacks(),
164
+ });
165
+ }
166
+ },
167
+ )
168
+ .command(
169
+ "disconnect <node-id>",
170
+ "Disconnects from one or all nodes",
171
+ yargs => {
172
+ return yargs.positional("node-id", {
173
+ describe: "node id to disconnect. Use 'all' to disconnect from all nodes.",
174
+ default: undefined,
175
+ type: "string",
176
+ demandOption: true,
177
+ });
178
+ },
179
+ async argv => {
180
+ const { nodeId: nodeIdStr } = argv;
181
+ if (theNode.commissioningController === undefined) {
182
+ console.log("Controller not initialized, nothing to disconnect.");
183
+ return;
184
+ }
185
+
186
+ let nodeIds = theNode.commissioningController.getCommissionedNodes();
187
+ if (nodeIdStr !== "all") {
188
+ const cmdNodeId = NodeId(BigInt(nodeIdStr));
189
+ nodeIds = nodeIds.filter(nodeId => nodeId === cmdNodeId);
190
+ if (!nodeIds.length) {
191
+ throw new Error(`Node ${nodeIdStr} not commissioned`);
192
+ }
193
+ }
194
+
195
+ for (const nodeIdToProcess of nodeIds) {
196
+ const node = theNode.commissioningController.getPairedNode(nodeIdToProcess);
197
+ if (node === undefined) {
198
+ console.log(`Node ${nodeIdToProcess} not connected`);
199
+ continue;
200
+ }
201
+ await node.disconnect();
202
+ }
203
+ },
204
+ )
205
+ .command(
206
+ "status <node-ids>",
207
+ "Logs the connection status for all or specified nodes",
208
+ yargs => {
209
+ return yargs.positional("node-ids", {
210
+ describe:
211
+ "node ids to connect (comma separated list allowed). Use 'all' to log status for all nodes.",
212
+ default: undefined,
213
+ type: "string",
214
+ demandOption: true,
215
+ });
216
+ },
217
+ async argv => {
218
+ const { nodeIds: nodeIdStr } = argv;
219
+ await theNode.start();
220
+ if (theNode.commissioningController === undefined) {
221
+ throw new Error("CommissioningController not initialized");
222
+ }
223
+ let nodeIds = theNode.commissioningController.getCommissionedNodes();
224
+ if (nodeIdStr !== "all") {
225
+ const nodeIdList = nodeIdStr.split(",").map(nodeId => NodeId(BigInt(nodeId)));
226
+ nodeIds = nodeIds.filter(nodeId => nodeIdList.includes(nodeId));
227
+ if (!nodeIds.length) {
228
+ throw new Error(`Node ${nodeIdStr} not commissioned`);
229
+ }
230
+ }
231
+
232
+ for (const nodeIdToProcess of nodeIds) {
233
+ const node = theNode.commissioningController.getPairedNode(nodeIdToProcess);
234
+ if (node === undefined) {
235
+ console.log(`Node ${nodeIdToProcess}: Not initialized`);
236
+ } else {
237
+ const basicInfo = node.basicInformation;
238
+ console.log(
239
+ `Node ${nodeIdToProcess}: Node Status: ${capitalize(decamelize(NodeStateInformation[node.nodeState], " "))}${basicInfo !== undefined ? ` (${basicInfo.vendorName} ${basicInfo.productName})` : ""}`,
240
+ );
241
+ }
242
+ }
243
+ },
244
+ ),
245
+ handler: async (argv: any) => {
246
+ argv.unhandled = true;
247
+ },
248
+ };
249
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { MatterNode } from "../MatterNode";
8
+
9
+ export default function commands(theNode: MatterNode) {
10
+ return {
11
+ command: "session",
12
+ describe: "Manage session",
13
+ builder: {},
14
+ handler: async () => {
15
+ if (!theNode.commissioningController) {
16
+ throw new Error("CommissioningController not initialized");
17
+ }
18
+
19
+ const sessions = theNode.commissioningController?.getActiveSessionInformation();
20
+ console.log(sessions);
21
+ },
22
+ };
23
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Logger } from "@matter/general";
8
+ import type { Argv } from "yargs";
9
+ import { MatterNode } from "../MatterNode.js";
10
+
11
+ export default function commands(theNode: MatterNode) {
12
+ return {
13
+ command: "subscribe [node-id]",
14
+ describe: "Subscribe to all events and attributes of a node",
15
+ builder: (yargs: Argv) => {
16
+ return yargs.positional("node-id", {
17
+ describe: "node id",
18
+ default: undefined,
19
+ type: "string",
20
+ });
21
+ },
22
+
23
+ handler: async (argv: any) => {
24
+ const { nodeId } = argv;
25
+ const node = (await theNode.connectAndGetNodes(nodeId))[0];
26
+
27
+ await node.subscribeAllAttributesAndEvents({
28
+ attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
29
+ console.log(
30
+ `${nodeId}: Attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(
31
+ value,
32
+ )}`,
33
+ ),
34
+ eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) =>
35
+ console.log(
36
+ `${nodeId} Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(
37
+ events,
38
+ )}`,
39
+ ),
40
+ });
41
+ },
42
+ };
43
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Bytes, Logger, serialize, UnexpectedDataError } from "#general";
8
+ import { TlvAny, TlvArrayReader, TlvElement, TlvStream, TlvType } from "#types";
9
+ import type { Argv } from "yargs";
10
+
11
+ const logger = Logger.get("tlv");
12
+
13
+ const TlvTypeNames: { [key: number]: string } = {
14
+ [TlvType.Null]: "(Null)",
15
+ [TlvType.Boolean]: "Boolean(",
16
+ [TlvType.UnsignedInt]: "UnsignedInt(",
17
+ [TlvType.SignedInt]: "SignedInt(",
18
+ [TlvType.Float]: "Float(",
19
+ [TlvType.Utf8String]: "Utf8String(",
20
+ [TlvType.ByteString]: "ByteString(",
21
+ [TlvType.Array]: "Array {",
22
+ [TlvType.List]: "List {",
23
+ [TlvType.Structure]: "Structure {",
24
+ [TlvType.EndOfContainer]: "}",
25
+ };
26
+
27
+ function logAnyTlvStream(encoded: TlvStream) {
28
+ if (encoded.length === 0) {
29
+ return undefined;
30
+ }
31
+ const reader = new TlvArrayReader(encoded);
32
+ logGenericElement(reader);
33
+ const nextElement = reader.readTagType();
34
+ if (nextElement !== undefined) {
35
+ throw new UnexpectedDataError(`Unexpected data left after parsing all data: ${Logger.toJSON(nextElement)}`);
36
+ }
37
+ }
38
+
39
+ function logGenericElement(reader: TlvArrayReader, preReadElement?: TlvElement<any>, allowTag = false) {
40
+ const element = preReadElement ?? reader.readTagType();
41
+ const {
42
+ tag,
43
+ typeLength: { type },
44
+ } = element;
45
+
46
+ switch (type) {
47
+ case TlvType.Null:
48
+ case TlvType.Boolean:
49
+ case TlvType.UnsignedInt:
50
+ case TlvType.SignedInt:
51
+ case TlvType.Float:
52
+ case TlvType.Utf8String:
53
+ case TlvType.ByteString:
54
+ if (tag !== undefined && !allowTag) {
55
+ throw new UnexpectedDataError(`Tag detected for a native type: ${Logger.toJSON(element)}`);
56
+ }
57
+ const value = reader.readPrimitive(element.typeLength);
58
+ const logValue = value instanceof Uint8Array ? Bytes.toHex(value) : value;
59
+ const logNumberHex =
60
+ typeof value === "number" || typeof value === "bigint" ? value.toString(16) : undefined;
61
+ logger.info(
62
+ tag?.id !== undefined ? `${tag.id}/0x${tag?.id?.toString(16)} => ` : "",
63
+ TlvTypeNames[type],
64
+ `${logValue}${logNumberHex !== undefined ? `/0x${logNumberHex}` : ""}`,
65
+ ")",
66
+ );
67
+ break;
68
+ case TlvType.Array:
69
+ case TlvType.List:
70
+ tag?.id !== undefined
71
+ ? logger.info(`${tag.id}/0x${tag.id.toString(16)} => `, TlvTypeNames[type])
72
+ : logger.info(TlvTypeNames[type]);
73
+ Logger.nest(() => logGenericArrayOrList(reader, type === TlvType.List));
74
+ logger.info(TlvTypeNames[TlvType.EndOfContainer]);
75
+ break;
76
+ case TlvType.Structure:
77
+ tag?.id !== undefined
78
+ ? logger.info(`${tag.id}/0x${tag.id.toString(16)} => `, TlvTypeNames[type])
79
+ : logger.info(TlvTypeNames[type]);
80
+ Logger.nest(() => logGenericStructure(reader));
81
+ logger.info(TlvTypeNames[TlvType.EndOfContainer]);
82
+ break;
83
+ default:
84
+ throw new UnexpectedDataError(`Unknown type: ${type}`);
85
+ }
86
+ }
87
+
88
+ function logGenericArrayOrList(reader: TlvArrayReader, allowTag = false) {
89
+ const result = new Array<any>();
90
+ while (true) {
91
+ const element = reader.readTagType();
92
+ const {
93
+ tag,
94
+ typeLength: { type },
95
+ } = element;
96
+ if (type === TlvType.EndOfContainer) break;
97
+ if (tag !== undefined && !allowTag) {
98
+ throw new UnexpectedDataError(`Tag detected : ${Logger.toJSON(element)}`);
99
+ }
100
+ logGenericElement(reader, element, allowTag);
101
+ }
102
+ return result;
103
+ }
104
+
105
+ function logGenericStructure(reader: TlvArrayReader) {
106
+ const result: { [key: string]: any } = {};
107
+ while (true) {
108
+ const element = reader.readTagType();
109
+ const {
110
+ tag,
111
+ typeLength: { type },
112
+ } = element;
113
+ if (type === TlvType.EndOfContainer) break;
114
+ if (tag === undefined || tag.id === undefined) {
115
+ throw new UnexpectedDataError(`Tag missing for a structure: ${Logger.toJSON(element)}`);
116
+ }
117
+ logGenericElement(reader, element, true);
118
+ }
119
+ return result;
120
+ }
121
+
122
+ export default function commands() {
123
+ return {
124
+ command: "tlv",
125
+ describe: "TLV decoding tools",
126
+ builder: (yargs: Argv) => {
127
+ return yargs
128
+ .command(
129
+ "decode [value]",
130
+ "Decodes a TLV string (hex) into matter.js JavaScript object",
131
+ yargs => {
132
+ return yargs.positional("value", {
133
+ describe: "The TLV value as hex string",
134
+ type: "string",
135
+ demandOption: true,
136
+ });
137
+ },
138
+ async argv => {
139
+ const { value } = argv;
140
+ const bytes = Bytes.fromHex(value);
141
+ const tlvEncoded = TlvAny.decode(bytes);
142
+ const decoded = TlvAny.decodeAnyTlvStream(tlvEncoded);
143
+
144
+ console.log("Decoded:", serialize(decoded));
145
+ },
146
+ )
147
+ .command(
148
+ "log [value]",
149
+ "Decodes a TLV string (hex string) and logs it's structure",
150
+ yargs => {
151
+ return yargs.positional("value", {
152
+ describe: "The TLV value as hex string",
153
+ type: "string",
154
+ demandOption: true,
155
+ });
156
+ },
157
+ async argv => {
158
+ const { value } = argv;
159
+ const bytes = Bytes.fromHex(value);
160
+ const tlvEncoded = TlvAny.decode(bytes);
161
+ logAnyTlvStream(tlvEncoded);
162
+ },
163
+ );
164
+ },
165
+ handler: async (argv: any) => {
166
+ argv.unhandled = true;
167
+ },
168
+ };
169
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": "../../tools/tsc/tsconfig.app.json",
3
+ "compilerOptions": {
4
+ "types": [
5
+ "node"
6
+ ]
7
+ },
8
+ "references": [
9
+ {
10
+ "path": "../../nodejs-ble/src"
11
+ },
12
+ {
13
+ "path": "../../nodejs/src"
14
+ },
15
+ {
16
+ "path": "../../general/src"
17
+ },
18
+ {
19
+ "path": "../../matter.js/src"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Quote aware Command line parser
3
+ *
4
+ * @license
5
+ * Copyright 2022-2024 Matter.js Authors
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+
9
+ /**
10
+ * This is a Typescript rewrite of https://github.com/cdhowie/shell-parser/
11
+ *
12
+ * Copyright 2019 Chris Howie
13
+ *
14
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
15
+ * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
16
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
17
+ * persons to whom the Software is furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
20
+ * Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
24
+ * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
25
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ */
27
+
28
+ class StringReader {
29
+ private pos: number;
30
+ public current: string | undefined;
31
+
32
+ constructor(private readonly str: string) {
33
+ this.pos = -1;
34
+ }
35
+
36
+ next() {
37
+ if (this.pos < this.str.length) {
38
+ this.current = this.str[++this.pos];
39
+ }
40
+
41
+ return this.current;
42
+ }
43
+ }
44
+
45
+ function isWhitespace(c: string) {
46
+ return c === " ";
47
+ }
48
+
49
+ function consumeWhitespace(r: StringReader) {
50
+ while (r.current !== undefined && isWhitespace(r.current)) {
51
+ r.next();
52
+ }
53
+ }
54
+
55
+ function escaped(r: StringReader) {
56
+ const n = r.next();
57
+ if (n === undefined) {
58
+ throw new Error("Unterminated escape sequence");
59
+ }
60
+
61
+ return n;
62
+ }
63
+
64
+ function quoted(r: StringReader) {
65
+ let s = "";
66
+
67
+ const q = r.current;
68
+ const e = q === '"';
69
+
70
+ for (let c = r.next(); c !== q; c = r.next()) {
71
+ if (c === undefined) {
72
+ throw new Error(`Unterminated quote: ${q}`);
73
+ }
74
+
75
+ if (e && c === "\\") {
76
+ s += escaped(r);
77
+ } else {
78
+ s += c;
79
+ }
80
+ }
81
+
82
+ return s;
83
+ }
84
+
85
+ export function commandlineParser(line: string) {
86
+ const args = [];
87
+ let s = "";
88
+
89
+ const r = new StringReader(line);
90
+ r.next();
91
+ consumeWhitespace(r);
92
+
93
+ // Indicates if we saw a quote in the current argument. This is necessary
94
+ // to allow empty arguments ('' or ""), otherwise we have no way to
95
+ // distinguish this case from nothing.
96
+ let sawQuote = false;
97
+
98
+ while (r.current !== undefined) {
99
+ if (isWhitespace(r.current)) {
100
+ args.push(s);
101
+ sawQuote = false;
102
+ s = "";
103
+
104
+ consumeWhitespace(r);
105
+ } else if (r.current == "\\") {
106
+ s += escaped(r);
107
+ r.next();
108
+ } else if (r.current === "'" || r.current === '"') {
109
+ s += quoted(r);
110
+ sawQuote = true;
111
+ r.next();
112
+ } else {
113
+ s += r.current;
114
+ r.next();
115
+ }
116
+ }
117
+
118
+ if (sawQuote || s.length) {
119
+ args.push(s);
120
+ }
121
+
122
+ return args;
123
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2024 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Bytes } from "@matter/general";
8
+ import { ValueModel } from "@matter/model";
9
+ import { ValidationDatatypeMismatchError } from "@project-chip/matter.js/common";
10
+ import { camelize } from "./String";
11
+
12
+ export function convertJsonDataWithModel(model: ValueModel, data: any): any {
13
+ const definingModel = model.definingModel ?? model;
14
+ switch (definingModel.effectiveMetatype) {
15
+ case "array":
16
+ if (!Array.isArray(data)) {
17
+ throw new ValidationDatatypeMismatchError(`Expected array, got ${typeof data}`);
18
+ }
19
+ return data.map(item => convertJsonDataWithModel(definingModel.children[0], item));
20
+ case "object":
21
+ if (typeof data !== "object") {
22
+ throw new ValidationDatatypeMismatchError(`Expected object, got ${typeof data}`);
23
+ }
24
+ for (const child of definingModel.children) {
25
+ const childKeyName = camelize(child.name);
26
+ data[childKeyName] = convertJsonDataWithModel(child, data[childKeyName]);
27
+ }
28
+ return data;
29
+ case "integer":
30
+ if (typeof data === "string") {
31
+ if (definingModel.metabase?.byteSize !== undefined && definingModel.metabase.byteSize > 6) {
32
+ // If we have an integer with byteSize > 6 and a string value, we need to convert the string to a
33
+ // BigInt also handles 0x prefixed hex strings
34
+ return BigInt(data);
35
+ } else if (data.startsWith("0x")) {
36
+ // Else if hex string convert to number
37
+ return parseInt(data.substring(2), 16);
38
+ }
39
+ }
40
+ break;
41
+ case "bytes":
42
+ if (typeof data === "string") {
43
+ // ByteArray encoded as hex-String ... so convert to ByteArray
44
+ return Bytes.fromHex(data);
45
+ }
46
+ break;
47
+ }
48
+
49
+ return data;
50
+ }