61 Commits
Author SHA1 Message Date
twislaandClaude Opus 5.5 dc4b49755a Merge branch 'm2': GNSS, and the memory back
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.4.0
2026-10-04 23:42:41 +02:00
twislaandClaude Opus 5.5 fff28af961 Rebuild the framework with smaller TLS buffers (ADR 0006)
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
2026-10-04 23:32:29 +02:00
twislaandClaude Opus 5.5 4e439410cd Stop IRC by hand; remove mDNS
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
2026-10-04 23:17:48 +02:00
twislaandClaude Opus 5.5 db265fb757 Trim task stacks and buffers by measurement: +15 KB with IRC up
Peak stack use was measured through each task's worst case (an
ECDSA-checked install over Wi-Fi and from SD, get/put, a core dump
fetch, an IRC TLS handshake); stacks are now peak plus about 2 KB: loop
8 -> 6 KB, update 8 -> 5, storage 10 -> 6, irc 8 -> 6. The Debug Build's
console ring goes 6 -> 4 KB, serial TX 2 -> 1 KB, the GNSS UART buffer
1 KB -> 512 B.

On the device, with IRC on TLS: 46 KB free (was 31), an 18 KB low (was
9.4). The re-run of every worst case left at least 1.6 KB of stack free
in each task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 23:02:35 +02:00
twislaandClaude Opus 5.5 578a39d3c1 M2: record cold-start time to first fix and the heap measurement
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 22:52:07 +02:00
twislaandClaude Opus 5.5 d7952612dd M2 step 6: Tracks, recorded to GPX in the background
`r` in the GNSS App (or `gnss track start|stop`) records a Track to
/gnss/tracks/YYYYMMDD-HHMMSS.gpx: a point every 5 s once moved 5 m
(lib/gnss/track, 7 tests: haversine, the rule, GPX text, the file name).
It keeps recording with the App closed, shows REC in the Status Bar, and
announces start and stop with a Toast. It needs a card and the time;
switching GNSS off stops it. Tracks are written like Captures: past 90 %
card usage, until full. Storage Clean-up gets a GNSS tracks category.

A Track cut short by a reset or power loss has no GPX footer; the GNSS
Service closes such files at the next boot.

Verified on the device: a recorded Track and one interrupted by a reset
both parse as GPX 1.1 on the PC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 22:46:57 +02:00
twislaandClaude Opus 5.5 463ed2dda8 M2 step 5: GNSS App, with the Position and Sky views
Position: the Fix line (or how long it has been searching), latitude and
longitude in decimal degrees or degrees-minutes-seconds (Settings ->
Coordinates), the Maidenhead locator, altitude, speed and course, HDOP
and UTC. Sky: the satellites by azimuth and elevation, coloured by
constellation, filled when used in the Fix, with used/in-view counts.
Tab switches. lib/gnss/geo_format holds the formatting, the locator and
the sky projection, host-tested (6 tests; locators checked against
FN31pr and JN58td).

Verified on the device by a window: 3D Fix from GPS, GLONASS, Galileo
and BeiDou.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 22:38:06 +02:00
twislaandClaude Opus 5.5 d408bada07 M2 step 4: GNSS mark in the Status Bar; the clock follows the Fix
Q61: a muted G while searching (or the receiver is silent), G with a 2D
Fix, G and the satellite count with a 3D Fix; nothing when GNSS is off.
Verified on the device by a window: 3D Fix, 9 of 11 satellites used
(GPS, GLONASS, BeiDou), HDOP 1.3, Status Bar "G9", and "gnss: clock set".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 22:29:20 +02:00
twislaandClaude Opus 5.5 988253ef02 M2 step 3: GNSS Service, with standby and the clock from a Fix
The GNSS Service reads the receiver (UART 15/13, 115200, 1 KB buffer)
from its 50 ms tick and feeds the NMEA parser. With a Fix it sets the
clock (GNSS is the most trusted TimeSource) and refreshes it every 10
min. Settings gets GNSS On/Off and the coordinate format (Q64).

Off puts the receiver in standby with $PCAS12,65535, renewed hourly; On
wakes it with a hot start, $PCAS10,0. Both measured on the device: the
output stops within a second, and a command wakes it within a second.

