Skip to main content

Balboa Spa (BWA Wi-Fi)

GEM driver for Balboa Water Group BP-series spa packs, reached over the LAN through the BWA Wi-Fi module (Balboa part 50350 or 51159). It speaks the pack's native binary protocol on TCP port 4257 — no cloud account, no vendor app, no polling of a Balboa server.

Balboa is the OEM control system inside a large share of North American hot tubs, so the badge on the cabinet is usually not "Balboa". If the topside panel is a Balboa TP600/TP800/ML-series and there is a BWA module in the equipment bay, this driver applies. Commonly rebadged on Sundance, Marquis, Cal Spas, Artesian, Vita Spa, Coleman, Master Spas and many others.

Feature coverage:

  • Pumps 1–3 (on, off, toggle, set_level with off/low/high), light 1, blower
  • Water setpoint (set_temperature), temperature range (high/low), heat mode (ready/rest), hold mode
  • Temperature scale switching (Fahrenheit/Celsius) and panel clock set
  • Live status decode of water temperature, setpoint, heating state, priming, filtration mode, circulation pump and mister
  • System model string read from the information response

Prerequisites

  • A BWA Wi-Fi module fitted and joined to the site network. The module is a small board in the equipment bay with a short pigtail into the pack's panel port. If the spa is only controllable from the topside panel, there is no module and this driver has nothing to talk to.
  • A DHCP reservation for the module. It advertises no useful hostname, so GEM addresses it by IP and a lease change silently breaks the integration.
  • A BP-series pack (BP501, BP2000, BP6013 and relatives). Older GS-series packs use a different module and a different protocol; they will answer on the port but never produce a valid frame.
  • Exclusive access to the socket. The module serves one TCP client at a time. The Balboa "Spa Control" / "bwa™" mobile app will fight GEM for it, and both ends will reconnect in a loop until one of them stops.
warning

Point-to-point, no authentication. Anything that can reach port 4257 can control the spa — including setting the water temperature. Keep the module on a network segment integrators control, not on a guest VLAN.

Setup steps

  1. Find the module. From the GEM host, confirm the spa answers on 4257:

    nc -vz 192.168.1.60 4257
  2. Reserve the address on the DHCP server so it does not move.

  3. Add the device. Go to Devices, create a device with driver balboa_spa, and set:

    • ip — the module address
    • port — leave at 4257
  4. Confirm it is talking. From the Script Console, run the device's get_status command. A healthy pack returns a decoded status object within a second or two; the pack pushes one about every second with no request from GEM.

  5. Create zones. Run get_zones to see what the pack is actually reporting, then add one zone per item you want on the UI. Zone address is the item name (see below). Delete zones for hardware this spa does not have — a two-pump tub has no pump3.

Attribute reference

Device attributes

AttributeRequiredDefaultPurpose
ipyesLAN address of the BWA Wi-Fi module.
portno4257TCP port. Always 4257 on shipping firmware.
status_timeoutno60000Milliseconds without a status frame before the driver drops the socket and reconnects. The pack normally pushes one per second.
press_attemptsno4How many button presses on/off/set_level may use to reach the requested state. See Toggle-only control below.
press_timeoutno2500Milliseconds to wait for the status frame confirming a press landed, before pressing again.

Attributes the driver writes

On the device: water_temperature, set_temperature, temperature_scale, temperature_range, heat_mode, heating, priming, hold_mode, circulation_pump, spa_time, spa_model.

On each zone, depending on its address: state (on/off), level (pumps and blower), and for the spa zone temperature, setpoint and system_mode.

Values are written only when they change. The pack pushes a full status frame every second; writing all of it blindly would flood the attribute store and its history.

Zone address format

The zone address is the item name — no numbers-and-dots scheme.

AddressItemControllable
spaThe water body: temperature, setpoint, heat modeSetpoint only
pump1, pump2, pump3Jet pumps (off / low / high on 2-speed pumps)Yes
light1Spa lightYes
blowerAir blowerYes
misterMisterNo — reported only
circulationCirculation pumpNo — driven by the pack's filtration logic

Toggle-only control

The Balboa protocol has no "set pump 1 to high" message. It has one message that means "press the pump 1 button", and the pack decides what that does next. Pumps cycle off → low → high → off; the light and blower cycle off → on → off.

So on, off and set_level are emulated: the driver presses the button, waits for the pushed status frame that reflects the result, and presses again if the state is still wrong — up to press_attempts times. Consequences worth knowing:

  • A command can take a couple of seconds, especially off on a two-speed pump running low (two presses).
  • If the pack does not move, the command returns an error rather than lying about success.
  • On a single-speed pump, "high" does not exist. The driver only insists on reaching high once it has actually observed that pump reporting high; before then, any non-zero level satisfies the request. That stops a single-speed pump from cycling forever hunting for a state it does not have.

toggle is the raw single press with no confirmation, for when you want exactly one button event.

Known limitations

  • Pumps 4–6, light 2 and aux outputs are not supported. Their toggle codes are not in the published protocol reference, and pressing an unidentified button code on live equipment is not something this driver will guess at. Packs with that hardware will simply not report or control it.
  • Filter cycles, preferences and the fault log are requestable but not decoded. get_filter_cycles, get_preferences and get_panel send the request and the raw response is logged; the payload layout is not parsed into attributes yet.
  • The mister and circulation pump are read-only — the circulation pump is owned by the pack's filtration schedule, and the mister has no documented toggle code.
  • No fault/error surfacing. The fault log message type is known but not decoded, so a pack in an error state shows only through its status flags (for example heating never engaging).
  • Setpoints are clamped, not rejected. A setpoint above the ceiling of the active temperature range is silently limited by the pack. Switch range first with set_temperature_range if you need the full span.

Troubleshooting

SymptomCheck
Connects, then drops immediately, over and overAnother client holds the socket. Close the Balboa / bwa mobile app; check no other automation controller is connected to the same module.
Connected, but every command returns device did not reach requested stateThe pack is receiving presses and not moving. Confirm the item exists on this spa, and that the topside panel is not in a lockout or hold state.
Temperature reads roughly double or half the panelScale mismatch. Read temperature_scale from get_status; if it disagrees with the panel, set it with set_temperature_scale. In Celsius the wire value is doubled for half-degree resolution.
water_temperature is emptyNormal while the pack is priming or waking from sleep — the sensor reads 0xff (unknown) and the driver leaves the last known value alone rather than writing a null. If it persists, check the topside panel for a pack fault.
No status frames at all after a successful connectVerify this is a BP-series pack with a BWA module. A GS-series pack accepts the connection but never emits a frame that passes CRC.
Commands work from the Script Console but zones never updateThe zone address does not match an item name. Addresses are case-insensitive but must be exactly spa, pump1pump3, light1, blower, mister or circulation.

Protocol notes

Frames are 7E · LEN · TYPE[3] · DATA · CRC8 · 7E, where LEN counts itself, the three type bytes, the payload and the CRC (that is, the whole frame minus the two delimiters). The CRC is CRC-8 with polynomial 0x07, initial value 0x02 and a final XOR of 0x02, computed across LEN through the last data byte.

0x7E is not escaped inside a frame, so a payload byte can legitimately equal the delimiter. The driver therefore locates frames by length and validates them by CRC — scanning for a trailing 0x7E would cut frames in half on real traffic.

Message types used: status update ff af 13 (pushed), toggle item 0a bf 11, set temperature 0a bf 20, set time 0a bf 21, settings request 0a bf 22, set temperature scale 0a bf 27, configuration request 0a bf 04.