custom_sdkconfig makes pioarduino regenerate the ESP-IDF libraries:
asymmetric TLS buffers (16 KB in, 4 KB out) and dynamic buffers that
free handshake-only data once connected. The rebuild also follows the
board: no PSRAM, 8 MB flash. The project now carries the partition
table the hybrid build needs (identical to the framework's: the app
slots must not move under updates over the air). Generated files are
ignored.
Debug Build, GNSS on, IRC on TLS: 78 KB free and a 59 KB low (M2 began
at 31 KB and 12.6 KB; the floor is 40 KB). The full stress set passes
on it: refused installs over Wi-Fi and from SD, put/get, screenshot,
core dump decode, Probation; under all of it at once, the low is 46 KB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
IrcService::disconnect() stops the session from any state: QUIT if
connected, then no more retries. /quit goes through it (before, it only
stopped a connected session; while waiting for Wi-Fi or retrying it did
nothing), and so does the new `irc stop` command. Stopped by hand,
opening the IRC App no longer reconnects; typing a line does.
mDNS is gone: it never crossed the dev box's routed network, and it
cost about 7.5 KB of RAM. Pushes go to the IP shown in Settings ->
Firmware, which drops its Name row. OTA Q54 records the change.
On the device, Debug Build: 105.6 KB free with Wi-Fi (was 98); with IRC
on TLS 54 KB free (was 46); after `irc stop`, back to 99 KB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
New commands everywhere: ls, rm and install <path> (Update from SD
without the Firmware page), all as Storage Service jobs. In Debug
Builds the console task answers get and put (one storage job per
transfer, file kept open, TCP flow control: about 300 KB/s, against
55 KB/s over serial) and screenshot (the RGB332 frame the UI composes
into). rdbg.py turns those into files and PNGs.
A failed put closes the connection: the rest of the file had been
parsed as commands. Card writes are retried 3 times after closing,
truncating to the last good byte and reopening, since FATFS keeps a
file in error after one failed write (seen once at 1.3 MB on this card).
onStorage() decides with one compare-and-swap whether the job or the
timeout wins, so an abandoned job can't touch a returned stack frame.
Verified on the device: screenshot; a get round trip byte-identical;
4 puts in a row; a tampered .ota refused by install; a good one put,
installed from SD, confirmed on Probation. The retry path itself has
not fired since it was added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
Every build now records at boot which version runs and, after a crash
restart, which one crashed (even across a Rollback). The core dump
summary (task, PC, reason, backtrace) is printed and raised as a
Notification; `crash` shows it later. After 3 crash restarts in a row
the firmware starts in Safe Mode: clock, Wi-Fi, Update Service and Debug
Console only (SafeMode, 2 host tests). A normal restart or a minute up
resets the count.
The main loop is now on the task watchdog (enableLoopWDT): Arduino only
watched core 0's idle task, so a stuck loop hung the device for good.
The Update Service restarts into an installed update by itself if the
main loop hasn't after 90 s.
Debug Builds: `coredump get` and `reset` are answered by the console's
own task; rdbg.py crash decodes the backtrace and rdbg.py coredump runs
esp-coredump, against ELFs archived by version and digest in .pio/elves.
The StorageService mutex is now made in the constructor: Safe Mode never
starts that Service, and `info` crashed on the null mutex, 29 times in a
row before the fix was pushed into Safe Mode over Wi-Fi.
Verified on the device: crash report and full core dump decoded over
Wi-Fi; Safe Mode at exactly 3 crashes, left by `reboot`; a hung loop
caught by the watchdog in 5 s; `reset` from the console task. ADR 0005.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
cardputer-adv-debug (-DRORO_DEBUG, version +debug) adds a Debug Console:
after a token line, a client gets the last 6 KB of console output, live
lines (ESP-IDF logs included) and the serial commands. The socket task
only queues lines; the main loop runs them. Release builds compile none
of it. The token lives in ~/.config/roro9stack/debug-token, created by
_docker.sh and passed into the container.
All output now goes through `console`, which never waits for USB: a host
that was attached but not reading stalled the main loop up to 2 s per
line. New commands everywhere: info (slots with their versions from NVS,
since the framework stamps its own into each image), tasks, reboot,
boot other, log level, help. scripts/rdbg.py is the client; flash.sh
--debug builds it; CI builds both variants. ADR 0004.
Verified on the device: USB-flashed, then updated over Wi-Fi to a Debug
Build that confirmed on Probation; both slots hold Debug Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
scripts/sd_put.sh <file> [card path] sends a file (by default into
/updates, for Update from SD) without taking the card out. The serial
driver drops bytes once its receive buffer is full, so the transfer is
stop-and-wait: 1 KB chunks, each acknowledged once the Storage Service
has written it, into a 2 KB receive buffer. The device checks the
SHA-256 before renaming <path>.part into place, and gives up after 5 s
of silence or a card job that never returns. FileReceiver holds the
logic, with 12 host tests. About 55 KB/s: 1.6 MB in under 30 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
- lib/wifi (host-tested): scan_log (CSV field quoting, header/row, a
once-per-interval throttle) and network_list_view (sort by signal /
channel / name, filter open-only / hide-hidden / strong-only)
- ScanEntry moved to lib/wifi so both are testable on the PC
- Wi-Fi Tools networks view: s sort, o/h/w filters, l toggles logging
to /wifi/scans/<date>.csv (header + one row per AP per logged scan,
throttled 30 s, needs the clock for timestamps); foreground-only
- Wi-Fi scan logs replace probe-request logs as a clean-up category
- Serial: cat <path>, and key <char> injects a character
Verified on the device: CSV written with header, timestamps, BSSID,
channel, RSSI, security and SSID; hidden networks marked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
- IrcService: networking on its own task, TLS with the built-in CA
bundle (or trust-on-first-use pinning when self-signed is allowed),
plain TCP when TLS is off; connects only while Wi-Fi is Connected,
marks pauses for Monitoring, reconnects with backoff, pings a quiet
server, writes Logs, raises Notifications for Mentions
- Session: forget /quit once disconnected (it was handled every loop);
the server Buffer never counts as unread (MOTD showed as [4])
- Status Bar: unread count
- Frame buffer 16 -> 8-bit colour (M1 Q46): min free heap with IRC on
TLS went from 51 KB to 79 KB
- Serial: irc start / say / dump
Verified on the device against irc.libera.chat:6697: certificate
checked, joined #roro9stack-test, sent a message, quit cleanly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
- lib/storage_model (host-tested): FAT-safe names, daily Log paths,
dates from Log/Capture file names, CleanupPlan by category and age,
byte formatting
- StorageService does all card I/O on its task: queued Log lines are
written in batches each second (dropped while Logs are paused),
plus file listing and deletion jobs
- Settings > Storage moves into StoragePage: usage, Clean up
(category, age with size preview, confirmation), Erase SD card
- Clock: local date for Log names
- Serial: log <text>, sd list
Verified on the device: lines land in /irc/dev/#test/2026-10-02.log
with folders created as needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
- lib/wifi (host-tested): SavedNetworks (up to 8, validated, hidden
flag, persisted) and WifiController (joins the strongest Saved
Network, tries hidden ones in turn, backoff 10/30/60 s, connect
timeout, Monitoring override, radio off without Saved Networks)
- WifiService carries out the controller's actions, sets the EU
country code, and syncs the Clock over SNTP without touching the TZ
- Wi-Fi On/Off setting; Settings > Wi-Fi page: status, add from a scan
or a hidden network, forget
- Status Bar: W + signal bars, W? while searching, MON while monitoring
- Serial: wifi add / wifi status (replaces the step 1 heap probe)
Verified on the device: joins the test network ~5 s after boot, clock
set over NTP, ~129 KB free heap while connected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
The firmware accepts burst, key <name>, sound on|off and short|normal
over serial, so UI behaviour can be reproduced on the device without
the keyboard. scripts/serial_log.sh records (and drives) it in a named
container that flash.sh removes before uploading.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
- pioarduino platform 55.03.312 (Arduino-ESP32 3.3.x / ESP-IDF 5.5),
board m5stack-stamps3, M5Cardputer 1.1.1
- native env with Unity for host-side tests
- scripts/ci.sh and scripts/flash.sh run PlatformIO in a container
- version injected from git describe
- boot stub shows name, version and pressed keys
- GPL-3.0 license
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT