Skip to content

Add USB serial config mode (no WiFi/AP required) - #436

Open
Variour wants to merge 6 commits into
mainfrom
claude/issue-355-feature-plan-lrjhol
Open

Add USB serial config mode (no WiFi/AP required)#436
Variour wants to merge 6 commits into
mainfrom
claude/issue-355-feature-plan-lrjhol

Conversation

@Variour

@Variour Variour commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • Refactors every REST endpoint in WebServer.h onto a shared, transport-agnostic (ApiRequest, ApiResponse) handler shape (ApiTypes.h), registered once in a dispatch table instead of being written directly against AsyncWebServerRequest.
  • Adds SerialConfigServer: a framed, chunked JSON protocol over the device's existing Serial connection that reaches the same dispatch table — so a device can be fully configured (network, hardware, groups, scenes, sound storage) over USB, without WiFi or the device's own AP. A new endpoint only needs to be registered once to work over both HTTP and serial.
  • Logger's serial sink is suspended for the duration of a serial config session so plain-text log lines can't corrupt the framed protocol.
  • Adds a window.fetch shim to data/index.html, engaged only when the page can't reach a device over HTTP, so every existing fetch() call site keeps working unmodified; the one XHR-based .wav upload branches explicitly for serial mode.
  • Publishes data/index.html to GitHub Pages (new pages-deploy.yml workflow) as the standalone entry point, since the device can't serve its own config page without a network connection.
  • Documents the wire protocol in docs/serial-config.md, with a README section pointing users at the GitHub Pages URL.

Closes #355

Test plan

  • npm run lint (ESLint) passes
  • npm test (mock server test suite, 36/36) passes
  • clang-format --dry-run --Werror passes across src/
  • Verified via Playwright against the mock server that normal HTTP-mode boot, tab switching, and a settings save still work with no console errors and the new connect-overlay stays hidden
  • Verified via Playwright that .wav upload through the Settings > Storage UI still works end-to-end in HTTP mode (the modified uploadStorageFile() correctly falls through to its original XHR path when not in serial mode)
  • Confirmed the REST API surface is byte-for-byte unchanged: identical set of /api/* paths and identical JSON field keys before/after the WebServer.h refactor (mock server server/index.js needs no changes as a result)
  • Not verified in this environment (no physical device or Web-Serial-capable browser available): the actual USB serial protocol end-to-end against real hardware. This is the primary remaining risk and should be tested on real hardware before merge.
  • Manual, one-time repo setup required: GitHub Pages source must be set to "GitHub Actions" in Settings > Pages for pages-deploy.yml to actually publish anything — not something CI can do itself.

Generated by Claude Code

claude added 5 commits July 22, 2026 20:16
…erver

Refactors every REST endpoint in WebServer.h onto a shared (ApiRequest,
ApiResponse) handler shape registered once in a dispatch table, instead of
being written directly against AsyncWebServerRequest. Adds SerialConfigServer,
a framed newline-delimited JSON protocol over the existing Serial connection
that reaches the same dispatch table — so a device can be fully configured
over USB without WiFi or the device's AP (#355), and a new endpoint only
needs to be registered once to work over both HTTP and serial.

Logger's serial sink is suspended for the duration of a serial config
session so plain-text log lines can't corrupt the framed protocol.
Adds a Web Serial-backed window.fetch shim (data/index.html) that speaks
SerialConfigServer's framed protocol, engaged only when the page can't
reach a device over HTTP (see the startup probe) — every existing
fetch()-based call site keeps working unmodified. The one XHR-based upload
path (storage .wav upload) branches on serial mode explicitly, and scene
save streams its body in chunks the same way the firmware side does.
WebSocket push has no serial equivalent, so a lightweight poll substitutes
for peers/groups live updates while connected this way (#355).
Publishes data/index.html to GitHub Pages so it's reachable without any
device serving it — the standalone entry point for USB-serial config
(#355). Requires the repo's Pages source to be set to "GitHub Actions"
in Settings > Pages, a one-time manual step this workflow can't do itself.
Adds docs/serial-config.md (wire protocol, design rationale, constraints),
a README section pointing users at the GitHub Pages entry point, and an
architecture.md update noting SerialConfigServer alongside BatteryWebServer.
@Variour Variour added the enhancement New feature or request label Jul 22, 2026 — with Claude
@github-actions

Copy link
Copy Markdown

Preview

URL: https://batterylight-pr-436.blackpond-ef672c92.germanywestcentral.azurecontainerapps.io

Hosted on Azure Container Apps — accessible from the internet.

… serial

Widens Config.h's deserialize* helpers from JsonVariant to JsonVariantConst
(they were always read-only) — WebServer.h's transport-agnostic handlers
pass ApiRequest::body as JsonVariantConst, which doesn't implicitly convert
to the mutable JsonVariant these previously required, and failed to build
on esp32s3/esp32dev CI.

Also fixes SerialConfigServer::_handleRequest, which built every ApiRequest
without ever copying the incoming frame's "body" field into it — every
non-streaming POST over serial was silently dispatched with an empty body.
Variour added a commit that referenced this pull request Jul 24, 2026
* mesh,web: add central event log + read endpoint for GenericEvent history

Records GenericEvent broadcasts (buzzer presses, resets, ...) in receipt
order in a fixed-size RAM ring buffer, with a read endpoint and live
WebSocket push for a web UI viewer.

#436 (shared ApiRequest/ApiResponse dispatch table for serial reachability)
is still an open, unmerged PR, so GET /api/events is registered directly
against AsyncWebServerRequest for now instead of the shared dispatch table
the issue's design calls for — it should move onto that table once #436
lands.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMsq87s9e7zRUvfLYKxqvk

* mesh,web: make event log display limit configurable, add device-side clear

Adds a persisted eventLogLimit device setting (default 10) capping how
many of the most recent matching entries GET /api/events returns, and a
POST /api/events/clear endpoint that wipes the device's ring buffer and
restarts arrival order at 0, broadcasting to connected WebSocket clients
so other open viewers stay in sync.

Adds matching UI: a "Max displayed" field in the Settings save flow and a
confirm-gated "Clear event log" button.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMsq87s9e7zRUvfLYKxqvk

* web: put event log's max-displayed and filter fields on one row

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMsq87s9e7zRUvfLYKxqvk

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a config mode that does not rely on network connectivity

2 participants