Files
roro9stack/site/content/dev/debug/files-and-screens.md
T
twislaandClaude Sonnet 5.5 3b4100dc6a
CI / build (pull_request) Successful in 8m38s
Site / build (pull_request) Successful in 9s
Site: the developer docs (phase 4), with the Debug Builds and the Debug Console first
/dev/ has Debug Builds and the Debug Console (builds and the token, the
console and its protocol, files and screenshots, driving the UI, crashes and
Safe Mode, the command reference), Build, test and release (including how an
update works), the architecture decisions and the milestone plans.

Generated from the repository by site/tools/gen_dev_docs.py: the ADRs, the
milestones, the README's sections, and the command reference, read from the
firmware's own `help` text. The pages are committed (Zola cannot read outside
its folder); the Site workflow checks they are current, and now also runs
when src/main.cpp changes. M0, M1 and CONTEXT.md are not published.
README: the gnss commands that the table lacked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 21:25:33 +02:00

3.7 KiB

+++ title = "Files, screenshots and the SD card" description = "Copy files to and from the card, take a screenshot, fetch a core dump and restart the device, all over Wi-Fi, with checksums." weight = 3 [extra] tag = "Console" +++

These are the binary commands: a text header line, then raw bytes. The console task answers them itself, so they keep working when the main loop is stuck. scripts/rdbg.py handles each one on the PC side; the protocol is given too, for your own tools.

get: card to PC

scripts/rdbg.py get /gnss/tracks/20261004-142530.gpx          # saved here, under its own name
scripts/rdbg.py get /captures/lora/capture.pcap my-capture.pcap

The device answers get: data <size>, then exactly <size> bytes, then get: end. A path it cannot open (missing, or a folder) gives get: error cannot open <path>. The script prints the size and the speed.

put: PC to card

scripts/rdbg.py put roro9stack-v0.12.0.ota                     # to /updates/roro9stack-v0.12.0.ota
scripts/rdbg.py put notes.txt /notes/from-the-pc.txt           # to a path you choose

The default destination is /updates/<name>, so this is also the way to install an update from the SD card without touching the device: put the .ota file, then scripts/rdbg.py install /updates/<name>.

The device does a few things you want from a file transfer:

  • the PC sends the size and the SHA-256 first (put <path> <size> <sha256>); the device answers put: ready <size> or put: error <why> (no card, not enough space: it wants the size plus 64 KB free, or a path or size it refuses);
  • it writes to a temporary .part file, creating missing folders, and only renames it into place after the whole file has been read back from the card and its SHA-256 matches: the checksum covers what is on the card, not what arrived;
  • a write the card refuses is retried up to 3 times, cutting the file back to the last good byte, and gives up rather than leave a hole in the middle;
  • it ends with put: done <path> <size> B, or put: error <why> and a closed connection (so the rest of the file is never read as commands). A failed transfer leaves nothing on the card.

Speed is about 300 KB/s. The same transfer exists over USB serial, for a device with no Wi-Fi: scripts/sd_put.sh <file> [card path] (about 30 seconds for 1.6 MB, with the card left in).

screenshot: the screen as a PNG

scripts/rdbg.py screenshot ui.png     # 480x270: the 240x135 screen at 2x

The device sends screenshot: rgb332 <width> <height> and then one byte per pixel: the frame the UI composed off-screen, in RGB332 (RRRGGGBB), the way M5GFX stores an 8-bit sprite. The script expands it and doubles it into a PNG.

  • It is read as it stands, while the UI may be drawing, so it can tear. It is for looking at, not for pixel-exact comparison.
  • It is the real thing: the screenshots on this site, in the user guide and the devlog, were taken this way.
  • Its main use is in a loop: send a key, wait a moment, take a screenshot, look. See Drive the UI.

coredump get: the crash dump

The raw contents of the core dump partition: coredump: data <size>, the bytes, coredump: end, or coredump: none. scripts/rdbg.py coredump fetches and decodes it in one go: see Crashes and Safe Mode.

reset: restart now

scripts/rdbg.py reset

The console prints debug: restarting now and restarts the chip after a moment. It does not go through the main loop, so it works when the loop is stuck. (The text command reboot does go through the main loop: a clean restart. boot other restarts into the other app slot: a manual rollback.)