code wiki / _hdl_build / nx_printer_ctl.nx
nx_printer_ctl.nx source
↩ module page · 252 lines · 10151 B
1// nx_printer_ctl.nx -- sovereign 3D-printer / home-IoT control ORGAN (CLI + MCP tool).
2//
3// The operator's ask: integrate the 3D printers + home electronics into the
4// existing website behind the opaque secure login. The pasted reference design
5// used Node/Express + nginx + http-proxy-middleware + WAN mTLS. This organ is
6// the SOVEREIGN realization on the nishi fabric (no third-party stack, per the
7// ecosystem-only + api-first doctrine): the sovereign edge already does opaque
8// cap-token auth + reverse-proxy; this organ is the cap-gated LAN control leg.
9//
10// Verbs (JSON on stdout = the MCP/API/agent interchange):
11// nx_printer_ctl info <ip> <port> [apikey] -- GET /printer/info
12// nx_printer_ctl estop <ip> <port> [apikey] -- POST /printer/emergency_stop
13// nx_printer_ctl pause <ip> <port> [apikey] -- POST /printer/print/pause
14// nx_printer_ctl resume <ip> <port> [apikey] -- POST /printer/print/resume
15// nx_printer_ctl cancel <ip> <port> [apikey] -- POST /printer/print/cancel
16// nx_printer_ctl contract -- emit the security/routing contract (no network)
17//
18// Composes nx_printer_ctl_lib (parse/emit/allowlist) over the shipped Moonraker
19// primitives. NEVER-BRICK by construction: fixed safe-endpoint allowlist, no
20// raw-gcode passthrough (see the lib header + nx_printer_ctl_gate).
21//
22// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
23import "nx_syscalls.nx"
24import "nx_printer_ctl_lib.nx"
25
26const PC_STDERR: i64 = 2
27const PC_EXIT_USAGE: i64 = 2
28
29func pc_werr(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(PC_STDERR, s, n); return 0 }
30
31func main(argc: i64, argv: *i64) -> i64 {
32 if argc < 2 {
33 pc_werr("usage: nx_printer_ctl info|status|ready|files|progress|estop|pause|resume|cancel <ip> <port> [apikey] | printstart <ip> <port> <file> | upload <ip> <port> <local> <remote> | contract | discover <a.b.c> [port] | survey <a.b.c> | monitor <a.b.c>\n" as *u8)
34 sys_exit(PC_EXIT_USAGE)
35 return PC_EXIT_USAGE
36 }
37 let verb: *u8 = argv[1] as *u8
38 let id: i64 = pctl_verb_id(verb)
39 if id == PCTL_V_BAD {
40 pc_werr("usage: nx_printer_ctl info|status|ready|files|progress|estop|pause|resume|cancel <ip> <port> [apikey] | printstart <ip> <port> <file> | upload <ip> <port> <local> <remote> | contract | discover <a.b.c> [port] | survey <a.b.c> | monitor <a.b.c>\n" as *u8)
41 sys_exit(PC_EXIT_USAGE)
42 return PC_EXIT_USAGE
43 }
44
45 let out: *u8 = sys_mmap(PCTL_OUT)
46
47 // contract verb needs no network / no args
48 if id == PCTL_V_CONTRACT {
49 let co: i64 = pctl_emit_contract(out)
50 sys_write(1, out, co)
51 sys_exit(0)
52 return 0
53 }
54
55 // discover verb: <subnet-prefix a.b.c> [port] (LAN sweep, no single host)
56 if id == PCTL_V_DISCOVER {
57 if argc < 3 {
58 pc_werr("usage: nx_printer_ctl discover <subnet-prefix a.b.c> [port]\n" as *u8)
59 sys_exit(PC_EXIT_USAGE)
60 return PC_EXIT_USAGE
61 }
62 let prefix: *u8 = argv[2] as *u8
63 let base24: i64 = pctl_parse_prefix(prefix)
64 if base24 < 0 {
65 pc_werr("bad subnet prefix (expect a.b.c, e.g. 192.168.8)\n" as *u8)
66 sys_exit(PC_EXIT_USAGE)
67 return PC_EXIT_USAGE
68 }
69 var dport: i64 = 7125
70 if argc > 3 {
71 let pp: i64 = pctl_parse_port(argv[3] as *u8)
72 if pp > 0 { dport = pp }
73 }
74 let ips: *i64 = sys_mmap(254 * 8) as *i64
75 // 250ms single-window budget: responsive devices (a live printer <50ms) are found;
76 // unreachable hosts are not waited on -> the full /24 sweep fits the MCP call window.
77 let cnt: i64 = pctl_discover_scan(base24, dport, 250, ips, 254)
78 let do2: i64 = pctl_emit_discover(out, prefix, dport, ips, cnt)
79 sys_write(1, out, do2)
80 sys_exit(0)
81 return 0
82 }
83
84 // survey verb: <subnet-prefix a.b.c> (multi-port home-device inventory)
85 if id == PCTL_V_SURVEY {
86 if argc < 3 {
87 pc_werr("usage: nx_printer_ctl survey <subnet-prefix a.b.c>\n" as *u8)
88 sys_exit(PC_EXIT_USAGE)
89 return PC_EXIT_USAGE
90 }
91 let sprefix: *u8 = argv[2] as *u8
92 let sbase24: i64 = pctl_parse_prefix(sprefix)
93 if sbase24 < 0 {
94 pc_werr("bad subnet prefix (expect a.b.c, e.g. 192.168.8)\n" as *u8)
95 sys_exit(PC_EXIT_USAGE)
96 return PC_EXIT_USAGE
97 }
98 let mask: *i64 = sys_mmap(256 * 8) as *i64
99 pctl_survey_scan(sbase24, LSCAN_BUDGET_MS, mask)
100 let so: i64 = pctl_emit_survey(out, sprefix, sbase24, mask)
101 sys_write(1, out, so)
102 sys_exit(0)
103 return 0
104 }
105
106 // monitor verb: <subnet-prefix a.b.c> (whole dashboard JSON in ONE sovereign call;
107 // replaces the beat's shell grep/date -- the beat just launches this + publishes)
108 if id == PCTL_V_MONITOR {
109 if argc < 3 {
110 pc_werr("usage: nx_printer_ctl monitor <subnet-prefix a.b.c>\n" as *u8)
111 sys_exit(PC_EXIT_USAGE)
112 return PC_EXIT_USAGE
113 }
114 let mprefix: *u8 = argv[2] as *u8
115 let mbase24: i64 = pctl_parse_prefix(mprefix)
116 if mbase24 < 0 {
117 pc_werr("bad subnet prefix (expect a.b.c, e.g. 192.168.8)\n" as *u8)
118 sys_exit(PC_EXIT_USAGE)
119 return PC_EXIT_USAGE
120 }
121 let mout: *u8 = sys_mmap(65536)
122 let mo: i64 = pctl_build_monitor(mout, mprefix, mbase24)
123 sys_write(1, mout, mo)
124 sys_exit(0)
125 return 0
126 }
127
128 // printstart verb: <ip> <port> <filename> (start a NAMED pre-sliced file; never raw gcode)
129 if id == PCTL_V_PRINTSTART {
130 if argc < 5 {
131 pc_werr("usage: nx_printer_ctl printstart <ip> <port> <filename>\n" as *u8)
132 sys_exit(PC_EXIT_USAGE)
133 return PC_EXIT_USAGE
134 }
135 let ps_ip: *u8 = argv[2] as *u8
136 let ps_ipv4: i64 = pctl_parse_ip(ps_ip)
137 if ps_ipv4 < 0 {
138 pc_werr("bad ip (expect dotted IPv4 a.b.c.d)\n" as *u8)
139 sys_exit(PC_EXIT_USAGE)
140 return PC_EXIT_USAGE
141 }
142 let ps_port: i64 = pctl_parse_port(argv[3] as *u8)
143 if ps_port < 0 {
144 pc_werr("bad port (expect 1..65535)\n" as *u8)
145 sys_exit(PC_EXIT_USAGE)
146 return PC_EXIT_USAGE
147 }
148 let ps_fn: *u8 = argv[4] as *u8
149 let ps_fnlen: i64 = pctl_slen(ps_fn)
150 let ps_host: *u8 = sys_mmap(64)
151 let ps_hl: i64 = pctl_build_host(ps_ip, ps_port, ps_host)
152 let ps_out: *u8 = sys_mmap(PCTL_OUT)
153 let ps_o: i64 = pctl_printstart(ps_out, ps_ipv4, ps_port, ps_host, ps_hl, ps_fn, ps_fnlen)
154 sys_write(1, ps_out, ps_o)
155 sys_exit(0)
156 return 0
157 }
158
159 // upload verb: <ip> <port> <local-gcode-path> <remote-filename> (multipart -> printer files)
160 if id == PCTL_V_UPLOAD {
161 if argc < 6 {
162 pc_werr("usage: nx_printer_ctl upload <ip> <port> <local-gcode-path> <remote-filename>\n" as *u8)
163 sys_exit(PC_EXIT_USAGE)
164 return PC_EXIT_USAGE
165 }
166 let up_ip: *u8 = argv[2] as *u8
167 let up_ipv4: i64 = pctl_parse_ip(up_ip)
168 if up_ipv4 < 0 {
169 pc_werr("bad ip (expect dotted IPv4)\n" as *u8)
170 sys_exit(PC_EXIT_USAGE)
171 return PC_EXIT_USAGE
172 }
173 let up_port: i64 = pctl_parse_port(argv[3] as *u8)
174 if up_port < 0 {
175 pc_werr("bad port\n" as *u8)
176 sys_exit(PC_EXIT_USAGE)
177 return PC_EXIT_USAGE
178 }
179 let up_local: *u8 = argv[4] as *u8
180 let up_remote: *u8 = argv[5] as *u8
181 let up_lenp: *i64 = sys_mmap(8) as *i64
182 let up_gcode: *u8 = sys_read_file(up_local, up_lenp)
183 let up_out: *u8 = sys_mmap(4096)
184 if (up_gcode as i64) == 0 {
185 let uo: i64 = pctl_cat(up_out, 0, "{\"verb\":\"upload\",\"result\":\"READ_FAIL\",\"note\":\"local gcode path unreadable\"}\n" as *u8)
186 sys_write(1, up_out, uo)
187 sys_exit(1)
188 return 1
189 }
190 let up_glen: i64 = up_lenp[0]
191 let up_host: *u8 = sys_mmap(64)
192 let up_hl: i64 = pctl_build_host(up_ip, up_port, up_host)
193 let up_o: i64 = pctl_upload_send(up_out, up_ipv4, up_port, up_host, up_remote, up_gcode, up_glen)
194 sys_write(1, up_out, up_o)
195 sys_exit(0)
196 return 0
197 }
198
199 // network verbs need <ip> <port>
200 if argc < 4 {
201 pc_werr("usage: nx_printer_ctl <verb> <ip> <port> [apikey]\n" as *u8)
202 sys_exit(PC_EXIT_USAGE)
203 return PC_EXIT_USAGE
204 }
205 let ip_s: *u8 = argv[2] as *u8
206 let port_s: *u8 = argv[3] as *u8
207 let ipv4: i64 = pctl_parse_ip(ip_s)
208 if ipv4 < 0 {
209 pc_werr("bad ip (expect dotted IPv4 a.b.c.d)\n" as *u8)
210 sys_exit(PC_EXIT_USAGE)
211 return PC_EXIT_USAGE
212 }
213 let port: i64 = pctl_parse_port(port_s)
214 if port < 0 {
215 pc_werr("bad port (expect 1..65535)\n" as *u8)
216 sys_exit(PC_EXIT_USAGE)
217 return PC_EXIT_USAGE
218 }
219 var api_key: *u8 = "\x00" as *u8
220 var api_key_len: i64 = 0
221 if argc > 4 {
222 api_key = argv[4] as *u8
223 api_key_len = pctl_slen(api_key)
224 }
225
226 let host: *u8 = sys_mmap(64)
227 let host_len: i64 = pctl_build_host(ip_s, port, host)
228 let scratch: *u8 = sys_mmap(PCTL_SCRATCH)
229 let resp: *u8 = sys_mmap(PCTL_RESP)
230 let rn: *i64 = sys_mmap(8) as *i64
231 rn[0] = 0
232
233 let status: i64 = pctl_call(id, ipv4, port, host, host_len, api_key, api_key_len, scratch, resp, rn)
234 let n: i64 = rn[0]
235 var eo: i64 = 0
236 if id == PCTL_V_STATUS {
237 eo = pctl_emit_status(out, host, status, resp, n)
238 } else {
239 if id == PCTL_V_READY {
240 eo = pctl_emit_ready(out, host, status, resp, n)
241 } else {
242 if id == PCTL_V_PROGRESS {
243 eo = pctl_emit_progress(out, host, status, resp, n)
244 } else {
245 eo = pctl_emit_result(out, id, host, status, resp, n)
246 }
247 }
248 }
249 sys_write(1, out, eo)
250 sys_exit(0)
251 return 0
252}