Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
9.1 KiB
S1 — System basics
Status: the three planned items are done: the SD driver fix in v0.6.1 (issue #21, ADR 0007), fixed IPv4 settings in v0.7.0 (issue #7), the System App in v0.8.0 (issue #11). Still open in the milestone: #39, following the SD driver upstream, and #40, the main loop's CPU use.
Goal: the device works on any network, the card can be trusted, and you can see what the system is doing. A side milestone, like G1.
Fixed IPv4, DNS and NTP (issue #7)
Not every network has a DHCP server: a lab bench, a direct link to a router, a network where addresses are handed out by hand. Until now every Saved Network used DHCP, DNS always came from DHCP, and the NTP server was pool.ntp.org, hard-coded.
IPv4 only. IPv6 isn't part of this, now or as a planned follow-up.
Decisions (design round 2026-10-05)
| # | Decision |
|---|---|
| Q105 | The IP setting is per Saved Network: Automatic (DHCP, as before) or Fixed, with its own address, prefix and gateway. New networks start Automatic. |
| Q106 | The subnet is entered as a prefix length (24), with the mask shown next to it. |
| Q107 | The gateway is optional: left empty, the device talks to its own subnet only. |
| Q108 | DNS is global: two servers in Settings, used on every Fixed network. On Automatic networks DHCP's DNS is used, unless "Always use my DNS" is on. |
| Q109 | DNS defaults: 9.9.9.9 (Quad9), then 1.1.1.1 (Cloudflare). |
| Q110 | NTP is global: two servers in Settings, names or addresses, defaulting to pool.ntp.org and time.cloudflare.com. NTP servers offered by DHCP are used first. GNSS still outranks NTP for the clock. |
| Q111 | What's typed is checked, host-tested in lib/wifi: an address is four numbers from 0 to 255; a prefix is 1 to 30; the address isn't the subnet's network or broadcast address; the gateway is inside the subnet and isn't the device's own address. Refusals say why. |
| Q112 | Addresses are typed in the line editor, limited to digits and dots. |
| Q113 | Enter on a Saved Network opens its page (IP, Address, Prefix, Gateway, Forget) instead of asking to forget it. Settings > Wi-Fi gains DNS servers, "Always use my DNS" and NTP servers. The Status row opens connection details: address, mask, gateway, DNS and NTP in use, and where each came from. |
| Q114 | A change applies at once: the network in use reconnects with the new settings. No automatic way back; the keyboard still works if Wi-Fi is cut. |
| Q115 | Console: wifi status shows address, gateway, DNS, NTP and their sources; wifi ip <ssid> dhcp, wifi ip <ssid> <address>/<prefix> [gateway], wifi dns <a> [b], wifi ntp <a> [b]. Debug Builds: wifi ip … try 60 goes back to the previous setting after 60 s unless confirmed with wifi ip keep. |
| Q116 | Left out: checking whether the address is already taken, and per-network DNS. |
The SDK already allows 3 NTP servers and 3 DNS servers and can take NTP servers from DHCP (CONFIG_LWIP_SNTP_MAX_SERVERS=3, CONFIG_LWIP_DHCP_GET_NTP_SRV=y), so the framework isn't rebuilt for this.
Done when
- A Saved Network set to Fixed joins with that address, mask and gateway, and the device reaches the internet (IRC, Gemini, NTP) through the DNS servers from Settings.
- Set back to Automatic, it gets its address from DHCP again.
- With "Always use my DNS" on, an Automatic network resolves through the servers from Settings.
- The NTP servers from Settings set the clock.
- Wrong entries are refused with a reason, in Settings and on the console.
- Connection details show what's in use and where it came from.
- Tested on
knbg-guestswith 10.39.39.12 (the device's DHCP lease) and 10.39.39.13 (free: the device is alone on that network).
Measured (2026-10-05 and 06, on knbg-guests)
The network is 10.39.39.0/24, gateway 10.39.39.1; DHCP gives 10.39.39.1 as DNS and offers no NTP server.
- Fixed 10.39.39.12/24 (the device's own lease) and Fixed 10.39.39.13/24, gateway 10.39.39.1: the device joins with that address, DNS is 9.9.9.9 and 1.1.1.1 from Settings, and a Gemini page loads (name resolution, routing, TLS). On .13, .12 no longer answers.
- A wrong gateway (10.39.39.254) on a 60 s trial: the device stops answering from another subnet, and comes back by itself with the previous setting.
- Back to Automatic: 10.39.39.12 by DHCP again, DNS 10.39.39.1 from DHCP.
- "Always use my DNS" on an Automatic network: DNS becomes 9.9.9.9 and 1.1.1.1; switched off, the device joins again and has DHCP's DNS back.
- NTP:
pool.ntp.organswers; set totime.cloudflare.comalone, that one answers within 25 s. - Refusals, on the console and in Settings: the network's own address, a gateway outside the subnet, a prefix of 31 or 99, 10.39.39.300, an unknown network, a DNS name where an address is needed, a host name with an underscore.
- In Settings: the network's page pre-fills Fixed with the address, prefix and gateway in use; leaving the page applies it; connection details show each value and where it came from.
- Not tested: NTP servers offered by DHCP (this network offers none), and a Fixed network with no gateway.
Work breakdown
- IPv4 logic (host-tested): parsing and formatting addresses, prefix and mask, the checks of Q111.
- Storage: the IP setting in each Saved Network; DNS, "Always use my DNS" and NTP in Settings.
- Wi-Fi Service: apply it when joining; DNS and NTP;
wifi statusand the console commands. - Settings: the network page, the DNS and NTP rows, connection details.
- Tests on the device, recorded here.
System Monitor (issue #11)
Every milestone so far was driven by measurements, heap floors, stack sizes, TLS dips, that needed a Debug Build and a computer. The System App shows them on the device, in any build.
Decisions (design round 2026-10-06)
| # | Decision |
|---|---|
| Q117 | An App of its own, System, in release builds too. Read-only. |
| Q118 | Four views, switched with Tab: Overview (CPU per core, memory, network, battery), Tasks, Memory, System. |
| Q119 | Sampled once a second. A task's share is its run time over the last second; a core's load is 100 % minus its idle task's share. |
| Q120 | History only while the App is open: two minutes at one sample a second, about 1 KB. The system already keeps what matters afterwards: the lowest free heap since boot and each task's lowest free stack. |
| Q121 | Bytes are counted per service: IRC, Gemini, the Debug Console and Firmware Updates add what they read and write to a shared counter. The network view shows the connection details, each service's bytes in and out, and the signal strength. |
| Q122 | Tasks: name, core, share, state and lowest free stack, sorted by share; s cycles the sort (share, stack, name). Under 512 bytes of stack left shows in the warning colour. |
| Q123 | Memory: free heap, lowest since boot, largest free block, and a two-minute graph of free heap with the floors of Q86 drawn as lines (55, 40 and 20 KB). |
| Q124 | System: uptime and why it last started, firmware and both app slots, chip temperature and CPU frequency, battery voltage and percentage, SD usage and write faults, the radio's and the GNSS receiver's state. |
| Q125 | info and tasks are split into a snapshot that the console and the App share; the arithmetic (shares from two samples, sorting, the stack warning) is host-tested. |
| Q126 | Left out: acting on tasks, an event log, exporting snapshots to the card. |
| Q127 | The main loop uses about 81 % of a core. The App shows it; fixing it is issue #40, not part of #11. |
The App has five views, not four: Q121's network view is one of its own (Overview, Tasks, Memory, Network, System).
Measured (2026-10-06)
- Traffic counters are exact. A Gemini fetch of a 164,970-byte page counts 164,986 bytes in (the page and its 16-byte header line) and 42 out (the 40-character URL and CRLF). A 1,797,760-byte upload counts 1,798,123 in for the Debug Console, commands included.
- The Memory view shows a TLS dip as it happens. Starting IRC and a 165 KB Gemini fetch together: free heap falls from about 100 KB through the three floors to a low of 12.1 KB, then settles near 50 KB. That's the dip accepted in G1 (Q86).
- A run-time counter only moves when its task is switched out. FreeRTOS adds to a task's run time at the context switch. The main loop takes the samples, and with core 1 to itself it's never switched out: its counter said 2 % while the core's idle task had 0 %. So the task that samples gets what's left of its core. With that: the main loop uses 100 % of core 1 at rest (issue #40 said 81 %, an average since boot).
taskson the console sampled twice inside one command at first, a quarter second apart, and showed the loop at 1 %: it was asleep in the command's own wait. It now samples, lets the loop run for a second, and prints.- Low stack, flagged:
IDLE0(232 bytes left),IDLE1(328 to 352) andspk_task(256 to 264), all the framework's own tasks. - Cost: 15.6 KB of flash for the App and the counters (1,742,723 bytes, release). Nothing while it's closed; about 2 KB of history and samples while it's open.