Files
roro9stack/site/content/dev/debug/files-and-screens.md
twislaandClaude Opus 5.5 3863d28593
CI / build (pull_request) Successful in 1m48s
Site / build (pull_request) Successful in 9s
Shell: the console's commands on the device's own screen and keyboard (#67)
An App in the Launcher that runs the same commands as USB serial and the
Debug Console, trusted like the first. It shows what the console prints
while it is open, through a second ring of the console's that exists only
meanwhile; Ctrl+b keeps only what follows your own commands. Tab completes
a command's name from the firmware's help text, Fn with up and down recalls
earlier lines, Alt with up and down scrolls back. `rm` asks first in the
Shell, `rm -f` doesn't. Nothing is kept once the App is left.

`screenshot [seconds]` saves the screen as a PNG in /screenshots on the
card, now or after a pause: written a row at a time, indexed colour with
RGB332 as the palette, in one stored deflate block.

487 host tests (11 new: the PNG writer, the Shell's log filter, Tab).
Checked on the device over the Debug Console: commands, Tab, history, a
screenshot fetched and decoded on the PC, rm with and without the question,
a delayed screenshot of another screen. 12 KB of flash; 7 KB of heap while
open. Decisions Q204 to Q212 in docs/milestones/S1.md. Safe Mode: #77.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-07 10:53:31 +02:00

3.9 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.
  • With a number, it saves to the card instead: screenshot 5 (or screenshot 0) writes a PNG to /screenshots on the SD card after that many seconds, as the Shell does. A bare screenshot over the console is the binary one above.
  • 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.)