TL;DR
The spa's BWA Wi-Fi module periodically drops off the network for 30–60 seconds at a time, then
rejoins. While it's connected the link is pristine, but during each dropout the Homebridge plugin
had nothing to read, pushed
null
to HomeKit, and Apple Home flashed "No Response." Aria built a small TCP proxy that drops the
edge-garbage and replays the last-good status through the blackouts, so HomeKit stays steady.
Plugin-version-agnostic. ~200 LOC. Shipped as a Docker sidecar on the NAS.
Before / after (per-hour HomeBridge log)
Current Temperature
pushes to HomeKit per hour (= "No Response" flashes)
The first 5 min was a clean-radio window — the proxy's value really shows up the next time the link wobbles, when the replay layer keeps HomeKit steady where before it would have flashed null.
Architecture
The proxy is a thin layer in front of the Balboa BWA Wi-Fi module. The Homebridge
balboa-spa plugin connects to the proxy
on 127.0.0.1:4257; the proxy owns the
single real connection upstream to 192.168.4.254:4257.
What the proxy does
- Holds the single TCP slot upstream. BWA modules accept one connection at a time on port 4257.
- Parses Balboa frames
[0x7E][len][type+data][cksum][0x7E]with CRC-8 SAE-J1850 validation (seed0x02, xor-out0x02— matches the plugin'scompute_checksum). - Drops corrupt frames silently before the plugin's parser sees them. No more bad-checksum logs, no more null pushes.
- Replays the last good status frame at ~1 Hz during upstream gaps so HomeKit always has a current reading.
- Stops replaying after 120 s of upstream silence — HomeKit eventually shows truthful "No Response" instead of a permanent lie.
- Pass-through for downstream control writes (set temp, jets, lights) — plugin sends commands, proxy forwards unchanged.
- Exponential backoff reconnect upstream: 1s → 2s → 4s → 8s → 16s → 30s → 60s steady.
- Per-minute stats: good frames, corrupt drops, replays, reconnects, client count, last-status-age.
Module identity (now decoded)
Root cause (corrected after bake analysis)
First read was "2.4 GHz signal corruption." That was wrong. The module sits on the office eero pod (~50 ft, one clean wall) and when it's connected the link is pristine — 200 frames/min, sub-80 ms freshness, zero corruption for minutes at a stretch. Not a weak-signal situation.
The real failure is the module dropping off the network entirely for 30–60 s at a time
(EHOSTUNREACH — it's unreachable at the link layer), then rejoining and being instantly perfect.
Episodic, not continuous. The handful of garbled frames only appear at the edges of these dropouts.
The spa's own filter pump is ruled out — connectivity went perfect exactly as the pump cycled on.
Two suspects remain, and neither is fixed by a closer pod: (A) the BWA 50350 module's own radio/firmware self-disconnecting (a documented quirk), or (B) eero's invisible band-steering kicking the 2.4-only client off the pod. A longer bake settles it — regular cadence points to eero, random bursts point to the module. Either way the proxy is the software resilience layer that neutralizes both: it drops the edge-garbage and replays the last-good reading through the blackouts, so HomeKit never sees the gap.
Status & next steps
- Shipped + validated. Sidecar container
spa-resilience-proxyon the NAS,--restart=always. Confirmed engaging through real dropouts (e.g. 47 replays in one minute while upstream delivered only 61 frames). - Baking longer to fingerprint the dropout cadence (regular → eero band-steering; random → module radio).
- Durable fix depends on that result — a closer pod won't help. If the module: newer "-06" BWA module or ESP32/RS485 swap. If eero: a dedicated 2.4 GHz IoT SSID or a non-eero AP by the spa that won't steer the client.
- Plan C (module replacement): ESP32 + RS485 inside the spa cabinet → TCP-server firmware, eliminates the BWA radio entirely. ~$15-25 hardware. Full build guide below ↓
Plan C — replace the BWA module with an ESP32 bridge
This is the durable fix. The root cause is the BWA Wi-Fi module's own radio (it self-disconnects) or
eero steering that 2.4-only client off the pod — both live inside a Balboa radio you don't control.
Plan C deletes that radio entirely: a tiny ESP32 wired straight to the spa's RS485 bus runs firmware that
re-presents the exact same TCP server on port 4257.
Our resilience proxy (or the Homebridge balboa-spa plugin)
just points at the ESP32's IP instead of 192.168.4.254 — one
IP change, nothing else. Leave the proxy in front for belt-and-suspenders.
The two Amazon parts
- ESP32 dev board (ESP32-WROOM-32 / DevKitC) — ~$8-12
- TTL-to-RS485 transceiver module, 3.3V/5V, auto-direction (e.g. the "ARCELI TTL to RS485" the project references) — ~$7 a pack
Likely-already-have extras: a few female-female Dupont jumper wires, and ideally a Balboa RS485 Y-splitter (spa-supply part) so you tap the bus without cutting the existing harness.
Cleaner two-piece alternative (solder-free): an M5Stack Atom Lite + Atomic RS485 Base. They stack together with zero wiring between them, run on USB-C, and the same firmware supports them. Only 3 wires to the spa. ~$25 total, the tidiest build. Bonus: a PoE base later puts the whole thing on Ethernet.
⚠️ The one thing that can fry a board — power
The spa's connector carries +12 to 15 VDC on pin 1. That will instantly destroy a 3.3V ESP32. Two safe ways to power it:
- Easiest — don't touch pin 1. Power the ESP32 from a USB phone charger / outlet near the spa. Only the two data wires + ground go to the spa bus.
- Fully internal — add a small LM2596 buck converter to step pin-1's 12-15V down to 5V/3.3V. This is the only reason you'd need a third part.
Wiring to the spa bus
The BWA module plugs into a 4-pin connector on the Balboa main board (Molex 43025-0400 / TE 794617-4). Tap it via an open receptacle on the board or a Y-splitter. Verified pinout (Balboa physical-layer spec, 115200 baud 8-N-1):
| Spa pin | Signal | Connect to |
|---|---|---|
| 1 | +12-15 VDC supply | Leave disconnected (USB power) — or buck-converter input |
| 2 | RS-485 B (Data+) | RS485 module B / A+ |
| 3 | RS-485 A (Data-) | RS485 module A / B- |
| 4 | Ground / return | RS485 module GND + ESP32 GND |
A/B polarity is harmless to get wrong: if pins 2 & 3 are swapped, nothing is damaged — the topside panel
just shows NO COMM. Swap the two data wires and you're done.
The only way to cause damage is wiring the 12-15V pin into the logic board.
ESP32 ↔ RS485 module (skip this if using the M5Stack)
- ESP32 3.3V → RS485 VCC
- ESP32 GND → RS485 GND
- ESP32 GPIO17 (TX) → RS485 DI
- ESP32 GPIO16 (RX) → RS485 RO
- auto-direction module: nothing on DE/RE. Manual module: tie DE+RE together → GPIO4.
Firmware
- Install PlatformIO (VS Code extension or CLI).
- Clone NorthernMan54/esp32_balboa_spa ↗.
- Enable BRIDGE mode (the build flag that turns on the local TCP server) and set your Wi-Fi SSID/password — pin it to your solid 2.4 GHz SSID, which is also where you sidestep eero band-steering.
- Flash over USB. It boots, joins Wi-Fi, reads the RS485 bus, and serves the BWA protocol on tcp/4257.
Cutover (≈5 min, fully reversible)
- Reserve a DHCP IP for the ESP32 in eero.
- Point the resilience proxy's upstream (or the plugin's host) at
<esp32-ip>:4257instead of192.168.4.254:4257. - Unplug the old BWA Wi-Fi module — the flaky radio is now physically out of the system.
- Watch the proxy's per-minute stats: corrupt drops + reconnects should fall to ~0 and stay there.
Bottom line on effort: ~$15-25 in parts, one evening. No soldering if you use a Y-splitter pigtail + a screw-terminal RS485 module (or the M5Stack). The actual work: stack 2 boards (or 4-6 jumpers), 3 wires to the spa, flash from your laptop, change one IP. The only "don't screw this up" step is not wiring 12V to the ESP32. Plan A (IoT SSID) and Plan B (newer BWA module) both still lean on a Balboa radio you can't fully control — Plan C removes that variable for good. It's the "just works, stop tinkering" endgame.
Files & tracking
- Source:
ops/spa-resilience-proxy/on branchclaude/spa-resilience-proxy - Linear: OPS-109
- Session journal:
memory/sessions/aria/2026-06-06-spa-resilience-proxy.md - NAS container:
spa-resilience-proxy(host network, imagespa-resilience-proxy:latest, ~50 MB, node:24-alpine) - HB config backup:
/volume1/docker/homebridge/config.json.bak.spa-proxy-2026-06-06 - Related: NAS Infrastructure