A native FHEM Perl module for direct TCP control of Balboa WiFi spa controllers (e.g. BP series, used in many hot tubs and whirlpools). No external dependencies, no middleware required.
- Direct TCP connection to the Balboa WiFi module (port 4257)
- Poll mode — connects briefly every N seconds, then disconnects. Keeps the connection free for the official Balboa BWA app to work in parallel
- Non-blocking polling via FHEM's
BlockingCall— the main FHEM loop is never blocked - Command queue — set commands are sent within the same TCP connection as the next poll cycle, avoiding connection conflicts
- Automatic retry with 15-minute timeout — if a command doesn't take effect (e.g. due to a WiFi dropout), it is retried automatically. After 15 minutes FHEM gives up, so you can still control the spa from the app or the display
- Reads: current temperature, target temperature, pump states, light, heating, heating mode, fault code
- Controls: target temperature, pump 1, pump 2, light
- FHEM installation
- Balboa WiFi module connected to local network (e.g. Balboa BP series)
- Perl modules (standard, included in all common FHEM installations):
IO::Socket::INETIO::SelectTime::HiRes
Copy 76_Balboa.pm into your FHEM module directory:
cp 76_Balboa.pm /opt/fhem/FHEM/Then reload the module in FHEM:
reload 76_Balboa
define <name> Balboa <ip> [<port>]
| Parameter | Description |
|---|---|
<ip> |
IP address of the Balboa WiFi module |
<port> |
TCP port (optional, default: 4257) |
Example:
define Whirlpool Balboa 192.168.178.127
| Command | Values | Description |
|---|---|---|
setTemp |
10–40 | Target temperature in °C |
pump1 |
on, off, high |
Pump 1 (on = low speed) |
pump2 |
on, off, high |
Pump 2 (on = low speed) |
light |
on, off |
Light |
statusRequest |
— | Trigger an immediate poll |
Examples:
set Whirlpool setTemp 38
set Whirlpool pump1 on
set Whirlpool pump2 off
set Whirlpool light on
set Whirlpool statusRequest
Note on pumps: Balboa pumps are toggled, not set directly. The module calculates how many toggle commands are needed to reach the desired state (off → low → high → off). A 2-speed pump cycles through:
off→low→high→off.
| Reading | Values | Description |
|---|---|---|
temp |
10.0–40.0 | Current water temperature (°C) |
setTemp |
10.0–40.0 | Target temperature (°C) |
pump1 |
off / low / high |
Pump 1 state |
pump2 |
off / low / high |
Pump 2 state |
light |
on / off |
Light state |
heating |
on / off |
Heating element active |
heatingMode |
ready / rest / ready_in_rest |
Heating mode |
tempScale |
C / F |
Temperature unit reported by spa |
faultCode |
255 = no fault |
Fault code (255 = OK) |
faultMessage |
Text | Fault description |
rawStatus |
Hex bytes | Raw status payload for debugging |
state |
connected / disconnected / timeout / disabled |
Connection state |
| Attribute | Values | Default | Description |
|---|---|---|---|
interval |
60, 120, 180, 300, 600 | 180 | Poll interval in seconds |
tempOffset |
−5 … +5 | 0 | Temperature calibration offset (°C) |
disable |
0, 1 | 0 | Disable polling |
Example:
attr Whirlpool interval 180
attr Whirlpool tempOffset 0.5
The module does not maintain a persistent TCP connection. Instead, it connects briefly every interval seconds:
Connect → Read status frame → (Send queued commands) → Read updated status → Disconnect
This keeps the connection free so the official Balboa BWA app (in LAN direct mode) can connect at any time.
When you issue a set command, it is placed in an internal queue. The command is then sent to the spa during the next poll cycle — within the same TCP connection as the status read. This prevents connection conflicts between set commands and ongoing polls.
If a poll is already running when you issue a set command, the command waits in the queue. As soon as the current poll finishes, a new poll is triggered within 1 second.
After sending a command, the module compares the expected value with the value reported by the spa in the next status read. If they don't match (e.g. due to a brief WiFi dropout):
- The command is automatically re-queued and retried
- The retry continues until the spa confirms the value
- After 15 minutes, retrying stops automatically — so you can always override from the app, the spa display, or physically
The remaining retry time is logged at verbose level 2:
Balboa Whirlpool: setTemp erwartet=33.0 gelesen=34.0 -> Retry (noch 14 Min)
Balboa Whirlpool: setTemp 33.0 bestaetigt
The Balboa WiFi protocol is a binary TCP protocol:
Frame: 7E [LEN] [TYPE-3-Bytes] [PAYLOAD] [CRC-8] 7E
- Port: 4257
- CRC-8: polynomial 0x07, initial value 0x02, final XOR 0x02, computed over
[LEN, type, payload] - LEN:
len(type) + len(payload) + 2(includes CRC byte and end delimiter) - The spa broadcasts status frames automatically ~once per second after connection
- Temperature stored as
raw = °C × 2(0.5°C precision in Celsius mode)
Protocol reference: ccutrer/balboa_worldwide_app, garbled1/balboa_homeassistant
If you previously used an HTTPMOD device with a PHP proxy:
Old:
define Whirlpool HTTPMOD http://192.168.x.x:85/index.php 240
New:
define Whirlpool Balboa 192.168.178.127
attr Whirlpool interval 180
All reading names are identical (temp, setTemp, pump1, pump2, light, heating, faultCode, faultMessage), so existing stateFormat, DbLog, plots and DOIFs continue to work without changes.
Adjust notify definitions — replace HTTP calls with direct FHEM commands:
# Old:
notify Whirlpool_Heizung:on {
HttpUtils_NonblockingGet({ url => "http://192.168.x.x:86/index.php?setTemp=35" })
}
# New:
notify Whirlpool_Heizung:on { fhem("set Whirlpool setTemp 35") }
notify Whirlpool_Heizung:off { fhem("set Whirlpool setTemp 10") }Important: After migration, faultCode will always read 255 (no fault). If your DOIF or automations check [Whirlpool:faultCode] eq "255" as a precondition for heating, this will now always pass — which is the correct behaviour.
Enable verbose logging in FHEM:
attr Whirlpool verbose 4
At verbose 4, the log shows:
- Poll start / connection details
- Parsed temperature, pump, light, heating values
- Raw status bytes
At verbose 3, the log shows:
- Sent command hex frames (for verifying CRC and frame format)
- Command confirmation or retry messages
The rawStatus reading contains the raw payload bytes from the last status frame, useful for verifying byte positions if readings seem incorrect.
- The module does not yet read the spa's fault log (fault codes from the hardware).
faultCodeis always255. - Only pump1 and pump2 are exposed as set commands. pump3 and blower toggle codes are defined internally but not surfaced.
- The official Balboa app in LAN direct mode and FHEM cannot connect simultaneously. FHEM holds the connection for only ~2–5 seconds per poll, so the app can connect freely in between.
- Balboa BP series controller with WiFi module
- FHEM 6.x on Linux / Raspberry Pi
This module is free software. Use it at your own risk.
Protocol documentation based on: