Skip to content

Serve the unchanging files from one table - #71

Merged
theAndreas merged 1 commit into
masterfrom
web-asset-table
Aug 24, 2026
Merged

Serve the unchanging files from one table#71
theAndreas merged 1 commit into
masterfrom
web-asset-table

Conversation

@theAndreas

Copy link
Copy Markdown
Collaborator

Stage C of lifting the web front end into firmware/, cut deliberately narrow: the asset table, not the dispatch.

What moved

The five files a clock hands out unchanged — the two pages, the manifest, the two icons — had a handler each, in both backends. Ten functions differing only in a blob, a content type, and whether a Content-Encoding header went with it. That is data, so it is WebFrontend::AssetType now, walked by each backend to register one route per entry.

The bytes moved with it, and that part mattered: WebPage.h is generated with internal linkage, so a second includer means a second copy of both pages in flash. It is included by WebFrontend.cpp and nothing else; neither WebInterface.cpp includes it any more.

Where the backends genuinely differ, and how each solves it

How a route carries which entry it answers for:

  • ESP32 — the IDF gives each route a user_ctx, so begin() fills a static httpd_uri_t[] pointing into the table.
  • RP2350ESPAsyncWebServer::on() takes a std::function, so the entry is captured instead.

Both loops are bounded by WebFrontend::MaxAssets, which a static_assert beside the table holds against the real count — adding a file without raising it fails the build rather than silently registering the first five of six.

What I deliberately did not do

Route dispatch and authorisation stay per backend. Their mechanisms genuinely differ (a C table of httpd_uri_t against .on() callbacks; a header decoded by hand against a library comparison), and moving them would have meant restructuring both 500-line web_test.cpp for little in return. The asset table is the part the simulator needs in stage D, and it is here.

The duplicated-blob risk, measured

This is the check the change turns on, since moving a header with internal linkage is exactly how you accidentally ship two copies of a 70 KB page:

before after
ESP32 flash 1 100 421 1 100 233
RP2350 flash 501 332 500 924

Flash went down on both. Nothing is stored twice, and the table costs less than the ten handlers did. The ESP32 gains 120 bytes of RAM for the static route array — five httpd_uri_t.

Verified

  • AVR-Dx byte-identical: 48078 / 1700.
  • All four targets build warning-free; both host suites pass.
  • documented-sizes.py passes on all three targets (the ESP32's RAM moved 15.9 → 16.0 %, inside the point of slack the check allows).
  • The panel renders in a browser: 19 of 111 cells lit, "ES IST VIERTEL VOR EINS", 12:45, brightness read back over the socket.

One honest note on that last check: it does not exercise the new table. serve.js answers the asset routes from disk itself, so what covers handleAsset is web_test.cpp on both backends — which does, and passes. The browser run covers the socket and JSON path.

A second honest note: the first browser run after starting the server came back with an empty plate, and two immediate re-runs were clean. The first page load is slower than the script's 3 s wait; the server log showed the socket connected and a frame delivered throughout. A flake in the check, not in the code.

Test change worth a look

The ESP32's stub had to learn that a route is a handler and a context. With one shared asset handler, capturing the function pointer alone made every asset answer as whichever registered last — the tests would have passed while testing one file five times.

🤖 Generated with Claude Code

The five files a clock hands out as they are - the two pages, the manifest
and the two icons - had a handler each, in both backends. Ten functions that
differed only in a blob, a content type, and whether a Content-Encoding header
went out with it. That is data, so it is a table now: WebFrontend::AssetType,
walked by each backend to register one route per entry.

The bytes moved with it. WebPage.h is generated with internal linkage, so it
is included by WebFrontend.cpp and by nothing else - a second includer would
put a second copy of both pages in flash. Neither WebInterface.cpp includes it
any more.

Where the two servers differ is how a route carries which entry it answers
for: the IDF gives a handler a user context per route, so the ESP32 registers
an array of httpd_uri_t pointing into the table, while ESPAsyncWebServer takes
a std::function and the RP2350 captures the entry instead. Both loops end at
WebFrontend::MaxAssets, which a static_assert in the table's own source holds
against the real count - adding a file without raising it fails the build
rather than registering the first five of six.

The ESP32's host test had to learn that a route is a handler *and* a context:
with one shared asset handler, capturing the function pointer alone made every
asset answer as whichever registered last.

Measured, and the reason to measure was the duplicated-blob risk above: flash
went *down* on both boards - 1 100 421 to 1 100 233 on the ESP32, 501 332 to
500 924 on the Pico - so nothing is stored twice and the table costs less than
the handlers did. The ESP32 gains 120 bytes of RAM for the static route array.
The AVR-Dx image is byte-identical at 48078/1700, all four targets build
warning-free, both host suites pass, and the documented sizes still check out.

The panel was driven in a browser again: 19 of 111 cells lit, "ES IST VIERTEL
VOR EINS", brightness read back over the socket. Worth noting that this does
not exercise the new table - serve.js answers the asset routes from disk
itself - so what covers it is web_test.cpp on both backends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@theAndreas
theAndreas merged commit fc860ca into master Aug 24, 2026
8 checks passed
@AndreasBur
AndreasBur deleted the web-asset-table branch August 25, 2026 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants