nova-control-command 0.0.2 → 0.0.4
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/dist/nova-control-command.js +92 -91
- package/package.json +1 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath as e } from "node:url";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import r from "node
|
|
6
|
-
import i from "node:
|
|
3
|
+
import { realpathSync as t } from "node:fs";
|
|
4
|
+
import { Command as n } from "commander";
|
|
5
|
+
import { openNova as r } from "nova-control-node";
|
|
6
|
+
import i from "node:readline";
|
|
7
|
+
import a from "node:fs/promises";
|
|
7
8
|
//#region src/CommandTokenizer.ts
|
|
8
|
-
function
|
|
9
|
+
function o(e) {
|
|
9
10
|
let t = [], n = "", r = 0;
|
|
10
11
|
for (; r < e.length;) {
|
|
11
12
|
let i = e[r];
|
|
@@ -34,24 +35,24 @@ function a(e) {
|
|
|
34
35
|
}
|
|
35
36
|
//#endregion
|
|
36
37
|
//#region src/REPL.ts
|
|
37
|
-
async function
|
|
38
|
-
let n = process.stdin.isTTY,
|
|
38
|
+
async function s(e, t) {
|
|
39
|
+
let n = process.stdin.isTTY, r = n ? `\x1b[1m${e}>\x1b[0m ` : `${e}> `, a = i.createInterface({
|
|
39
40
|
input: process.stdin,
|
|
40
41
|
output: process.stdout,
|
|
41
42
|
terminal: n,
|
|
42
|
-
prompt:
|
|
43
|
+
prompt: r
|
|
43
44
|
});
|
|
44
|
-
n && (process.stdout.write("NOVA interactive shell — type \"help [command]\" for help, \"exit\" to quit\n"),
|
|
45
|
-
for await (let r of
|
|
45
|
+
n && (process.stdout.write("NOVA interactive shell — type \"help [command]\" for help, \"exit\" to quit\n"), a.prompt());
|
|
46
|
+
for await (let r of a) {
|
|
46
47
|
let i = r.trim();
|
|
47
48
|
if (i === "" || i.startsWith("#")) {
|
|
48
|
-
n &&
|
|
49
|
+
n && a.prompt();
|
|
49
50
|
continue;
|
|
50
51
|
}
|
|
51
52
|
if (i === "exit" || i === "quit") break;
|
|
52
|
-
let s =
|
|
53
|
+
let s = o(i);
|
|
53
54
|
if (s.length === 0) {
|
|
54
|
-
n &&
|
|
55
|
+
n && a.prompt();
|
|
55
56
|
continue;
|
|
56
57
|
}
|
|
57
58
|
try {
|
|
@@ -59,48 +60,48 @@ async function o(e, t) {
|
|
|
59
60
|
} catch (t) {
|
|
60
61
|
process.stderr.write(`${e}: ${t.message}\n`);
|
|
61
62
|
}
|
|
62
|
-
n &&
|
|
63
|
+
n && a.prompt();
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
+
a.close();
|
|
65
66
|
}
|
|
66
67
|
//#endregion
|
|
67
68
|
//#region src/ScriptRunner.ts
|
|
68
|
-
async function
|
|
69
|
-
let
|
|
70
|
-
if (t === "-")
|
|
69
|
+
async function c(e, t, n) {
|
|
70
|
+
let r;
|
|
71
|
+
if (t === "-") r = process.stdin;
|
|
71
72
|
else try {
|
|
72
|
-
|
|
73
|
+
r = (await a.open(t)).createReadStream();
|
|
73
74
|
} catch {
|
|
74
75
|
return process.stderr.write(`nova-control: cannot open script '${t}'\n`), 2;
|
|
75
76
|
}
|
|
76
|
-
let s =
|
|
77
|
-
input:
|
|
77
|
+
let s = i.createInterface({
|
|
78
|
+
input: r,
|
|
78
79
|
terminal: !1
|
|
79
|
-
}),
|
|
80
|
+
}), c = 0;
|
|
80
81
|
for await (let t of s) {
|
|
81
82
|
let r = t.trim();
|
|
82
83
|
if (r === "" || r.startsWith("#")) continue;
|
|
83
|
-
let i =
|
|
84
|
+
let i = o(r);
|
|
84
85
|
if (i.length === 0) continue;
|
|
85
|
-
let
|
|
86
|
+
let a = 0;
|
|
86
87
|
try {
|
|
87
|
-
|
|
88
|
+
a = await n(i);
|
|
88
89
|
} catch (e) {
|
|
89
|
-
|
|
90
|
+
a = 1, process.stderr.write(`nova-control: ${e.message}\n`);
|
|
90
91
|
}
|
|
91
|
-
if (
|
|
92
|
-
case "stop": return s.close(),
|
|
92
|
+
if (a !== 0) switch (c = a, e) {
|
|
93
|
+
case "stop": return s.close(), a;
|
|
93
94
|
case "continue": break;
|
|
94
95
|
case "ask":
|
|
95
|
-
if (!await
|
|
96
|
+
if (!await l()) return s.close(), a;
|
|
96
97
|
break;
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
|
-
return s.close(),
|
|
100
|
+
return s.close(), c;
|
|
100
101
|
}
|
|
101
|
-
async function
|
|
102
|
+
async function l() {
|
|
102
103
|
return process.stdin.isTTY ? new Promise((e) => {
|
|
103
|
-
let t =
|
|
104
|
+
let t = i.createInterface({
|
|
104
105
|
input: process.stdin,
|
|
105
106
|
output: process.stdout
|
|
106
107
|
});
|
|
@@ -111,105 +112,105 @@ async function c() {
|
|
|
111
112
|
}
|
|
112
113
|
//#endregion
|
|
113
114
|
//#region src/nova-control-command.ts
|
|
114
|
-
var
|
|
115
|
+
var u = {
|
|
115
116
|
OK: 0,
|
|
116
117
|
GeneralError: 1,
|
|
117
118
|
UsageError: 2
|
|
118
|
-
},
|
|
119
|
+
}, d = class extends Error {
|
|
119
120
|
ExitCode;
|
|
120
|
-
constructor(e, t =
|
|
121
|
+
constructor(e, t = u.GeneralError) {
|
|
121
122
|
super(e), this.name = "NovaCommandError", this.ExitCode = t;
|
|
122
123
|
}
|
|
123
|
-
},
|
|
124
|
-
async function
|
|
125
|
-
if (
|
|
126
|
-
return
|
|
124
|
+
}, f = "nova-control", p, m = 9600, h = "stop", g;
|
|
125
|
+
async function _() {
|
|
126
|
+
if (p == null) throw new d("--port is required — specify the serial port (e.g. /dev/ttyACM0 or COM3)", u.UsageError);
|
|
127
|
+
return g ??= await r(p, m), g;
|
|
127
128
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
129
|
+
function v() {
|
|
130
|
+
g != null && (g.destroy(), g = void 0);
|
|
130
131
|
}
|
|
131
|
-
function
|
|
132
|
-
|
|
132
|
+
function y(e, t = 9600, n = "stop") {
|
|
133
|
+
p = e, m = t, h = n;
|
|
133
134
|
}
|
|
134
|
-
function
|
|
135
|
-
|
|
135
|
+
function b() {
|
|
136
|
+
v(), p = void 0, m = 9600, h = "stop";
|
|
136
137
|
}
|
|
137
|
-
function
|
|
138
|
+
function x(e) {
|
|
138
139
|
e.exitOverride(), e.configureOutput({ writeErr: () => {} });
|
|
139
|
-
for (let t of e.commands)
|
|
140
|
+
for (let t of e.commands) x(t);
|
|
140
141
|
}
|
|
141
|
-
function
|
|
142
|
-
let
|
|
143
|
-
return
|
|
142
|
+
function S(e = !1) {
|
|
143
|
+
let t = new n(f);
|
|
144
|
+
return t.description("NOVA robot arm CLI").allowUnknownOption(!1).configureOutput({ writeErr: () => {} }), e || (t.option("--port <path>", "serial port path (e.g. /dev/ttyACM0 on Linux/macOS, COM3 on Windows)").option("--baud <rate>", "baud rate (default: 9600)", "9600").option("--on-error <mode>", "script error mode: stop | continue | ask (default: stop)"), t.hook("preAction", (e, t) => {
|
|
144
145
|
let n = t.optsWithGlobals();
|
|
145
|
-
|
|
146
|
-
})),
|
|
147
|
-
await (await
|
|
148
|
-
}),
|
|
146
|
+
p = n.port, m = Number(n.baud ?? "9600"), h = n.onError ?? "stop";
|
|
147
|
+
})), t.command("home").description("send all servos to their home positions").action(async () => {
|
|
148
|
+
await (await _()).home();
|
|
149
|
+
}), t.command("move").description("set one or more servo positions without interrupting the others").option("--shift-to <deg>", "shift head forward (>90°) or back (<90°) — s1").option("--roll-to <deg>", "roll head clockwise (>90°) or counter-clockwise (<90°) — s2").option("--pitch-to <deg>", "pitch head up (>110°) or down (<110°) — s3").option("--rotate-to <deg>", "rotate body around Z-axis — s4").option("--lift-to <deg>", "lift head on secondary axis, range 20°–150° — s5").action(async (e) => {
|
|
149
150
|
let t = {};
|
|
150
|
-
if (e.shiftTo != null && (t.s1 = Number(e.shiftTo)), e.rollTo != null && (t.s2 = Number(e.rollTo)), e.pitchTo != null && (t.s3 = Number(e.pitchTo)), e.rotateTo != null && (t.s4 = Number(e.rotateTo)), e.liftTo != null && (t.s5 = Number(e.liftTo)), Object.keys(t).length === 0) throw new
|
|
151
|
-
let n = await
|
|
151
|
+
if (e.shiftTo != null && (t.s1 = Number(e.shiftTo)), e.rollTo != null && (t.s2 = Number(e.rollTo)), e.pitchTo != null && (t.s3 = Number(e.pitchTo)), e.rotateTo != null && (t.s4 = Number(e.rotateTo)), e.liftTo != null && (t.s5 = Number(e.liftTo)), Object.keys(t).length === 0) throw new d("move: specify at least one servo option (--shift-to, --roll-to, --pitch-to, --rotate-to, --lift-to)", u.UsageError);
|
|
152
|
+
let n = await _();
|
|
152
153
|
n.State = t, await n.sendServoState();
|
|
153
|
-
}),
|
|
154
|
-
let t = await
|
|
154
|
+
}), t.command("shift-to").description("shift head forward (>90°) or back (<90°) — s1").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
155
|
+
let t = await _();
|
|
155
156
|
t.State = { s1: Number(e) }, await t.sendServoState();
|
|
156
|
-
}),
|
|
157
|
-
let t = await
|
|
157
|
+
}), t.command("roll-to").description("roll head clockwise (>90°) or counter-clockwise (<90°) — s2").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
158
|
+
let t = await _();
|
|
158
159
|
t.State = { s2: Number(e) }, await t.sendServoState();
|
|
159
|
-
}),
|
|
160
|
-
let t = await
|
|
160
|
+
}), t.command("pitch-to").description("pitch head up (>110°) or down (<110°) — s3").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
161
|
+
let t = await _();
|
|
161
162
|
t.State = { s3: Number(e) }, await t.sendServoState();
|
|
162
|
-
}),
|
|
163
|
-
let t = await
|
|
163
|
+
}), t.command("rotate-to").description("rotate body around Z-axis — s4").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
164
|
+
let t = await _();
|
|
164
165
|
t.State = { s4: Number(e) }, await t.sendServoState();
|
|
165
|
-
}),
|
|
166
|
-
let t = await
|
|
166
|
+
}), t.command("lift-to").description("lift head on secondary axis, range 20°–150° — s5").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
167
|
+
let t = await _();
|
|
167
168
|
t.State = { s5: Number(e) }, await t.sendServoState();
|
|
168
|
-
}),
|
|
169
|
+
}), t.command("wait").description("pause for <ms> milliseconds before the next command").argument("<ms>", "duration in milliseconds (non-negative number)").action(async (e) => {
|
|
169
170
|
let t = Number(e);
|
|
170
|
-
if (isNaN(t) || t < 0) throw new
|
|
171
|
+
if (isNaN(t) || t < 0) throw new d(`wait: invalid duration '${e}' — expected a non-negative number`, u.UsageError);
|
|
171
172
|
await new Promise((e) => setTimeout(e, t));
|
|
172
|
-
}),
|
|
173
|
-
let e = await
|
|
173
|
+
}), t.command("state").description("print the current servo state as JSON").action(async () => {
|
|
174
|
+
let e = await _();
|
|
174
175
|
process.stdout.write(JSON.stringify(e.State) + "\n");
|
|
175
|
-
}), e || (
|
|
176
|
-
await
|
|
177
|
-
}),
|
|
176
|
+
}), e || (t.command("shell").description("start an interactive REPL").action(async () => {
|
|
177
|
+
await s(f, (e) => C(e));
|
|
178
|
+
}), t.option("--script <file>", "run commands from a script file (use - for stdin)").action(async (e) => {
|
|
178
179
|
if (e.script != null) {
|
|
179
|
-
let t = await
|
|
180
|
+
let t = await c(h, e.script, C);
|
|
180
181
|
process.exit(t);
|
|
181
|
-
} else process.stdout.write(
|
|
182
|
-
}),
|
|
182
|
+
} else process.stdout.write(t.helpInformation()), process.exit(u.OK);
|
|
183
|
+
}), t.addHelpCommand(!0)), t;
|
|
183
184
|
}
|
|
184
|
-
async function
|
|
185
|
-
if (e.length === 0) return
|
|
186
|
-
let t =
|
|
187
|
-
|
|
185
|
+
async function C(e) {
|
|
186
|
+
if (e.length === 0) return u.OK;
|
|
187
|
+
let t = S(!0);
|
|
188
|
+
x(t);
|
|
188
189
|
try {
|
|
189
190
|
return await t.parseAsync([
|
|
190
191
|
"node",
|
|
191
|
-
|
|
192
|
+
f,
|
|
192
193
|
...e
|
|
193
|
-
]),
|
|
194
|
+
]), u.OK;
|
|
194
195
|
} catch (t) {
|
|
195
196
|
let n = t;
|
|
196
|
-
return n.code === "commander.help" || n.code === "commander.helpDisplayed" ?
|
|
197
|
+
return n.code === "commander.help" || n.code === "commander.helpDisplayed" ? u.OK : n.code === "commander.unknownCommand" ? (process.stderr.write(`${f}: unknown command '${e[0]}' — try '${f} help'\n`), u.UsageError) : n.code === "commander.unknownOption" || n.code === "commander.missingArgument" || n.code === "commander.missingMandatoryOptionValue" ? (process.stderr.write(`${f}: ${n.message}\n`), u.UsageError) : t instanceof d ? (process.stderr.write(`${f}: ${t.message}\n`), t.ExitCode) : (process.stderr.write(`${f}: ${t.message ?? String(t)}\n`), u.GeneralError);
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
|
-
async function
|
|
200
|
-
let e =
|
|
201
|
-
|
|
200
|
+
async function w() {
|
|
201
|
+
let e = S();
|
|
202
|
+
x(e);
|
|
202
203
|
try {
|
|
203
204
|
await e.parseAsync(process.argv);
|
|
204
205
|
} catch (t) {
|
|
205
206
|
let n = t;
|
|
206
|
-
(n.code === "commander.help" || n.code === "commander.helpDisplayed" || n.code === "commander.version") && process.exit(
|
|
207
|
+
(n.code === "commander.help" || n.code === "commander.helpDisplayed" || n.code === "commander.version") && process.exit(u.OK), (n.code === "commander.unknownCommand" || n.code === "commander.unknownOption" || n.code === "commander.missingArgument" || n.code === "commander.missingMandatoryOptionValue") && (process.stderr.write(`${f}: ${n.message}\n\n`), process.stderr.write(e.helpInformation()), process.exit(u.UsageError)), t instanceof d && (process.stderr.write(`${f}: ${t.message}\n`), process.exit(t.ExitCode)), process.stderr.write(`${f}: ${t.message ?? String(t)}\n`), process.exit(u.GeneralError);
|
|
207
208
|
} finally {
|
|
208
|
-
|
|
209
|
+
v();
|
|
209
210
|
}
|
|
210
211
|
}
|
|
211
|
-
process.argv[1] === e(import.meta.url) &&
|
|
212
|
-
process.stderr.write(`${
|
|
212
|
+
t(process.argv[1]) === e(import.meta.url) && w().catch((e) => {
|
|
213
|
+
process.stderr.write(`${f}: fatal: ${e.message ?? e}\n`), process.exit(u.GeneralError);
|
|
213
214
|
});
|
|
214
215
|
//#endregion
|
|
215
|
-
export {
|
|
216
|
+
export { b as _destroyForTests, y as _setupForTests, C as executeTokens };
|