Consolidate flash/debug tooling on espresso.py; remove stale scripts#227
Consolidate flash/debug tooling on espresso.py; remove stale scripts#227evnchn wants to merge 24 commits into
Conversation
Addresses zauberzeug#224. - Remove flash.py + its only helper esp.py. espresso.py already covers flash/erase/enable/reset and auto-detects the serial device on Jetson. - Remove backtrace.sh, which hardcoded a long-gone ESP-IDF 4.2 toolchain path. - Repoint upload_ssh.py from 'sudo ./flash.py' to 'sudo ./espresso.py flash' (pass-through args are now espresso.py flash flags, via argparse.REMAINDER). flash.py's positional vocabulary (nano/xavier/orin/nand/v05/usb/enable/reset/-e) cannot be faithfully translated -- espresso.py is Orin-only and flag-driven -- so the wrapper rejects those tokens fast, before rsync, with a migration message: a loud, documented break rather than an opaque remote failure. - Drop the removed scripts from .github/zip_content.txt so the release zip step keeps matching every listed file. - docs/tools.md: replace the backtrace.sh section with the direct addr2line invocation it wrapped (toolchain on PATH, no hardcoded IDF version). - AGENTS.md + .cursor/rules: point the file tree and flash command at espresso.py. core_dumper.py is left standalone; it has no overlap with espresso.py. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @evnchn — the consolidation itself is in good shape: no dangling references to the removed scripts anywhere, the zip manifest stays consistent, and removing backtrace.sh in favor of the documented one-liner is the right call. The feedback below is almost entirely about one spot — the legacy guard in upload_ssh.py — and it leads to a proposal to take this PR one step further (which also answers your two open questions). The legacy guard tries too hard — and that's where all the runtime findings liveThe guard models espresso.py's argparse behavior token by token and gives per-token migration advice. Every runtime issue we found is a consequence of exactly that ambition:
Proposal: finish the consolidation in this PR — fold upload_ssh.py into espresso.pyNone of the above needs a smarter guard; it needs no guard. Since this PR is literally about consolidating the tooling on espresso.py, I'd take it the last step here rather than in a follow-up: give espresso.py a That solves the whole problem class structurally instead of patching it:
Since #221 espresso.py runs on non-Jetson hosts, so it can drive the remote invocation from a dev machine. Happy to help spec the details if you want. On your core_dumper.py question: in this picture, yes — fold it in as Independent of the guard (should also be fixed here)
|
|
Agreed — this is the right call and all seven reproduce (the bare- One boundary I'll preserve unless you meant otherwise: local |
…fail Finish the tooling consolidation (per review of zauberzeug#227) by deleting the legacy-arg guard in upload_ssh.py rather than fixing it: move remote flashing into espresso.py itself, so espresso's real parser validates arguments locally before anything touches the network. This structurally removes the whole class of guard bugs (bare orin -> wrong pins, --dev prefix rejection, --device -e slipping through, the zero-token bypass, --help swallowed by REMAINDER). - Add `--host user@host[:path]`: rsync the build artifacts + espresso.py to the target and re-invoke the command over SSH with the same args minus --host. Works for every command, restoring remote erase/enable/reset/coredump. Only user-typed flags are forwarded, so a local default device can never leak to the remote (the remote resolves its own device/pins/L4T). Remote paths are shell-quoted with the tilde still expanding; custom artifact paths must live under build/ (the rsync scope) and fail fast otherwise. - Fold core_dumper.py into `espresso.py coredump`, reusing espresso's serial-device selection and --chip; esp_coredump is imported lazily so the flash path keeps no dependency on it. - Hard-fail when a Jetson lacks gpiod instead of silently selecting the no-op pin controller and failing later with a generic "Flashing failed". - Refactor parsing/dispatch into main(argv); importing the module now runs no argument parsing and touches no hardware. - upload_ssh.py reduced to a one-release deprecation pointer at the new command. - docs: Orin-only Robot Brain note, --host + coredump usage, ESP-IDF 5.2+ (not 5.x), and the flagged line-length breaks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
TL;DR — draft of the fold is pushed (commit cd99b18); this is the "draft diff" from my last comment. I took the structural route you proposed rather than patching the guard: remote flashing now lives in Two decisions I made that I'd like your eyes on before I call it done:
Ask: does the arg shape + that What's in the diff (+274/−163, 7 files)
Verification — what I actually ran, and what I did NOTConfirmed (ran it):
NOT yet done (honest scope): I don't have a Robot Brain to hand, so the remote rsync+ssh path is verified by inspecting the exact Maker/checker — a second model (Codex) caught 3 real bugs pre-push; all fixed & re-verifiedI ran the diff past a different-lineage reviewer (Codex) as a skeptic before pushing. It found three genuine defects, each now fixed and re-checked:
Its final verdict on the current diff was ship. Nothing here overrode my own judgement — it just caught what I'd talked myself past. |
falkoschindler
left a comment
There was a problem hiding this comment.
Great turnaround @evnchn — this is exactly the structural route we had in mind, and I independently re-verified your claims: the dry-run esptool commands are byte-identical to pre-PR for both chips, import espresso is side-effect-free, the guard-bug class is gone (--device -e now fails locally, --help behaves, no migration table to be wrong), the three Codex fixes hold under adversarial inputs (quoting is consistent across rsync and ssh, build/../build/x.bin is rejected, no shell=True anywhere), and all four doc items are fixed with no new >120-char lines.
Answering your two questions:
- Arg shape: yes,
--hoston every command is exactly right — that's what brings remoteerase/enable/resetback. build/constraint: keep it. Failing fast locally beats a silent file-not-found on the remote, and defaults are unaffected. No need to ship arbitrary paths.
Four things to fix before this is done, ranked:
run_remoteunconditionally rsyncsbuild/, which breaks the advertised recovery flows — espresso.py (line 241)../espresso.py erase --host user@robotfrom a machine without abuild/directory (fresh clone, ops laptop) dies in the first rsync with exit 23 before the erase ever happens — yet erase needs no artifacts at all. Please skip the artifact rsync for commands that don't need artifacts (erase/enable/disable/reset; coredump only needsbuild/lizard.elf) and always sync just espresso.py itself. Related: both rsync sources are cwd-relative, so invoking the script from another directory fails the same way — resolving against the script's own directory would fix that.--dry-runsemantics under the new features. Two spots: (a)--dry-run --hoststill executes both rsyncs for real — a "dry run" overwrites the remote's espresso.py and binaries before showing what would happen (espresso.py, line 430); it should skip the rsyncs or pass--dry-runto rsync too. (b) The new gpiod hard-fail fires before the dry-run check, soflash --dry-runon a Jetson without gpiod now errors where it previously printed the commands (line 450) — a dry run touches no pins, so gate the hard-fail onnot args.dry_run.- Remote
coredumpruns under sudo — espresso.py (line 430).use_sudo=not args.dry_runapplies to all commands, but esp_coredump installed viapip install --useron the Jetson isn't on root's sys.path, so remote coredump raises ModuleNotFoundError even though it works locally on the robot without sudo. Coredump needs dialout, not root — limit sudo to the pin/flash commands. - Small hardening: insert
--before the ssh/rsync target (or reject a leading-dash host) so a pasted host string like-oProxyCommand=...can't become an option injection — one line, low severity for a trusted-operator tool but free to fix. - The branch now conflicts with main, and the resolution has a trap. #229 landed on main (
--baud/--no-stub) inside the region your refactor rewrote, so git can't auto-merge espresso.py — the flags need to be re-applied in the new structure (twoadd_argumentlines plusBAUD/STUB_ARGSin the three esptool call sites). The trap: they must also be added to the hardcoded flag list in the--hostforwarding function, or--baud 115200 --host ...silently flashes the remote at the default baud rate. That list is the same hand-maintained-mirror pattern we just eliminated from the wrapper, and this conflict is its first real drift — consider deriving the forwarded arguments from the parser actions (forward whatever differs from its default) so future flags can never be forgotten. Please also re-run your dry-run comparison after the merge, since baud is now variable.
Two footnotes, no action needed unless you feel like it: rsync ≥ 3.2.4 no longer shell-interprets remote args by default, so for a --host path containing spaces the shlex-quoted rsync destination and the ssh cd target can diverge (the ~/lizard default is unaffected); and the ~user expansion regex only matches [\w-]+ usernames, so ~john.doe/... won't expand. Both are edge cases worth a comment at most.
With 1–3 and the main merge (5) addressed I'm happy to merge. The one remaining gap is the live end-to-end run over SSH onto a real Jetson, which you flagged yourself — we have hardware here, so we'll take that part.
…, sudo, merge zauberzeug#229 Falko's review on zauberzeug#227: - run_remote only rsyncs the artifacts a command needs (flash→*.bin, coredump→*.elf); pin-only commands (erase/enable/disable/reset/release_pins) skip the build/ rsync, so they no longer die with exit 23 from a machine with no build/ (fresh clone, ops laptop). Sources resolve against the script's own directory, so espresso works from any cwd. - --dry-run --host now touches nothing on the remote: the rsync/ssh commands are printed instead of executed, so a dry run can never overwrite the remote's espresso.py/binaries. - The gpiod hard-fail is gated on `not dry_run` — a dry run touches no pins. - Remote coredump no longer runs under sudo (esp_coredump is a `pip install --user` module, needs dialout not root); sudo is limited to the pin/flash commands. - Reject a leading-dash --host so a pasted `-oProxyCommand=...` can't become an ssh/rsync option. - Re-apply zauberzeug#229 (--baud/--no-stub) in the new None-default structure, and DERIVE the forwarded --host args from the parser actions (forward whatever differs from default) so a future flag can never be silently dropped — killing the hand-maintained-mirror class Falko flagged as its first real drift. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…spresso-224 # Conflicts: # espresso.py
… scope Codex maker/checker review caught that _require_build_relative accepted any path under build/, but remote rsync only transfers *.bin (flash) / *.elf (coredump) — so `flash --host X --firmware build/lizard` (no extension) was accepted locally yet silently missing on the remote, the exact "fail fast, don't fail on the remote" guarantee this validation exists to provide. Require the matching extension too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @falkoschindler — all four ranked items plus the #229 merge are in.
One extra from a second-lineage (Codex) review pass: the Verification
The branch now merges cleanly with |
`--host user@robot:` used to yield a literal empty remote path: rsync created a `''` directory in the remote home (or, with rsync < 3.2.4, spilled build/ directly into it) and the SSH step failed to find ./espresso.py — a broken flash after already mutating the remote. parse_host now maps the trailing colon to `~`, matching scp/rsync semantics (colon means the remote home); without a colon the default ~/lizard is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
coredump() passed a hardcoded 115200 to esp_coredump, so an explicitly typed --baud was parsed, even forwarded over --host, and then silently ignored — a regression from the removed core_dumper.py, which honored -b/--baud. The coredump default stays 115200 (esp_coredump's own default; the flash/erase 921600 would be wrong here), the --baud help text now documents both defaults, and the info line prints the effective baud. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
use_sudo was switched off under --dry-run, so the previewed ssh command lacked the sudo a real run would use — anyone copy-pasting the preview would flash without root and hit the exact permission failure the sudo exists to avoid. Dry-run safety already lives entirely in the runner selection (_print_remote vs _run_checked), so the clause had no other effect; the preview now prints the command exactly as it would be executed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deferred esp_coredump import raised a raw ModuleNotFoundError traceback, since the top-level handler only catches RuntimeError and CalledProcessError. This hits remote use in particular: --host only rsyncs espresso.py and the ELF, so a Robot Brain without the pip package died with a bare traceback. The import error is now re-raised as a RuntimeError with an install hint (matching the gpiod handling), and docs/tools.md notes that under --host the package must be installed on the Robot Brain itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The non-Jetson fallback /dev/tty.SLAB_USBtoUART is the macOS name of the CP210x bridge and can never exist on Linux, so every espresso command on a plain Linux dev machine required --device — a regression for the coredump workflow, whose predecessor core_dumper.py defaulted to /dev/ttyUSB0. The fallback is now platform-aware: ttyUSB0 on non-Jetson Linux, the SLAB name on macOS. Nothing can break: the SLAB path never matched anything on Linux anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g dataclass The runtime configuration lived in 16 module globals that main() rebound via a two-line global statement, which forced inert placeholder values at module level and spread every default over three spots that had already drifted apart (module constant, main() fallback, help text). Config's field defaults are now the single source: build_parser interpolates them via the DEFAULT instance, main() constructs the real Config once and passes it to the command functions. Derived values (pin swap, on/off levels, flash frequency, bootloader offset, per-command baud) are properties, so they cannot drift from their inputs, and the raw args namespace no longer leaks into command functions. Behavior is unchanged; the esptool invocations are byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rsync >= 3.2.4 escapes remote args itself (leaving a leading tilde for the login shell to expand), so the shlex quotes from quote_remote_path arrived as literal characters in the remote directory name whenever the path needed quoting: with --host 'user@robot:~/my lizard' the artifacts landed in "$HOME/'my lizard'" while the ssh step cd'ed into the quote-less "$HOME/my lizard" and failed. Hand the raw path to rsync and keep quote_remote_path for the ssh command line only, where the path is embedded in shell syntax and shlex quoting is correct. The default ~/lizard carried no quotes and was unaffected either way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deprecation stub told every former upload_ssh.py user to switch to espresso.py --host, but on Nano/Xavier Robot Brains — the old flow's default target — that replacement cannot work: espresso.py rejects L4T < 35 and its EN/G0 pin names are Orin-specific. The "use release 0.12.x" caveat only lived in docs/tools.md, not in the message these users actually see at the moment of breakage. Add the one-line caveat to the stub itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ctory A local flash resolves build/lizard.bin & co. against the cwd, and so does the --host artifact validation — but run_remote shipped script_dir/build. Invoking /repo/espresso.py flash --host from a different directory with its own fresh build/ therefore silently rsynced the stale /repo/build and flashed old firmware. Resolve build/ against the cwd like a local run would, so the remote flashes exactly what a local flash would use; a foreign cwd without build/ now fails loudly before anything is sent. Only espresso.py itself keeps coming from the script's own directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without -p, rsync keeps the permissions of an existing destination file, so a remote espresso.py that had once lost its exec bit (scp/sftp with a restrictive umask, an editor save) stayed non-executable forever: every --host run updated the content, "./espresso.py" failed with "Permission denied" (sudo does not help), and re-running never healed the state. With -p the script's 755 mode is reapplied on every copy, matching the artifact rsync that already preserves permissions via -a. Verified with rsync 3.4.1 that -p repairs a 644 destination even when the content is unchanged (--chmod=Fu+x, the obvious alternative, does not in that case). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remote coredump deliberately runs without sudo (root would not see a pip --user-installed esp_coredump), so on a Robot Brain whose SSH user is not in the dialout group it dies with PermissionError on /dev/ttyTHS* — while flash --host on the same host works because it is wrapped in sudo. State the prerequisite in the core-dump section next to the esp-coredump note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…istry What a command needs was spread over four places that had to be kept in sync by hand: PIN_COMMANDS (sudo + GPIO setup), ARTIFACT_SUFFIX and REMOTE_ARTIFACT_INCLUDES (the same .bin/.elf knowledge twice, coupled only by comments), the parser's choices list, and an if/elif dispatch chain in main() whose else branch was unreachable. A new subcommand forgotten in one of the tables would run without sudo or silently rsync nothing. Register each command once (handler, uses_pins, artifacts, artifact_suffix); the choices list, the --host artifact validation and rsync include pattern, the sudo/GPIO decisions, and the dispatch are all derived from that entry. Validation now also only covers the artifact flags the command actually transfers, instead of all four regardless of command. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The remote transfer walked the entire ESP-IDF build tree (tens of thousands of files on both ends) just to match --include=*.bin / *.elf, shipped unrelated binaries along the way, and needed the suffix/build-prefix checks in _require_build_relative only to reject paths the glob would silently miss. run_remote now receives the resolved artifact paths of the command (from the COMMANDS registry, with the same defaults the remote applies) and copies exactly those files via rsync -R, which preserves their relative paths below the target directory. Missing artifacts fail locally before anything is sent, custom paths outside build/ now simply work under --host, and the only remaining constraint is what the mechanism truly requires: relative paths without "..". The artifact_suffix registry field and the extension knowledge disappear entirely. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removing flash.py from the release manifest left anyone who unpacks the zip and relied on flash.py or upload_ssh.py with neither the script nor a migration pointer -- only git checkouts still saw the stub. List upload_ssh.py so the stub, which prints the espresso.py --host migration plus the Nano/Xavier "use 0.12.x" caveat and exits 2, reaches exactly the users most likely to be broken by the removal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
falkoschindler
left a comment
There was a problem hiding this comment.
Thanks @evnchn, this is in great shape now. After the first review I ran it through two more Fable review passes (until I ran out of tokens for the week), and the last 15 commits are the result. It turned into a fair-sized refactor of espresso.py, but I'm happy with where it landed.
Summary of what changed since the first review:
Structure
- Importing espresso.py is now side-effect-free: the module-global configuration is replaced by a frozen Config dataclass, and all argument parsing and hardware resolution moved into main().
- The scattered per-command tables and if/elif dispatch are consolidated into a single COMMANDS registry (handler, pin usage, artifacts).
--host remote mode
- Forwards the verbatim argv to the remote instead of reconstructing it from parsed values.
- Passes the rsync destination path unquoted (rsync >= 3.2.4 escapes remote args itself; quoting broke paths with spaces).
- Preserves permissions when copying espresso.py, restoring the exec bit on a pre-existing non-executable remote copy.
- Rsyncs build artifacts resolved against the cwd (matching a local run), not the script directory, and copies exactly the files the command needs (rsync -R) instead of walking the whole build tree by extension glob.
- scp-style trailing colon now maps to the remote home directory.
coredump
- Honors an explicit --baud.
- Fails with a clear, actionable error when esp_coredump is not installed.
Defaults, UX & migration
- Defaults to /dev/ttyUSB0 on non-Jetson Linux hosts (macOS still gets the CP210x name).
- The --host --dry-run preview shows the real sudo prefix.
- The upload_ssh.py deprecation stub points Nano/Xavier users to release 0.12.x and now ships in the release zip, so it actually reaches those users.
- Docs note that the remote coredump needs serial-port (dialout) access for the SSH user.
Since this adds a fair amount of new remote/flash machinery, I'd like a thorough hardware test before we merge. @JensOgorek could you exercise it on real hardware — flash and coredump, and ideally the --host remote path against a Robot Brain? Approving from my side otherwise.
- _pin_config/_flash_mode: wrap yield in try/finally so a failed esptool run or Ctrl-C still resets the ESP out of bootloader and releases pins - turn a missing esptool.py into a clean error instead of a traceback - reject whitespace in the --host remote path (openrsync and rsync < 3.2.4 silently word-split it and copy to the wrong place with exit 0) - reject dash-prefixed artifact paths under --host (rsync argv injection) - cd -- so a dash-prefixed remote path cannot become a cd option - flush prints around subprocess calls so piped/CI output stays ordered Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Posted by Claude Code on evnchn's behalf. TL;DR: Pushed 052f969 — a small hardening commit (+23/−9) on top of the refactor stack, from a hardware-free review pass (dry-runs, argv edge cases, and real rsync/ssh transfers to a LAN Linux box; no ESP32 involved). pre-commit passes locally and in this PR's CI run on the new head. Six fixes, each with evidence in the folds:
One judgement call you may want to overrule: the whitespace rejection (fix 2) also blocks users whose rsync ≥ 3.2.4 handles those paths fine — rejecting felt safer than a warning because openrsync fails silently, but it's a two-line change to soften. Everything else should be uncontroversial. Codex adversarially re-reviewed this fix diff and found no new functional blocker. Fix 1 — failed flash left the ESP in bootloader (repro + after)Both context managers ran their cleanup after a bare Repro (no hardware): a fake Before (f10424d): After (052f969) — the reset sequence now runs before the error exit: Exit code stays 1; behavior on the success path is unchanged (cleanup ran there already). Fix 2 — openrsync silent miscopy: exact commands + observed matrixRan real transfers from macOS 15 to a LAN Ubuntu box (server rsync 3.2.7), both client flavors:
The dangerous half is the last row: the transfer succeeds (exit 0), the artifacts land somewhere else, and the subsequent ssh step Scope: this proves the two macOS client flavors against an rsync 3.2.7 server. The trailing-colon Fixes 3–6 — evidence, plus the full verification listFix 3 (dash artifact): dry-run before the fix showed the injected argv — Fix 4 (missing esptool): Fix 5 ( Fix 6 (flush): observed live — with output piped, the remote-run banners printed after the subprocess output they announce. Verification on the final diff (all on 052f969):
If the whitespace-reject call (or any of these) doesn't match your intent, say the word and I'll adjust — none of them are load-bearing for the rest of the PR. |
…he pip package Verified on real hardware: esp_coredump 1.16.0 unconditionally locates the coredump partition via $IDF_PATH/components/partition_table/parttool.py (even when an offset is passed) and symbolizes with the target GDB, so 'pip install esp-coredump' alone leaves the reading machine unable to dump. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Posted by Claude Code on evnchn's behalf. TL;DR: The hardware pass the PR description still wanted has now been run — on non-Jetson hardware: an ESP32-D0WD (4 MB, CH340) and an ESP32-S3 N16R8 (16 MB, CH343) attached to a LAN Ubuntu box, driven from a Mac via What this pass cannot cover, and stays open for a Robot Brain: Jetson EN/G0 pin control (gpiod v1/v2), THS UART auto-detection on real L4T, piggyboard Test matrix (all via espresso.py at the current head, esptool v5.3.1 on the remote)
Both boards' entire flash was backed up ( The coredump feature, end to end on real siliconProvoked a crash on the S3 via a DSL expression over serial (integer division by zero — a dedicated issue with an MRE follows separately), which panics and saves a dump to the Then, from the Mac: rsyncs
Why the docs commit (c8623f4): what remote coredump really requires — incl. a refuted shortcutOn a machine with only
Refuted shortcut: espresso could pass Environment notes picked up along the way (nothing blocking)
With the earlier hardening commit and this pass, everything testable without a Robot Brain is green from my side; the remaining verification (Jetson pins, piggyboard, L4T device paths) is best done on your hardware. Happy to fold in any renames or doc tweaks you'd prefer first. |
|
Reviewed the diff and ran a hardware pass (with Claude Fable 5 assisting). Test target: a classic ESP32-D0WD-V3 (8MB flash, CP2102N bridge) on a plain Linux dev machine — i.e. the non-Jetson path, complementing the ESP32-S3 test already done for this PR. What I testedFlash: Coredump, end to end: I deliberately crashed Lizard with Dry runs and the stub: Minor findings (non-blocking)
Follow-up proposal: hosted legacy flash toolsRather than only pointing Nano/Xavier users at release 0.12.x (whose zip bundles old binaries), we plan to host the removed scripts on an orphan branch Author's open questions
Side observation (separate issue material)Any integer division by zero in a Lizard script reboots the whole controller (see the crash recipe above). A guard in Still pending before mergeThe real Jetson GPIO path and a real |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
falkoschindler
left a comment
There was a problem hiding this comment.
@evnchn Nothing left from my side after our earlier review rounds — I re-checked the final state on the branch (dry-run flash, remote --host dry-runs, the path/host guards) and everything behaves as described. The remaining notes are cosmetic and don't need fixing. Approving; let's just do the planned hardware pass of flash/coredump/--host on a real Robot Brain before merging.
|
Hardware pass on a real Robot Brain is done — everything works. ✅ I drove Good to merge from my side. 🚀 Test details (setup, per-command results, observations)Setup: rb63 — Jetson Orin, L4T R36.4.3, ESP32-D0WD-V3 on
Observation, not a blocker: the remote commands run under |
|
Posted by Claude Code on evnchn's behalf. TL;DR — LGTM from me as the assignee. I re-read the final diff top to bottom (espresso.py refactor, the Between @falkoschindler and @JensOgorek this has had unusually thorough coverage for a tooling PR:
The only merge action left is the maintainers' click — flagging that it's green from the assignee's chair too. 🚀 What I independently checked on the final diff (grounding the LGTM, not parroting it)
Follow-ups tracked — deliberately NOT in this PRKeeping #227 minimal per @falkoschindler's "no need to grow this further":
Not-a-blocker observation from the Robot Brain pass: remote commands run under |
Consolidates the flash/debug tooling on espresso.py and removes the dead/superseded scripts, per #224. Two Fable review passes on top of the original change turned espresso.py into a fair-sized refactor as well. +464 / −397 across 12 files.
Changes
flash.py+esp.pyespresso.py(flash/erase/enable/reset + Jetson device auto-detect)backtrace.shcore_dumper.pyespresso.pyas thecoredumpsubcommand (reuses espresso's device/chip selection; theesp_coredumpimport stays deferred so the flash path doesn't depend on it)espresso.py--host user@host[:path]remote mode, acoredumpsubcommand, and a substantial refactor (see below)upload_ssh.pyespresso.py --hostmigration to stderr and exits 2 (see below).github/zip_content.txtupload_ssh.pystub, so the releasezip -@keeps matching every listed file and ships the migration pointerdocs/tools.md,docs/getting_started.mdxtensa-esp-elf-addr2line; correctedflash <device_path>→flash --device <device_path>(the bare form is rejected byespresso.py); new sections for--hostand core dumpsAGENTS.md,.cursor/rules/*espresso.pyespresso.py refactor
Beyond the new features, espresso.py was reworked for robustness:
main(). Importing the module no longer probes GPIO or/etc/nv_tegra_release.Configdataclass: the scattered module globals (ON/OFF/CHIP/DEVICE/...) are replaced by one immutable config with derived values as properties, so e.g.flash_freqcannot drift fromchip.COMMANDSregistry: per-command knowledge (handler, pin usage, artifacts) lives in a single table instead of parallel lists plus an if/elif dispatch.--host user@host[:path]remote modeRsyncs
espresso.pyplus exactly the build artifacts the command needs to the target, then re-runs the command there over SSH. Works for every command (flash,erase,enable,disable,reset,coredump). Notable properties:--host/--dry-run), so nothing typed is dropped or reshaped, and locally-resolved defaults don't leak to the remote — which resolves its own device/pins/L4T version.rsync -R, relative paths preserved); pin-only commands copy nothing.espresso.pyitself comes from the script's directory, copied with-pto keep its exec bit.cd(raw for rsync, which escapes remote args itself), tokens areshlex-quoted, and artifact paths must be relative and free of...--host --dry-runprints the exact rsync/ssh commands and mutates nothing remotely.Two open questions from the issue — my call, easy to flip
backtrace.sh: removed; the doc now shows the directaddr2lineit wrapped (the script hardcoded a dead 4.2 path, and the one-liner is trivial + correct for esp32/esp32s3). Happy to instead rebuild it against the v5.3 toolchain if you'd rather keep the script.core_dumper.py: folded intoespresso.pyas thecoredumpsubcommand — it reuses espresso's serial-device and--chipselection and works remotely via--host.upload_ssh.py: a deliberate, documented breakflash.py's positional vocabulary (nano/xavier/orin/nand/v05/usb/enable/reset/-e) is not carried over. A faithful translation isn't possible:espresso.pyis Orin-only (hardcodedPR.04/PAC.06, rejects L4T < R35) and its piggyboard default differs from flash.py's. So instead of mistranslating or failing opaquely on the remote,upload_ssh.pyis now a stub that always prints the migration and exits 2 — it transfers nothing and runs nothing, regardless of arguments. The stub names the replacement (./espresso.py flash --host user@host[:path] ...) and points Nano/Xavier Robot Brains at release 0.12.x, and it now ships in the release zip so users who unpack it (not just git checkouts) see the pointer. Meant to be dropped a release or two after 0.13.0.Verification
espresso.py flash --dry-runemits the correct esptool command for esp32s3.zip -@ < .github/zip_content.txtstep runs clean against the edited manifest.git grepshows zero remaining references to the removed scripts across the tree.pre-commit(mdformat + hooks) passes on all changed files.The CH343-USB-bridge flashing quirk seen during hardware testing is a separate espresso robustness matter tracked in #228. A thorough hardware pass on the refactored code (flash + coredump +
--host) is still wanted before merge.