Files
roro9stack/site/content/dev/build/flash.md
T
twislaandClaude Opus 5.5 c68741cc46
CI / build (pull_request) Successful in 7m20s
Site / build (pull_request) Successful in 9s
One firmware: the Debug Console in every build, off until switched on, with the device's own token
There is no Debug Build any more (ADR 0010, issue #68, Q188 to Q195). The
console and the test commands are compiled into every firmware. It listens
only while Settings > Debug Console is on, which isn't the default; off,
neither its task nor its 4 KB ring exists. The token is made by the device
and shown on that page; a client proves it knows it by answering a challenge
with an HMAC, so it never crosses the network, and five wrong answers close
the console for a minute. DBG in the Status Bar while it listens.

Over USB serial only: debug on, debug token <value>, debug token new.
scripts/flash.sh --debug uses them to set a device up with the developer's
token. scripts/rdbg.py takes the token from -t, $RORO_DEBUG_TOKEN or the
file, answers the challenge, and fetches a release's ELF to decode a crash.

Gone: the cardputer-adv-debug environment, RORO_DEBUG, the +debug version,
scripts/debug_flags.py, update install ... force, and the rule that a Debug
Build doesn't install releases. Old clients and old firmwares don't talk to
each other.

Against the builds it replaces: 30 KB more flash and 88 bytes more static
RAM than the release, 4 KB less RAM than the Debug Build. 468 host tests.
Checked on the device: off by default, login, the pause after wrong tokens,
Safe Mode with the console, the setting surviving an update, debug off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 22:59:35 +02:00

3.9 KiB

+++ title = "Flash and update" description = "Put the firmware on a Cardputer over USB, then update it over Wi-Fi or from the SD card, and from the project's releases." weight = 2

[extra] docs = true source = "README.md" tag = "Flash" +++

Flash

  1. Connect the Cardputer by USB-C.

  2. Run:

    scripts/flash.sh            # auto-detects the port; or: scripts/flash.sh /dev/ttyACM1
    

    This uploads the firmware, then opens the serial monitor. Quit the monitor with Ctrl+C.

If the upload can't connect, put the device in download mode: hold G0 (the button next to the screen) while plugging in USB, or while pressing reset. Then retry.

If you get "permission denied" on the port, your user needs access to the serial device. Run this once, then log out and back in:

sudo usermod -aG dialout "$USER"

Firmware Updates over Wi-Fi (OTA)

Once the Cardputer runs an OTA-capable firmware (flashed once over USB), updates can go over Wi-Fi:

scripts/ota_keygen.sh                  # once: creates the signing key (see ADR 0003)
scripts/flash.sh --ota 10.39.39.12     # build, sign and push; or set RORO_OTA_HOST

The device shows the push address in Settings → Firmware. It installs a correctly signed update right away, restarts (waiting up to 60 s if you're typing), and runs the new firmware on Probation. If the new firmware crashes, or can't reconnect Wi-Fi within 3 minutes, it rolls back to the previous one and says so.

To install from the SD card instead, copy the .ota file from .pio/build/cardputer-adv/ into /updates on the card, then use Settings → Firmware. With the Cardputer on USB, the card can stay in: scripts/sd_put.sh <file.ota> sends it over the serial console into /updates (about 30 s for 1.6 MB, checked with SHA-256 before it's renamed into place; SD_PUT_DEBUG=1 shows the console while it runs).

The private key lives in ~/.config/roro9stack/ota-key.pem and must never be committed. If it's lost, generate a new pair and flash once over USB. (CI signs releases with a copy kept as a repository secret, ADR 0008.)

Updates from Gitea

With no PC and no card, the device can install the project's releases itself (docs/milestones/R1.md). In Settings → Firmware:

  • Latest release checks the server (Enter, or c) and says v0.11.0 (new) or (current). Enter again opens the release: its version, date, size and the tag's message, with Install when it's newer. The download goes straight into the inactive slot, so no card is needed; the signature is checked after the first 160 bytes, before anything is written, and the image's hash at the end. The new firmware then runs on Probation as for any update.
  • Older releases lists the last ten, newest first. Opening an older one offers to go back to it, with a different question.
  • Settings → Check for updates (on by default): once a day, with Wi-Fi up and the clock set, the device looks at the latest release and says v0.11.0 is out: see Settings > Firmware, once per version. It installs nothing by itself, and doesn't announce a version that already failed and rolled back on this device.

The connection is checked against the two ISRG roots Let's Encrypt chains end in (ADR 0009), not the usual bundle of about 130 authorities. Whatever the connection, the Update File's own signature is what decides what gets installed.

IRC steps aside. A secure connection takes about 52 KB of memory at its peak, and IRC's own takes 40 KB of the 107 KB there is. A check or an install you ask for makes IRC disconnect for the few seconds it takes and reconnect afterwards. The daily check never does that: with IRC connected it waits for a moment when IRC isn't, so while IRC stays connected for days it doesn't run, and Latest release is the way to check.

There is no separate Debug Build any more (ADR 0010): every firmware installs releases, and the Debug Console is a setting, which an update leaves as it was.