Commands: gnss status (Fix, satellites per constellation, bytes and
sentences), gnss nmea on|off, gnss send <sentence>, gnss restart. The
probe is gone; never drive GPIO 15 (the receiver's output): the first
probe's swapped-pin attempt silenced it until a power cycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 22:26:53 +02:00
twislaandClaude Opus 5.5 c9b1ecb772 M2 step 2: NMEA parser (host-tested)
lib/gnss: RMC, GGA, GSA and GSV into one GnssState: Fix type, position,
altitude, speed, course, HDOP, UTC time (trusted only with a Fix) and the
satellites in view across constellations. GSV sequences are kept per
constellation and signal band and merged per satellite with the stronger
SNR; GSA's system ID marks which satellites are used. 16 tests, using
lines captured from the device.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 22:05:01 +02:00
twislaandClaude Opus 5.5 f812c62a3d M2 step 1: gnss probe finds the receiver on RX 15 / TX 13 at 115200
A temporary `gnss probe` command listens on both pin orders at 115200
and 9600. Only RX 15 / TX 13 at 115200 carries NMEA, as Meshtastic's
board file says; M5Stack's GPIO 8/9 are the keyboard's I2C bus. The
output format (NMEA 4.10, GSA system IDs, GSV per signal) is recorded in
docs/milestones/M2.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 22:05:01 +02:00
twislaandClaude Opus 5.5 9c30d47982 M2 design: GNSS plan, glossary (GNSS Service, Fix, Track), ADR 0001 note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 22:00:09 +02:00
twislaandClaude Opus 5.5 634a20c339 key command: del and tab
The serial and Debug Console `key` command could type characters but not
erase them, so text typed into a field by mistake couldn't be cleared
remotely. `key del` and `key tab` inject Delete and Tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 21:27:40 +02:00
twislaandClaude Opus 5.5 2c63e9fd6e Merge branch 'ota': Firmware Updates, Debug Builds, Safe Mode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.3.0
2026-10-04 18:14:26 +02:00
twislaandClaude Opus 5.5 388e847cd4 Debug Console: files, screenshots and Update from SD over Wi-Fi
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
2026-10-04 03:18:23 +02:00
twislaandClaude Opus 5.5 14ff13f634 Safe Mode, crash reports, and a watched main loop
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
2026-10-04 03:07:56 +02:00
twislaandClaude Opus 5.5 0fb7f4e9d5 Debug Builds: the console over Wi-Fi (Debug Console, TCP 2323)
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
2026-10-04 02:44:14 +02:00
twislaandClaude Opus 5.5 5b199c2436 sd put: copy a file to the SD card over USB serial
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
2026-10-04 02:31:19 +02:00
twislaandClaude Opus 5.5 8ec4e9e449 ADR 0003: the bootloader does roll back; Arduino had validated the image
Verified on the device: a crashing update pushed over Wi-Fi died once,
and the next boot was the previous firmware, with the "Update ... failed"
Toast. bootGuard() stays as a second line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 02:15:38 +02:00
twislaandClaude Opus 5.5 7afe6b7d17 OTA: keep new images on Probation; Arduino validated them before setup()
Arduino-ESP32's initArduino() marks a PENDING_VERIFY image valid unless
the sketch overrides the weak verifyRollbackLater(). Every update was
therefore VALID before bootGuard() or Probation ever ran (otadata read
back state 0x2 on a crash-looping test build), and nothing rolled back.
The bootloader was never the problem. Override it to return true, so
Probation decides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 02:12:45 +02:00
twislaandClaude Opus 5.5 45542dcbff OTA: the firmware rolls itself back; the bootloader doesn't
A deliberately crashing update looped forever on the device: the
prebuilt bootloader ignores ESP_OTA_IMG_PENDING_VERIFY despite the
app-side rollback config. UpdateService::bootGuard() now runs first in
setup(): it counts starts on Probation in NVS and, on the second
unconfirmed start, marks the image invalid and reboots into the
previous one. Confirming (or the Wi-Fi rollback) resets the counter.
ADR 0003 records the limit: a crash in the first milliseconds still
needs USB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 21:48:39 +02:00
twislaandClaude Opus 5.5 de5931210f OTA: clean refusal report in ota_push.py; shorter signature error
The device refuses at the header and hangs up mid-transfer; the push
client now says so instead of crashing on the reset. The error fits a
Toast (47 characters).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 21:43:03 +02:00
twislaandClaude Opus 5.5 08b59eb203 OTA: answer once the announced image size has arrived
The Arduino network client treats a half-closed connection as closed,
so the device's reply after the sender's EOF was lost. The header
already carries the image size: UpdateParser::complete() lets the
device finish and answer while the connection is open. ota_push.py
half-closes only if no answer comes within 3 s, for older firmware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 21:39:35 +02:00
twislaandClaude Opus 5.5 fa62a07993 README: Firmware Updates over Wi-Fi and from the SD card
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 21:37:29 +02:00
twislaandClaude Opus 5.5 21b3d9e422 OTA steps 3-5: Update Service, Probation and Rollback, Update from SD
- EcdsaVerifier (mbedTLS, embedded public key) and EspOtaSink (writes
  the inactive app slot, esp_ota_end validates the image, then sets
  the boot partition)
- UpdateService: listens on TCP 3232 (and mDNS roro9stack-<id>) while
  Wi-Fi is Connected; streams into UpdateParser; replies OK/ERR to the
  sender; remembers the pending version so a Rollback is reported
  after the reboot
- Probation (host-tested): confirm after the first frame + 30 s + Wi-Fi
  (if configured); roll back if configured Wi-Fi never connects in 3 min
- Main loop: full-screen progress while receiving; restart once
  installed, waiting up to 60 s for Text Entry to end
- Settings > Firmware: version, Probation status, push address and
  name, and the .ota files in /updates on the SD card to install
- StorageService.runJob() runs work on the storage task (SD installs)
- wifi status prints IP and running version; RORO_TEST_CRASH test hook

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 21:35:33 +02:00
twislaandClaude Opus 5.5 90cb6ef9b2 OTA step 2: signing key, Update File builder, push client
- scripts/ota_keygen.sh: ECDSA P-256 key pair; the private key goes to
  ~/.config/roro9stack/ (0600), the public key to keys/ and
  src/platform/ota_public_key.h; .gitignore refuses *key.pem
- scripts/make_ota.py: wraps firmware.bin into a signed .ota (openssl)
- scripts/ota_push.py: sends it over TCP 3232, prints the device's answer
- scripts/flash.sh --ota <host>: build, sign, push

Checked: a generated .ota has the documented layout and its signature
verifies with openssl against the committed public key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 21:31:39 +02:00
twislaandClaude Opus 5.5 326e864264 OTA step 1: Update File format and streaming parser (host-tested)
lib/ota: a 160-byte header (magic, format, image size and SHA-256,
version, ECDSA signature over the first 80 bytes) then the image.
UpdateParser checks the header and signature before writing anything,
hashes the image as it streams into an UpdateSink, and only finishes
the sink when the hash matches. Downgrades are flagged, not refused.
Includes a dependency-free SHA-256 and semver comparison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 21:30:13 +02:00
twislaandClaude Opus 5.5 e9efbcca4d OTA design: glossary, ADR 0003 (own signature check), plan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 21:27:04 +02:00
twislaandClaude Opus 5.5 045c7c3e8f Merge IRC polish and Wi-Fi scan tools
IRC: join after NickServ login, keyed auto-join, SASL->NickServ
fallback, nick regain; Alt scrolls and Up/Down recall sent lines; /j.
Wi-Fi Tools: sort and filter the network list, and scan logging to CSV.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.2.1
2026-10-03 21:04:06 +02:00
twislaandClaude Opus 4.8 28d025fb13 Wi-Fi Tools: scan logging to CSV, plus sort and filter
- 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
2026-10-03 20:52:43 +02:00
twislaandClaude Opus 5.5 5ac6afa557 IRC App: Alt scrolls, Up/Down recall sent lines; /j for /join
- Alt + ; / Alt + . scroll the Buffer back and forward
- Up / Down (Fn + ; / Fn + .) browse the last 30 sent lines, shell-style,
  returning to the draft past the newest (InputHistory, host-tested)
- /j is short for /join

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 20:12:33 +02:00
twislaandClaude Opus 5.5 1f0c1b2e14 IRC: join after NickServ login, keyed auto-join, SASL fallback
- With NickServ, auto-join waits for the logged-in reply (900) or 2 s
  at most, so registered-only IRC channels let us in
- A failed SASL login falls back to NickServ (its own password, or the
  SASL account and password)
- IDENTIFY names the account explicitly, and once logged in on a
  fallback nick, REGAIN takes ours back from a stale session
- Auto-join entries take keys ("#private key, #public"); keys from
  /join are reused when rejoining; keyed channels go first in JOIN
- Serial irc dump: whole Buffers, and which login is configured
  (never the secrets)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 19:48:16 +02:00
twislaandClaude Opus 5.5 83eb791924 Merge M1: Wi-Fi Service, Logs and Clean-up, IRC, Wi-Fi Tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.2.0
2026-10-03 18:42:24 +02:00
twislaandClaude Opus 5.5 e7da9bd388 Close M1: record outcome and carried-over items
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 18:42:24 +02:00
twislaandClaude Opus 5.5 dd16352509 IRC: edit server settings as a draft, applied under the service lock
The IRC App edited the live config while the IRC task could be reading
it to connect. The App now edits a copy; applyConfig() validates it and
swaps it in under the lock, and the task connects from a snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-03 18:42:24 +02:00
twislaandClaude Opus 5.5 4cd6a63498 M1 step 6: Wi-Fi Tools from ordinary scans
- lib/wifi (host-tested): channel occupancy (neighbour spill, signal
  weighting, quietest of 1/6/11) and a signal tracker (history, lost
  detection, click interval)
- WifiService: scan results carry BSSID, channel and security; a scan
  can target one channel for quick tracker refreshes; endListScans()
  turns the radio back off when Wi-Fi is disabled
- Wi-Fi Tools App: networks nearby, channel occupancy bars, signal
  tracker with clicks (m to mute)
- M1 plan: Monitoring-mode views and captures deferred

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 16:33:11 +02:00
twislaandClaude Opus 5.5 076baa77d1 M1 step 5: IRC App
- Chat: header (Buffer n/N, unread elsewhere, topic or connection
  state), wrapped hh:mm <nick> lines (own in accent, Mentions green,
  info grey), input line; Tab cycles Buffers, Fn+Up/Down scrolls back,
  Enter sends, Back leaves while the IRC Service keeps running
- /settings: server form (host, port, TLS, self-signed pinning, nick,
  SASL, NickServ, auto-join); Save & reconnect restarts the session
- Opening the App starts the IRC Service; viewing a Buffer clears its
  unread count; IrcSession gains a revision counter for redraws

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 15:14:59 +02:00
twislaandClaude Opus 5.5 74a341418e M1 step 4b: IRC Service on the device; 8-bit frame buffer
- 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
2026-10-02 15:10:03 +02:00
twislaandClaude Opus 5.5 311eb38723 M1 step 4a: IRC protocol core (host-tested)
lib/irc: IrcMessage parse/serialize, base64, ReconnectPolicy
(5 s .. 5 min), IrcConfig (validated, persisted) and IrcSession:
registration with SASL PLAIN or NickServ, nick fallback, PING, Buffers
capped at 50 lines with unread counts, Mentions, CTCP ACTION/VERSION,
topics, /names on request only, others' join/part/quit hidden, user
commands, and rejoining after a pause. No networking: the session
returns lines to send, Log entries and Notifications.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 15:03:21 +02:00
twislaandClaude Opus 5.5 d3948ccc3a M1 step 3: Log writing and Storage Clean-up
- 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
2026-10-02 14:56:06 +02:00
twislaandClaude Opus 5.5 1a0baf4ac7 M1 step 2: Wi-Fi Service, Saved Networks, NTP, Settings page
- 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
2026-10-02 14:46:07 +02:00
twislaandClaude Opus 5.5 d8fe676c65 Add Wi-Fi/TLS heap probe; limit deep+ LDF to native tests
The deep+ dependency finder broke the framework's WiFi -> Network
include on the device build; only the native test env needs it.
The probe serial command (probe <ssid>TAB<password>) measures heap
with Wi-Fi connected and one TLS connection; credentials never touch
the repo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 14:32:35 +02:00
twislaandClaude Opus 5.5 0c470922bc M1 design: glossary for Wi-Fi/IRC, milestone plan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 14:15:38 +02:00
twislaandClaude Opus 5.5 3b99d9461a Merge M0: skeleton, services, UI kit, Settings and setup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.1.0
2026-10-02 14:02:13 +02:00
twislaandClaude Opus 5.5 cf2e7968b9 Close M0: record outcome and carried-over items
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 14:02:13 +02:00
twislaandClaude Opus 5.5 5fd351c45e Arrow keys work without Fn outside Text Entry
; . , / are arrows on their own unless the foreground App is editing
text (App::textEntryActive); Fn + those keys are arrows everywhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 13:59:49 +02:00
twislaandClaude Opus 5.5 51698a8011 M0 step 7: Settings, About, Storage, first-boot setup
- lib/apps_model (host-tested): SettingsMenu (rows, readable values,
  choices, validation messages) and SetupWizard (names, Region
  confirmation, timezone; saves only when finished)
- AppManager: modal Apps that Home/Back can't leave (setup wizard)
- SettingsApp: all settings plus Storage (usage, erase SD behind a
  dialog) and About (version, node id, battery, memory, uptime) pages,
  replacing the temporary Diagnostics App
- SetupApp: first-boot wizard, opened modally until SetupDone
- Node id and default names derived from the MAC like Meshtastic
- Widget demo is now hidden (About, then w)
- lib_ldf_mode = deep+ so libraries see each other's headers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 13:47:03 +02:00
twislaandClaude Opus 5.5 05c9d9cc56 Add serial dev commands and a serial log helper
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
2026-10-02 13:38:25 +02:00
twislaandClaude Opus 5.5 9218a34c3e Light the screen (dimmed) while a Notification's Toast shows
An Off screen turns on dimmed for the Toast's duration so the user can
see what beeped. It isn't user activity: the timeouts aren't restarted,
and a key pressed meanwhile reaches the App.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 11:35:13 +02:00
twislaandClaude Opus 5.5 15ea53e17f Notifier: queue up to 8 Toasts so a burst of 5 isn't trimmed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-02 11:30:06 +02:00