Skip to content

feat(nrf): make init a synchronous handshake - #13

Open
teal-bauer wants to merge 1 commit into
mainfrom
feat/nrf-sync-init-handshake
Open

teal-bauer wants to merge 1 commit into
mainfrom
feat/nrf-sync-init-handshake

Conversation

@teal-bauer

Copy link
Copy Markdown
Member

Summary

InitializeNRF52 was fire-and-forget — it wrote 6 frames at the nRF and returned nil unconditionally, with the version reply arriving asynchronously through the readLoop. A non-responsive nRF would leave the service running degraded forever, with no version in Redis and no fault flag.

Make init wait for the version reply with a 2-second timeout. On timeout, return an error so callers (Bootstrap.startNormally, ReconnectUSock) surface FaultNRFInit or failed:reconnect respectively.

Changes

  • pkg/service/service.go: new versionRxCh chan struct{} (size 1) on Service.
  • pkg/service/nrf_commands.go: InitializeNRF52 drains versionRxCh before sending the version request, then waits on it after all 6 init commands. A reply that arrives during the request burst is picked up from the buffered channel — the wait is typically a no-op on healthy hardware.
  • pkg/service/usock_handlers.go: handleBLEVersionMessage does a non-blocking send on versionRxCh and ClearFault(FaultNRFInit) on every version receipt — a late-arriving version after a handshake-timeout self-heals the fault.

Out of scope

  • Mid-life recovery: nRFs that brick mid-life (reboot into broken app while service is running) aren't caught — there's no handshake to time out on. Per discussion, nRFs don't spontaneously brick mid-life on this hardware.
  • Auto-probe on handshake-timeout: the current change just sets the fault. If FaultNRFInit shows up in production with frequency that warrants automation, we can add a probe + recovery on top later.

Test plan

  • go build ./... clean
  • go vet ./... clean
  • go test ./pkg/service/... passes
  • ARM cross-build via make build
  • Deployed to hardware: handshake completes during init (version arrives in-band during steps 3-6, consumed by the wait at the end). FaultNRFInit clear, firmware-update-status: idle.
  • Verify timeout path in production once a real handshake-failure occurs (or contrived by powering off the nRF — not done in this branch).

InitializeNRF52 was fire-and-forget — it wrote 6 frames at the nRF and
returned nil unconditionally, with the version reply arriving asynchronously
through the readLoop. A non-responsive nRF would leave the service running
degraded forever, with no version in Redis and no fault flag.

Make init wait for the version reply with a 2-second timeout. On timeout,
return an error so callers (Bootstrap.startNormally, ReconnectUSock)
surface FaultNRFInit or failed:reconnect respectively.

- service.go: new versionRxCh channel (size 1) on Service.
- nrf_commands.go: InitializeNRF52 drains versionRxCh before sending the
  version request, then waits on it after all 6 init commands. Picks up a
  reply that arrived during the request burst from the buffered channel,
  so the wait is typically a no-op on healthy hardware.
- usock_handlers.go: handleBLEVersionMessage does a non-blocking send on
  versionRxCh and ClearFault(FaultNRFInit) on every version receipt — a
  late-arriving version after a handshake-timeout self-heals the fault.

Verified on hardware: handshake completes during init (version arrives
in-band during steps 3-6, consumed by the wait at the end). FaultNRFInit
clear, firmware-update-status idle.
@teal-bauer
teal-bauer requested a review from Zanooda May 5, 2026 13:40
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.

1 participant