Commit Graph
135 Commits
Author SHA1 Message Date
twislaandClaude Sonnet 5.5 148611ff4c Updates: the Debug Build message fits on one line of the screen
"A Debug Build keeps its console: update it from your PC" ran off the
edge of the release page. It now reads "Debug Build: update from the PC".

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 17:01:04 +02:00
twislaandClaude Sonnet 5.5 325e7755ad R1 plan: the CI timings, measured from the jobs' own start and end times
The first full run took 11.4 minutes, not 17: that was the waiting time.
A pull request takes about 8.7, a release build alone 5.5, a push to main
1.1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 17:01:04 +02:00
twisla df2aaddbc8 Merge pull request 'Updates from Gitea: check, list and install releases from the device (#6)' (#55) from gitea-updates into main
CI / build (push) Successful in 14m4s
Reviewed-on: #55
v0.11.0
2026-10-06 14:29:01 +00:00
twislaandClaude Sonnet 5.5 e2f00e93c2 CI: a branch's pushes run nothing, its pull request runs once
CI / build (pull_request) Successful in 8m43s
A branch with an open pull request ran twice per push: once for the push,
once for the pull request. Pushes to main still run the tests and publish
the badges; every other branch is tested by its pull request.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 16:17:52 +02:00
twislaandClaude Sonnet 5.5 d490a18b9a Updates from Gitea, step 5: the daily check's announcement, the docs, ADR 0009
The announcement was cut at the notification's 48 bytes; it now reads
"v0.11.0 is out: see Settings > Firmware". README: Updates from Gitea
and its limits. ADR 0009: the device trusts the two ISRG roots. R1.md:
what was built and the checks on the device, with what wasn't checked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 16:16:32 +02:00
twislaandClaude Sonnet 5.5 4ef41347ab Updates from Gitea, step 4: the Firmware page shows the project's releases
Latest release (checked on Enter, or c), Older releases (the last ten,
newest first), a release page with the tag's message, and an install
dialog; going back to an older release asks differently. A failed check
someone asked for shows a Toast. A Debug Build shows the latest release
and says it can't install it: it would take its console away.

Tried on the device: a check, the list, the release page, the dialog
(Cancel is the default; Back cancels), and the install from the screen
with its progress screen, then the restart into the release build and its
confirmation.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 16:06:49 +02:00
twislaandClaude Sonnet 5.5 510ce42a99 Updates from Gitea, step 3: install from Gitea, and IRC steps aside for it
A release downloads straight into the inactive slot through the existing
install path: the signature is checked after 160 bytes, before anything
is written, the hash at the end. Tried on the device against the real
server: a download cut short, a flipped byte in the signature and one in
the image are each refused with the running firmware untouched; the real
v0.10.0 installed, restarted, and confirmed itself on Probation.

A TLS connection to Gitea peaks at about 52 KB of heap whether or not the
certificate is verified. With IRC connected (66 KB free) a check left 3 KB
and a download 836 bytes. A check, list or install a person asks for now
makes IRC step aside (holdForUpdate) and come back after: the lowest free
heap during a full download with IRC connected is 38 KB. The daily check
never interrupts IRC; with IRC up it waits. A TLS connection starts with
80 KB free (it was 55).

Debug Builds get test knobs: update probe <host>, update damage cut|flip,
update pretend <version>.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 15:51:10 +02:00
twislaandClaude Sonnet 5.5 5754ae5b57 Updates from Gitea, step 2: the connection (check and list work on the device)
The roots (ISRG X1 and X2), an HTTPS client that reads the answer as a
stream, GiteaReleases (the latest and a list of ten), the Update
Service's requests, install from Gitea through the existing install path,
the daily check's schedule, the Check for updates setting, and console
commands: update check | list | status | install <tag>.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 15:20:58 +02:00
twislaandClaude Sonnet 5.5 b0e8226943 Updates from Gitea, step 1: the model (host-tested)
A streaming JSON scanner (a 33 KB list of releases costs a few hundred
bytes), the release reader built on it, HTTP response heads and chunked
bodies, URLs, and the decisions: which release is an update, whether to
announce it, and which download URLs the device takes. Tested against the
real answers of git.twis.la.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 15:11:58 +02:00
twislaandClaude Sonnet 5.5 6b7e90765f R1 plan: updates from Gitea, the design round (Q162-Q174)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 15:08:33 +02:00
twisla 3120c63b4b Merge pull request 'CI: coverage and badges; tests on a push, builds on a pull request' (#51) from coverage into main
CI / build (push) Successful in 1m7s
Reviewed-on: #51
2026-10-06 12:39:25 +00:00
twislaandClaude Opus 5.5 873af31e21 R1 plan: pull requests, merged by rebase then a merge commit (Q161)
CI / build (push) Successful in 1m6s
CI / build (pull_request) Successful in 8m17s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 14:37:44 +02:00
twislaandClaude Opus 5.5 16345ed6b3 CI: the badges are published from main only
CI / build (push) Successful in 1m5s
CI / build (pull_request) Successful in 8m19s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 14:23:08 +02:00
twislaandClaude Opus 5.5 86ddb87f54 CI: a push runs the tests, a pull request also builds the firmware
CI / build (push) Successful in 1m6s
Rebuilding both firmwares on every push was more than anyone looked at.
A push now runs the host tests with their coverage (under two minutes);
a pull request adds the release firmware and the Debug Build, and is how
changes reach main; a tag still does everything before it releases.
Pull requests from forks don't run. scripts/ci.sh takes 'tests' or
'builds' for one half; coverage.sh now fails when a test fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 14:19:56 +02:00
twislaandClaude Opus 5.5 5ecd5d003f Coverage of lib/ by the host tests, and badges in the README
CI / build (push) Successful in 9m9s
scripts/coverage.sh builds the host tests with coverage counters and
reports with gcovr: 94.6% of the 3,193 lines of lib/ today (lib/SD and
src/ have no host tests and aren't counted). CI runs it on every push,
puts the figure in the job's summary, and on main publishes a coverage
badge and a latest-release badge to the branch 'badges'. The README shows
them next to Gitea's own badge for the workflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 13:56:08 +02:00
twislaandClaude Opus 5.5 edc140e30c Merge branch 'ci': CI on every push, a signed release on every tag
CI / build (push) Successful in 8m10s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 13:51:19 +02:00
twislaandClaude Opus 5.5 6459ca5446 R1 plan: CI and releases are in place
CI / build (push) Successful in 8m8s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 13:51:18 +02:00
twislaandClaude Opus 5.5 a94c14f000 R1 plan: CI as built on the container runner
CI / build (push) Successful in 8m5s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 12:52:43 +02:00
twislaandClaude Opus 5.5 db7e6ccc18 CI: jobs run in a container, PlatformIO directly in it, the toolchains in a volume
CI / build (push) Successful in 8m5s
The runner now gives each job a container. The workflow asks for
python:3.12-slim, installs git, a compiler and PlatformIO, and mounts the
roro9stack-pio volume as the cache; the scripts skip their own docker run
when RORO_NO_DOCKER says they're in the build container already. A tag
from before the framework was rebuilt gets the stock framework libraries
back before it builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 12:39:37 +02:00
twislaandClaude Opus 5.5 ababfaf993 Release build: tags from before Firmware Updates carry no public key to check against
CI / build (push) Failing after 12m51s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 12:25:20 +02:00
twislaandClaude Opus 5.5 6b6bb975f5 R1 plan and ADR 0008: CI signs releases; README section on CI
CI / build (push) Successful in 8m0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 12:19:00 +02:00
twislaandClaude Opus 5.5 1fade6287b CI: tests and builds on every push, a signed release on a tag (#5)
CI / build (push) Successful in 11m26s
The workflow runs on the runner's host and builds in the project's Docker
image through scripts/ci.sh, as on a developer's machine. A tag v*, or a
run by hand for an older tag, builds that tag's sources, signs the Update
File with the key held in the repository's secrets, checks the signature
against the public key in the sources, and publishes a Gitea release with
the .ota, the factory image, the ELF and checksums.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 12:06:57 +02:00
twislaandClaude Opus 5.5 661227cd2d CI: try the runner's label as it is registered
CI / probe (push) Successful in 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 11:58:53 +02:00
twislaandClaude Opus 5.5 c481bb5191 CI: a first workflow, to see what the runner gives a job (#5)
CI / probe (push) Successful in 19s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 11:56:10 +02:00
twislaandClaude Opus 5.5 9f65c75f01 Merge branch 'notes': the Notes App
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.10.0
2026-10-06 10:11:31 +02:00
twislaandClaude Opus 5.5 a35d82c654 F1 plan: Notes ships as v0.10.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 10:11:31 +02:00
twislaandClaude Opus 5.5 e8a654a15f Notes: plain text notes on the SD card, with an editor that saves by itself (#19)
The Notes App lists the files of /notes by their first line, newest first:
n starts a note, Enter opens it, r renames its file, d deletes it after
asking, s sorts by name. A new note's file is named after its first line.

The editor wraps at spaces, 38 columns by 8 rows; Fn+arrows move through
the wrapped text, Ctrl+A and Ctrl+E go to the ends of the line. There is no
save key: the note is written five seconds after the last key, on Back, on
leaving the App, when the screen turns off and before the device powers
off. A save writes a temporary file and puts it in the note's place; a save
cut short is put back, or offered, the next time.

A note is up to 16 KB, held in one buffer reserved when it's opened: the
file is read straight into it and typing never makes it grow. A failed
allocation aborts on this device, and with IRC connected the largest free
block is about 31 KB: a first version that copied the note once on loading
restarted the device when a full note was opened with IRC connected.
Editing files of any size is #47.

The Storage App's text viewer gets `e`, which edits a text file up to 16 KB
with the same editor unless the file is read-only.

439 host tests. Checked on the device: docs/milestones/F1.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 10:08:10 +02:00
twislaandClaude Opus 5.5 c868977f1c A key sent through the Debug Console could turn the screen "off" for a tick
The `key` command stamps the power timer from millis(); the power tick
then compared with its pass's older time, and the unsigned difference read
as 49 days without a key. The screen state went Off for one tick, and the
next key was swallowed as a wake-up: about one remote key in twenty-five.
Keys from the keyboard pass the loop's own time and were never affected.
The same shape as #46. PowerPolicy::update now treats a stamp from the
future as "just now", with a test.

rdbg.py: piped lines written while it was still connecting stayed in
Python's read buffer until the next line arrived (readline() behind
select()). It reads the descriptor directly now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 10:08:10 +02:00
twislaandClaude Opus 5.5 4ab873e9f8 F1 plan: the Notes design round (Q141-Q150)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 09:29:38 +02:00
twislaandClaude Opus 5.5 50fcf6b7a3 Merge branch 'f1': the Storage App
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.9.0
2026-10-06 09:03:50 +02:00
twislaandClaude Opus 5.5 80d68ccfd7 F1 plan: the Storage App ships as v0.9.0; the SNTP panic is #46
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 09:03:49 +02:00
twislaandClaude Opus 5.5 e14eb304b5 Wi-Fi: SNTP could be started twice at a join, and ESP-IDF asserts on that
At every join the DNS and NTP setup ran twice in the same pass: the
"check now and then" timer compared this pass's time with a stamp taken
from millis() a moment later, and the unsigned difference underflowed.
Starting SNTP is only queued for the network task, so when the second run
looked before the first had been carried out, it queued a second start:
"Operating mode must not be set while SNTP client is running", a panic
nine seconds after boot. Rare (once in the dozen or so boots of this
branch's testing), there since v0.7.0. Rollback caught it: the update
was on Probation and the device went back to the build before.

The service now remembers that it started SNTP instead of asking
esp_sntp_enabled(), and the timer compares signed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 08:52:54 +02:00
twislaandClaude Opus 5.5 b7aed8e91c F1 steps 2-6: the Storage App, its viewers, and Maintenance moved in (#3)
The Storage App browses the SD card: folders first with sizes and dates,
three sorts, one item at a time with a clipboard (c, x, v), rename, delete
after counting what's inside, new folder, details. A listing holds 256
entries and says when a folder has more.

FileOps does the card's work for the App and the console alike, one
operation at a time on the storage task in turns of about 150 ms, so Logs
and Captures are still written during a long copy. A copy shows progress,
can be cancelled (what it wrote is taken back) and compares sizes after.
The read-only rules are checked there: the firmware's top-level folders,
/gemini/cache, and files being written (a Track, a Capture, an upload,
today's IRC Logs). A listing reads the folder straight from FatFs: through
the Arduino File, 329 entries took over two seconds.

Viewers by type: text read a screen at a time whatever the file's size
(logs open at the end), a hex dump, a Capture's packets as the LoRa Scanner
lists them, a Track's summary, and an Update File checked as an install
would check it, without writing anything. Tab shows any file as hex or text.

Settings > Storage is gone: usage, Storage Clean-up and Erase are the App's
Maintenance, behind a warning. The Storage Warning points there.

The Clock sets the system time whatever its source, so files are dated
correctly with a GNSS Fix alone (Q137).

Console: cp, mv, mkdir, du, cancel; rm takes folders and follows the rules;
ls shows dates; Debug Builds get `sd fill`.

424 host tests. Checked on the device: docs/milestones/F1.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 08:45:43 +02:00
twislaandClaude Opus 5.5 7ab8f043d0 F1 step 1: names, read-only rules and the packed listing (host-tested)
lib/files: path parts; names checked for rename and new folder; why
something can't be renamed, moved or deleted (the Gemini cache, a file
being written or a folder holding one, the firmware's top-level
folders), and why it can't go into a folder; the viewer for a file by
its name, with a sniff for text. FileList keeps a folder's entries
packed, 256 at most, the first 256 by name whatever order the card lists
them in, and sorts by name, date or size with folders first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 07:37:36 +02:00
twislaandClaude Opus 5.5 63bae576ef F1 plan: the Storage App's design round (Q128-Q140)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 07:34:43 +02:00
twislaandClaude Opus 5.5 77ace09c64 Merge branch 's1': the main loop rests, and GNSS can pause for the radio
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.8.1
2026-10-06 06:42:33 +02:00
twislaandClaude Opus 5.5 c4465675a0 S1 plan: the resting loop and the GNSS pause ship in v0.8.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 06:42:33 +02:00
twislaandClaude Opus 5.5 70fb37ebb5 The radio's noise: the GNSS receiver costs 8 dB; a setting pauses it (#20)
Debug Builds: `lora noise test` changes one thing at a time, Sweeps the
band, and reports the floor under each condition; it runs on the device
by itself, since one condition pauses Wi-Fi (not saved, so a restart
brings it back). Result, at 125 kHz: -117 dBm with the antenna switched
off, -106 with the GNSS receiver in standby, -98 with it running. The
receiver's serial line isn't it (one sentence a second changes nothing),
and neither are the main loop, the CPU frequency, Wi-Fi, the screen or
the radio's own regulator, all within 1 dB.

Settings > "Pause GNSS for LoRa", off by default: the receiver waits in
standby while the radio listens or sweeps, except during a Track, and
has a Fix again about 7 s after. The GNSS App says it's paused.

11 dB remain between the antenna with GNSS quiet and the chip alone,
untouched by anything that can be switched from the firmware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 03:41:15 +02:00
twislaandClaude Opus 5.5 06a593293d The main loop rests between passes (#40)
It made 50,000 passes a second and kept core 1 100 % busy at rest. Keys
are buffered by the keyboard controller, the consoles and the radio have
their own tasks, and no Service ticks more often than every 50 ms, so
the loop now rests 5 ms after a pass with the screen on and 20 ms with
it off; never during a serial file transfer. Safe Mode's loop too.

Screen off: 50 passes a second and core 1 at 1 %; screen on: 167 and
10 %. The chip settles 4 C cooler (34.3 against 38.3). GNSS, Gemini, an
upload, the Sweep and the radio's interrupt all checked at the new pace.
`tasks` shows the loop's passes; Debug Builds: `loop spin on|off`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 03:08:02 +02:00
twislaandClaude Opus 5.5 9078ab9c39 Merge branch 's1': the System App, and traffic counted per service
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.8.0
2026-10-06 01:32:01 +02:00
twislaandClaude Opus 5.5 06aa3fe28b S1 plan: the System App ships in v0.8.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 01:32:01 +02:00
twislaandClaude Opus 5.5 e36aa50922 S1 #11: the System App: tasks, memory, network and system, live
Five views, Tab between them: Overview (each core's load, memory,
traffic, battery, two minutes of load), Tasks (share of a core over the
last second, lowest free stack, flagged under 512 bytes; `s` sorts),
Memory (free heap against the floors of Q86), Network (bytes per
service, and what's moving now), System (what `info` prints, plus
battery, card, radio, GNSS). It samples once a second and keeps history
only while open.

The arithmetic is host-tested, including the trap found on the device: a
task's run-time counter only moves when it's switched out, so the task
that samples (the main loop, alone on its core) gets what's left of its
core. `tasks` now samples across a second of normal running instead of
inside its own wait. The main loop uses 100 % of core 1 at rest (#40).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 01:28:26 +02:00
twislaandClaude Opus 5.5 70bb2a4137 S1 #11: bytes read and written, counted per network service
A Counted<> wrapper around the network clients adds what goes through
their buffer read and write to a per-service counter (IRC, Gemini, Debug
Console, Updates); the single-byte calls and print() end up there, so
each byte counts once. `net` prints the totals. For TLS it's the plain
text the service sees.

Checked on the device: a Gemini fetch counts 164,986 in (a 164,970-byte
page and its 16-byte header) and 42 out (the URL and CRLF).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 01:10:55 +02:00
twislaandClaude Opus 5.5 1df94b684a S1 #11: the System Monitor's design round (Q117-Q127)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 01:04:41 +02:00
twislaandClaude Opus 5.5 00f69e8d53 S1 plan: fixed IPv4 shipped in v0.7.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 00:55:32 +02:00
twislaandClaude Opus 5.5 f834095ee3 Merge branch 's1': fixed IPv4 addresses, DNS and NTP servers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.7.0
2026-10-06 00:48:41 +02:00
twislaandClaude Opus 5.5 acf7697bdc S1 #7 step 4: fixed IPv4, DNS and NTP in Settings
Enter on a Saved Network opens its page instead of asking to forget it:
"IP address" switches between Automatic and Fixed, with an address, a
prefix and an optional gateway. Fixed starts from what the network is
giving the device; the draft is checked and applied on leaving the page,
so a half-typed address is never used. "DNS and NTP" holds the two DNS
servers, "Always use my DNS" and the two NTP servers. Enter on Status
shows the connection's details and where each value came from. Address
fields take digits and dots only; refusals show as Toasts.

Checked on the device through the screens: Fixed 10.39.39.13 applied and
reverted to Automatic, a prefix of 99 refused. Measurements in
docs/milestones/S1.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-06 00:04:53 +02:00
twislaandClaude Opus 5.5 3e279738b6 S1 #7 step 3: the Wi-Fi Service applies IP, DNS and NTP settings
Joining a Saved Network uses its Fixed address, mask and gateway, or
DHCP. DNS comes from Settings on Fixed networks and when "Always use my
DNS" is on; NTP servers come from Settings, after any that DHCP offered.
Both are re-checked every 30 s, since a DHCP renewal puts DHCP's DNS back
and clears the NTP slots it didn't fill. `wifi status` shows what's in
use, where it came from, and which NTP servers answered; `wifi ip`,
`wifi dns`, `wifi ntp`. Debug Builds: `wifi ip ... try <s>` reverts
unless kept.

On knbg-guests (10.39.39.0/24, gateway .1): Fixed .12 and .13 both reach
the internet through 9.9.9.9; a wrong gateway on trial cut the device off
and came back by itself; back to DHCP; both NTP servers answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-05 23:52:22 +02:00
twislaandClaude Opus 5.5 bdd027cb50 S1 #7 steps 1-2: IPv4 checks, the IP setting per Saved Network, DNS and NTP settings
The plan and decisions Q105 to Q116 (docs/milestones/S1.md). lib/net:
strict IPv4 parsing, prefix and mask, and the checks a Fixed setting must
pass, each refusal with its reason. A Saved Network is Automatic or Fixed
(address/prefix and an optional gateway), kept with it in flash. Settings:
two DNS servers (9.9.9.9, 1.1.1.1), "Always use my DNS", two NTP servers
(pool.ntp.org, time.cloudflare.com). Host-tested: 383 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-05 23:39:07 +02:00
twislaandClaude Opus 5.5 7e8882d9cb Merge branch 's1': the SD driver's ready test, and sd card
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
v0.6.1
2026-10-05 23:25:55 +02:00