led-bridge: manage the serial connection lifecycle (reconnect on drop)#16
Merged
Merged
Conversation
Treat the serial port as a connection that can be absent, drop, and return, rather than a permanent handle opened once at construction: - Lazy/ensured connect: openLocked() opens the port on demand and is the single path for both first connect and reconnect (nil port = disconnected). - Drop-on-failure: a failed write closes the handle so the next write reconnects to the live device — fixes the case where the device reset and led-bridge kept writing to a stale handle forever (frozen on the last state). - Reconnect backoff: throttle redials so an absent device isn't dialed every tick (opening the port resets the ESP, which would otherwise reboot-loop). - Build-safe: construction no longer fails if the device is absent; it comes up disconnected, logs it, and connects when the device appears. - Status() reports a `connected` flag alongside the existing write health. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Julie Krasnick (jckras)
requested review from
Nicolas Palpacuer (NickPPC),
Ale Paredes (ale7714) and
Daniel Botros (danielbotros)
June 5, 2026 15:09
Nicolas Palpacuer (NickPPC)
approved these changes
Jun 5, 2026
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.
Summary
Makes
led-bridgeresilient to the serial device coming and going. Previously it opened the serial port once at construction and held that handle forever — so if the ESP ever reset or dropped off USB (a reflash, a USB blip, a loose connection), led-bridge kept writing to a now-dead handle. Every write failed with EIO,last_statefroze at the last successfully-written value, and it stayed stuck untilviam-serverwas restarted. It also failed to build at all if the device wasn't present at startup.This reworks the connection as a managed lifecycle: the port can be absent, drop, and return, and led-bridge rides all of it.
What changed (all in
resources/led-bridge/led-bridge.go)openLocked()opens the port on demand; anilport simply means "disconnected." First-connect, reconnect-after-drop, and absent-at-boot all flow through the same code, so there's no special-case "reopen" branch to keep in sync.reconnectBackoff, 2s) so an absent device isn't dialed every tick. Important because opening the port resets the ESP — unthrottled retries would reboot-loop it.connected: false, logs it, and connects when the device appears (instead of the whole resource going unavailable).Status()now reports aconnectedflag alongside the existing write-health counters.No new goroutine: reconnection rides the existing poll loop, which is correct for a write-driven service (a disconnect only matters at the next write, which is exactly when reconnection triggers).
Notes
main.Test plan
go build ./...andgo vetpassmessages_sentresumes, noviam-serverrestart neededviam-serverwith the ESP unplugged → led-bridge builds, reportsconnected: false, then connects when plugged inStatus()showsconnected: trueandlast_statetracking voice-command during normal operation