refactor(fw-esp32): one home for the serial io_task JSON half - #245
Merged
Conversation
`serial::io_task`'s server-message half — the stack JSON writer, the framed write, and the overflow-warning detail strings — was copied verbatim between fw-esp32c6 and fw-esp32s3, and a third copy is in flight for the classic ESP32. None of it is a chip fact. The one thing that is: each firmware does something small between write chunks. The C6 and S3 tick the RTC watchdog's liveness flag so a slow host cannot starve the feeder; the UART-based v3 drains its 128-byte RX FIFO, which would otherwise overflow during a multi-second write. `ChunkedWriter` takes that as an `FnMut()` hook called before every chunk — a `FnMut` rather than a `fn()` precisely so the second kind, which needs the RX half and the line buffer, fits without a second writer. `WritePolicy` carries the other two divergences (chunk size, and what to call the link in error text) so the v3 migration is an import swap rather than a fork. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deletes ~200 lines that now live in `fw_esp32_common::serial`. What is left in `io_task` is the part that is actually about this chip: the USB-Serial-JTAG peripheral, the connection monitor, the not-draining probe, and the channels. `link_writer` is the seam. It binds the shared writer to `recovery::watchdog::note_io_alive`, which is still called once per chunk, before the chunk, exactly as before — the RWDT is an esp-hal peripheral and fw-esp32-common may not hold chip facts, so the tick goes down as a hook. `ser-write-json` leaves the dependency list with the code that used it. Feature resolution is unchanged: `lpc-wire` already pulls the same version with the same `alloc` feature, so this is a declaration cleanup and not an image change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The same ~200 lines the C6 just dropped, and with them the reason the header carried a "sole divergence from fw-esp32c6's copy" note: `format!` was only ever imported for the server-gated write paths, and those paths now live one crate down. The two `io_task.rs` files still differ, but only in the three places where the boards actually differ — the board module, and the two `fw_harness` gates. `ser-write-json` leaves the dependency list with the code that used it; `lpc-wire` already pulls the same version with the same `alloc` feature, so feature resolution and the image are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The serial
io_taskJSON-serialization half —StackJsonWriter,timed_write_server_msg,server_message_detailand the chunked+timeoutwriter underneath them — existed in three hand-synced copies. The S3 copy
carried a comment explaining which single line diverged from the C6's, so the
two would stay diffable; fw-esp32v3 (PR #239) documents the same duplication
under a "Known duplication" header. The code is chip-agnostic. This gives it
one home in
fw-esp32-commonand migrates the two crates that exist on main.What moved
Two new modules under
fw-esp32-common/src/serial/:chunked_write.rs— not gated onserver, because the outgoing-logpath uses it with the feature off.
WritePolicy(timeout, chunk size, linkname) with a
USB_SERIAL_JTAGconst carrying the old 250 ms / 256 B values,and
ChunkedWriterwithwrite_all/write_all_with— the same loop,byte for byte.
server_msg.rs—#[cfg(feature = "server")].StackJsonWriter+StackJsonError,write_server_msg/write_full_server_msgas an inherentimpl on
ChunkedWriter,server_message_detailand its two helpers.PROBE_TIMEOUT/PROBE_INTERVALstayed behind — they belong to the io loop,which did not move.
UsbConnectionMonitoralso stays: the write path only eversaw it as a
connected: bool.The one seam
timed_write_all_withtickedrecovery::watchdog::note_io_alive()per chunk,so a slow host cannot starve the watchdog feeder into resetting the device.
That tick did not move down: the RWDT is an
esp-halperipheral, so it isa chip fact
fw-esp32-commonis forbidden to hold, and fw-esp32v3 has nowatchdog module at all — moving it would have handed the third consumer an
unusable dependency. It is now a hook the writer calls in exactly the old
position, bound by a small
link_writer()in each crate.The hook is
FnMut, notfn(), because v3's per-chunk work is a UART RX-FIFOdrain that needs captured state. It is returned as an opaque
impl FnMut()rather than a named
fn()pointer for a measured reason, noted in the code:the pointer form cost 256 B of un-inlinable indirect calls.
Size
just fw-esp32c6-size-check: +272 B (+0.01%) — image 2,862,432 → 2,862,704 B,headroom 283,296 → 283,024 B against a 65,536 B margin. The residue is
WritePolicy's fields being struct loads instead of literal constants at twocall sites; one measurement round halved it from 528 B and I stopped there.
Verification
just checktest_espnow --no-default-featuresconfig, which buildsfw-esp32-commonwithserveroff)just fw-esp32c6-size-checkjust build-fw-esp32s3just clippy-fw-esp32c6+-harnessesjust clippy-fw-esp32s3The two
io_task.rsfiles now differ in only the board import, one word ofprose, and the C6's extra harness cfgs.
Follow-up: fw-esp32v3
That crate lives on #239 and is not on main yet, so its migration is a
separate PR. The API was designed against its copy so the move is nearly
mechanical, but three things there are not pure delete-and-import:
UartLink::write_chunkedbecomes aChunkedWriter, which needspoll_rxturned into a free function so the closure can capture
rx/read_bufferdisjointly from
&mut tx.frequency, one chunk of phase. Believed behaviour-neutral since the io loop
drains at the top of the next iteration, but it is a real change.
log::warn!s insidewrite_chunkedlose theirafter {offset} of {n} Bdetail, since the shared writer returns a bareboolby design.Risk
Compile- and size-verified only; neither board was flashed. The write path is
byte-identical by construction, but a smoke run on a C6 and an S3 would be
worth having before merge.
🤖 Generated with Claude Code