axiometa-cli 1.0.2__tar.gz → 1.0.4__tar.gz
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.
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/PKG-INFO +1 -1
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/__init__.py +9 -1
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/core.py +30 -4
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/flash.py +11 -5
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/interactive.py +15 -10
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/PKG-INFO +1 -1
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/pyproject.toml +1 -1
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/README.md +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/__main__.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/auth.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/chat.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/cli.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/detect.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/knowledge_client.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/provision.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/serve.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/setup.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/ui.py +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/SOURCES.txt +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/dependency_links.txt +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/entry_points.txt +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/requires.txt +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/top_level.txt +0 -0
- {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axiometa-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: Prompt to hardware, in your terminal: detect a board, chat to build, deploy, watch serial.
|
|
5
5
|
Author-email: Axiometa <dr.dumcius@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://axiometa.io
|
|
@@ -16,4 +16,12 @@ Three ideas the CLI implements, all grounded in the backend registries:
|
|
|
16
16
|
- The serial CHANNEL is one contract; only its TRANSPORT changes: raw stdout in a terminal, or a
|
|
17
17
|
127.0.0.1 + token-gated localhost page (opened locally, or reached over `ssh -L`) for a surface.
|
|
18
18
|
"""
|
|
19
|
-
|
|
19
|
+
# Read the real installed version from package metadata so `--version` never goes stale again.
|
|
20
|
+
try:
|
|
21
|
+
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
22
|
+
try:
|
|
23
|
+
__version__ = _pkg_version("axiometa-cli")
|
|
24
|
+
except PackageNotFoundError:
|
|
25
|
+
__version__ = "0.0.0+dev"
|
|
26
|
+
except Exception:
|
|
27
|
+
__version__ = "0.0.0+dev"
|
|
@@ -15,6 +15,7 @@ import os
|
|
|
15
15
|
import shutil
|
|
16
16
|
import subprocess
|
|
17
17
|
import sys
|
|
18
|
+
import time
|
|
18
19
|
from dataclasses import dataclass, field
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
|
|
@@ -233,15 +234,37 @@ _NO_MP_MSG = ("the board is not responding to mpremote — it has no MicroPython
|
|
|
233
234
|
"Lay MicroPython down over SWD first: axiometa flash --firmware <micropython.uf2>")
|
|
234
235
|
|
|
235
236
|
|
|
237
|
+
def _reset_via_run(swd: dict | None) -> bool:
|
|
238
|
+
"""Pulse the RP2040 RUN pin (a Pi GPIO) to drop the board into a FRESH REPL before mpremote talks
|
|
239
|
+
to it. The REPL is single-owner: a program from a prior `run` (or a wedged loop) holds the port, so
|
|
240
|
+
the next deploy can't enter raw REPL and surfaces as "not responding to mpremote". A hardware reset
|
|
241
|
+
over RUN clears that every time — it is what a manual power-cycle did by hand. Pi-only (`pinctrl`),
|
|
242
|
+
best-effort and silent where RUN isn't wired or pinctrl is absent (USB boards reset over USB instead,
|
|
243
|
+
so they never reach here needing it)."""
|
|
244
|
+
if not swd:
|
|
245
|
+
return False
|
|
246
|
+
run_gpio = swd.get("run_gpio")
|
|
247
|
+
if not run_gpio or not shutil.which("pinctrl"):
|
|
248
|
+
return False
|
|
249
|
+
if not _run(["pinctrl", "set", str(run_gpio), "op", "dl"], timeout=5).ok:
|
|
250
|
+
return False
|
|
251
|
+
time.sleep(0.3)
|
|
252
|
+
_run(["pinctrl", "set", str(run_gpio), "op", "dh"], timeout=5)
|
|
253
|
+
time.sleep(1.2) # let MicroPython boot through _boot.py to a reachable REPL
|
|
254
|
+
return True
|
|
255
|
+
|
|
256
|
+
|
|
236
257
|
def _mpremote_reachable(target: list) -> bool:
|
|
237
258
|
"""Quick check that a MicroPython REPL is actually there, so a bare board gives a clear message
|
|
238
259
|
instead of a cryptic connect error."""
|
|
239
260
|
return _run(["mpremote", *target, "eval", "1"], timeout=10).ok
|
|
240
261
|
|
|
241
262
|
|
|
242
|
-
def mpremote_run(program: Path, port: str | None, companions: list,
|
|
263
|
+
def mpremote_run(program: Path, port: str | None, companions: list, swd: dict | None = None,
|
|
264
|
+
on_line=None) -> DeployResult:
|
|
243
265
|
"""Ephemeral test: copy to RAM and run live. Gone on reset. The fast generate-run-read-fix loop."""
|
|
244
266
|
target = ["connect", port] if port else []
|
|
267
|
+
_reset_via_run(swd) # fresh REPL first — a prior `run` holds the single-owner port otherwise
|
|
245
268
|
if not _mpremote_reachable(target):
|
|
246
269
|
return DeployResult(False, _NO_MP_MSG, [])
|
|
247
270
|
for cmp in companions:
|
|
@@ -249,9 +272,11 @@ def mpremote_run(program: Path, port: str | None, companions: list, on_line=None
|
|
|
249
272
|
return _run(["mpremote", *target, "run", str(program)], on_line=on_line)
|
|
250
273
|
|
|
251
274
|
|
|
252
|
-
def mpremote_install(program: Path, port: str | None, companions: list,
|
|
275
|
+
def mpremote_install(program: Path, port: str | None, companions: list, swd: dict | None = None,
|
|
276
|
+
on_line=None) -> DeployResult:
|
|
253
277
|
"""Persist: copy companions + main.py to flash, soft-reset so it auto-runs on boot."""
|
|
254
278
|
target = ["connect", port] if port else []
|
|
279
|
+
_reset_via_run(swd) # fresh REPL first (same single-owner reason as mpremote_run)
|
|
255
280
|
if not _mpremote_reachable(target):
|
|
256
281
|
return DeployResult(False, _NO_MP_MSG, [])
|
|
257
282
|
for cmp in companions:
|
|
@@ -385,9 +410,10 @@ def deploy(board: dict, sub: dict, project_dir: Path, verb: str, port: str | Non
|
|
|
385
410
|
return DeployResult(False, f"no {sub['device_program']} in {project_dir} — generate it first.", [])
|
|
386
411
|
if verb in ("run", "install"):
|
|
387
412
|
companions = _companions(board, sub, project_dir)
|
|
413
|
+
swd = (sub.get("host_interface") or {}).get("swd")
|
|
388
414
|
if verb == "run":
|
|
389
|
-
return mpremote_run(program, port, companions, on_line)
|
|
390
|
-
return mpremote_install(program, port, companions, on_line)
|
|
415
|
+
return mpremote_run(program, port, companions, swd, on_line)
|
|
416
|
+
return mpremote_install(program, port, companions, swd, on_line)
|
|
391
417
|
if verb == "deploy":
|
|
392
418
|
if not sub.get("fqbn"):
|
|
393
419
|
return DeployResult(False, "this substrate has no fqbn; use run/install via mpremote.", [])
|
|
@@ -57,20 +57,26 @@ def uf2_to_bin(uf2_path: Path) -> tuple[Path, int]:
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def _interface_cfg(swd: dict) -> str:
|
|
60
|
-
"""An OpenOCD interface config snippet for the SWD wiring (env-overridable).
|
|
60
|
+
"""An OpenOCD interface config snippet for the SWD wiring (env-overridable).
|
|
61
|
+
|
|
62
|
+
RUN (GPIO23) is deliberately NOT given to OpenOCD as `srst`. The RP2040's SW-DP is a multidrop
|
|
63
|
+
port and RUN resets the debug port itself: asserting srst during `program ... reset` drops the DAP
|
|
64
|
+
mid-flash and floods "Too long SWD WAIT" until OpenOCD aborts (cores examine fine, then the reset
|
|
65
|
+
step fails). The program flow uses the Cortex-M software reset (SYSRESETREQ) instead, which leaves
|
|
66
|
+
the DAP alive. RUN stays wired for the runtime mpremote reset (see module docstring) — it is just
|
|
67
|
+
wrong to drive it as srst while flashing this chip.
|
|
68
|
+
"""
|
|
61
69
|
driver = os.environ.get("AXIOMETA_OPENOCD_DRIVER", "bcm2835gpio")
|
|
62
70
|
swclk = swd.get("swclk_gpio", 25)
|
|
63
71
|
swdio = swd.get("swdio_gpio", 24)
|
|
64
|
-
srst = swd.get("run_gpio", 23)
|
|
65
72
|
speed = os.environ.get("AXIOMETA_OPENOCD_SPEED", "1000")
|
|
66
73
|
lines = [
|
|
67
74
|
f"adapter driver {driver}",
|
|
68
75
|
f"adapter gpio swclk {swclk}",
|
|
69
76
|
f"adapter gpio swdio {swdio}",
|
|
77
|
+
"transport select swd",
|
|
78
|
+
f"adapter speed {speed}",
|
|
70
79
|
]
|
|
71
|
-
if srst:
|
|
72
|
-
lines += [f"adapter gpio srst {srst}", "reset_config srst_only srst_nogate"]
|
|
73
|
-
lines += ["transport select swd", f"adapter speed {speed}"]
|
|
74
80
|
return "\n".join(lines) + "\n"
|
|
75
81
|
|
|
76
82
|
|
|
@@ -392,12 +392,18 @@ def run(args) -> None:
|
|
|
392
392
|
if not text:
|
|
393
393
|
continue
|
|
394
394
|
low = text.lower()
|
|
395
|
-
|
|
395
|
+
# Commands work WITH or WITHOUT the leading slash, and tolerate a trailing "?": a lone word like
|
|
396
|
+
# "run" only ever means "deploy it", never a build prompt. `cmd` is the bare single token (empty
|
|
397
|
+
# if the user typed a real sentence), so "run faster" still goes to Axie as a build request.
|
|
398
|
+
cmd = low.lstrip("/").rstrip("? ").strip()
|
|
399
|
+
if " " in cmd:
|
|
400
|
+
cmd = ""
|
|
401
|
+
if cmd in ("exit", "quit"):
|
|
396
402
|
break
|
|
397
|
-
if
|
|
403
|
+
if cmd == "help":
|
|
398
404
|
ui.hint("describe a build in plain words, or: /flash /deploy /run /install /host /monitor /serve /board /exit")
|
|
399
405
|
continue
|
|
400
|
-
if low.startswith("/flash"):
|
|
406
|
+
if cmd == "flash" or low.startswith("/flash"):
|
|
401
407
|
parts = text.split()
|
|
402
408
|
fw = Path(parts[1]).expanduser() if len(parts) > 1 else None
|
|
403
409
|
res = core.flash_firmware(board, sub, fw, on_line=lambda ln: print(ui.c(" " + ln, "grey")))
|
|
@@ -405,20 +411,19 @@ def run(args) -> None:
|
|
|
405
411
|
if not res.ok and fw is None:
|
|
406
412
|
ui.hint("usage: /flash <path-to-micropython.uf2>")
|
|
407
413
|
continue
|
|
408
|
-
if
|
|
409
|
-
|
|
410
|
-
res = core.deploy(board, sub, pdir, verb if verb in core.verbs_for(sub) else verb, port,
|
|
414
|
+
if cmd in ("deploy", "run", "install"):
|
|
415
|
+
res = core.deploy(board, sub, pdir, cmd, port,
|
|
411
416
|
on_line=lambda ln: print(ui.c(" " + ln, "grey")))
|
|
412
417
|
(ui.ok if res.ok else ui.err)(res.detail.splitlines()[0] if res.ok and res.detail else res.detail)
|
|
413
418
|
continue
|
|
414
|
-
if
|
|
419
|
+
if cmd == "host":
|
|
415
420
|
run_host(pdir); continue
|
|
416
|
-
if
|
|
421
|
+
if cmd == "monitor":
|
|
417
422
|
monitor(port); continue
|
|
418
|
-
if
|
|
423
|
+
if cmd == "serve":
|
|
419
424
|
from . import serve as serve_mod
|
|
420
425
|
serve_mod.serve(pdir, port, over_ssh=profile["over_ssh"]); continue
|
|
421
|
-
if
|
|
426
|
+
if cmd == "board":
|
|
422
427
|
board, sub, port = _pick_target(pdir, boards, devices)
|
|
423
428
|
session.board_id = board["id"]
|
|
424
429
|
ui.ok(f"target: {board['name']} · {sub['label']}"); continue
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axiometa-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: Prompt to hardware, in your terminal: detect a board, chat to build, deploy, watch serial.
|
|
5
5
|
Author-email: Axiometa <dr.dumcius@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://axiometa.io
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|