Home Assistant integration for RS-485 pool equipment — variable-speed pool pumps (Modbus master mode), Hayward / Silverline inverter heat pumps (touch-panel listener mode), and EMEC pH/Redox pool controllers (ERMES-family ASCII protocol, full read + write) — with two hardware approaches to connect them to Home Assistant.
⚠️ The EMEC WDPHRH must be on its own physical RS-485 bus (own USR-TCP232 gateway + own MAX485/MAX13487E), because its 500 ms heartbeat traffic and 38400 baud rate are incompatible with sharing a Modbus RTU bus. See EMEC WDPHRH controller below.
- Supported devices
- Two ways to connect
- InverFlow Eco — Modbus register map
- Hayward pool heat pump
- EMEC WDPHRH controller
- HA integration setup (Option A)
- ESPHome setup (Option B)
- Entities
- Resilience & wake-up mechanism
- Adding new devices
- Troubleshooting
- Related projects
| Device | Manufacturer | Protocol | Baud | Address | Mode | Status |
|---|---|---|---|---|---|---|
| InverFlow Eco | Madimack / Aquagem | Modbus RTU 8N1 | 9600 | 0xAA (170) | Polled (we are master) | ✅ Fully supported |
| Pool heat pump | Hayward / Silverline | Touch-panel RTU 8N1 | 9600 | 0x02 (fixed) | Listener (pump is master) | ✅ Sensors + climate |
| WDPHRH pH/Redox controller | EMEC | ERMES-family ASCII 8N1 | 38400 | 01 (RS-485) | Polled with heartbeat | ✅ Full read + write (setpoints, modes, alarms, reset) |
The InverFlow Eco is an OEM version of the Aquagem InverPro pool pump. All variants sharing the same controller (Madimack, Aquagem, Fairland INVERX, etc.) should be compatible.
The Hayward pool heat pump support targets recent Hayward / Silverline inverter units whose Wi-Fi dongle speaks the Modbus-framed touch-panel protocol. On this bus the pump is the master and the integration emulates the Wi-Fi module (address
0x02), so it runs a persistent listener instead of polling. It exposes a fullclimateentity (on/off, heat/cool/auto, setpoint) plus temperature / current / fan sensors. It is not for the older single-wire PC1000/PC1001 controllers.
Both options give you the same Home Assistant entities and the same local, cloud-free control. The difference is in the hardware between your pump and Home Assistant.
A USR-TCP232-304 (or compatible) converter sits on the RS-485 bus and bridges it to your home network over TCP. The HA integration connects to it directly — no additional hardware required beyond the converter.
Home Assistant
│
│ TCP (LAN)
▼
USR-TCP232-304
192.168.0.x:8899
9600 baud / 8N1
│
│ RS-485
▼
InverFlow Eco
addr 170 (0xAA)
Pros: simple setup, no firmware to maintain, one device for multiple pumps on the same bus. Cons: requires a separate Ethernet/WiFi device on the network.
An ESP32 with a cheap MAX485 TTL module connects directly to the pump RS-485 bus over GPIO. ESPHome handles the Modbus protocol and exposes entities to Home Assistant via the native API.
Home Assistant
│
│ ESPHome native API (WiFi)
▼
ESP32 + MAX485 module
GPIO14=TX GPIO15=RX GPIO23=DE/RE
│
│ RS-485
▼
InverFlow Eco
addr 170 (0xAA)
Pros: ~5€ total hardware cost, no extra device on the network, fully local. Cons: one ESP32 per RS-485 bus segment, requires flashing ESPHome firmware.
The ESP32 wiring below is for the InverFlow pump. For the Hayward heat pump on an ESP32, use the dedicated repo ha-esphome-hayward (auto-direction transceiver,
climateentity included).
| Option A — USR TCP bridge | Option B — ESP32 + MAX485 | |
|---|---|---|
| Hardware cost | ~25-40€ | ~5-10€ |
| Setup complexity | Low — web UI config | Medium — ESPHome flash |
| Multiple devices on same bus | ✅ Yes | |
| Firmware to maintain | ❌ None | ✅ ESPHome OTA |
| HA integration | Custom component (this repo) | ESPHome native API |
| Network dependency | TCP over LAN | WiFi |
| Wake-up mechanism | Automatic (coordinator) | Boot + 5min interval + button |
Confirmed by RS-485 capture session and Tuya/Modbus correlation (2026-05-08).
| Register (hex) | Register (dec) | Key | Description | Notes |
|---|---|---|---|---|
| 0x07D1 | 2001 | error_code |
Error bitmask | 0=no error — also the wake-up register |
| 0x07D2 | 2002 | op_condition |
Operation condition bitmask | bit0=1 → running |
| 0x07D3 | 2003 | speed_pct |
Running capacity % | Actual speed, not setpoint |
| 0x07D4 | 2004 | power_w |
Instant power W | Confirmed vs Tuya DPS 5 |
| 0x07D7 | 2007 | const_2007 |
Firmware constant (~88) | Fixed value, meaning unknown |
| 0x07D8 | 2008 | const_2008 |
Firmware constant (~20) | Fixed value, meaning unknown |
| 0x07D9 | 2009 | const_2009 |
Firmware constant (~28) | Fixed value, meaning unknown |
| Register (hex) | Register (dec) | Description | Range |
|---|---|---|---|
| 0x0BB9 | 3001 | Speed setpoint % | 0=stop, 30–100=run |
Speed is snapped to the nearest 5% and clamped to 30% minimum when running.
| Bit | Error |
|---|---|
| 0 | DC voltage abnormal |
| 1 | AC current sampling circuit failure |
| 2 | Phase-deficient protection |
| 3 | Master drive error |
| 4 | Heatsink sensor error |
| 5 | Heatsink overheat |
| 6 | Output current exceeds limit |
| 7 | Input voltage abnormal |
| 8 | No water protection |
| 9 | Panel↔master comm failure |
| 10 | Panel EEPROM read error |
| 11 | RTC read error |
| 12 | Main EEPROM read error |
| 13 | Motor current detection error |
| 14 | Motor power overload |
| 15 | PFC protection |
The InverFlow Eco has no dedicated ON/OFF Modbus register:
turn_off→ write 0 to register 0x0BB9turn_on→ write last known speed (default 80%) to register 0x0BB9
Running state is determined by reading op_condition bit 0.
After a power cut or extended RS-485 silence, the pump stops responding. A single FC=03 read on register 0x07D1 re-activates the RS-485 interface. Both solutions implement this automatically.
Unlike the InverFlow (which the integration polls as a Modbus slave), the Hayward heat-pump control board is itself the master: it broadcasts its sensor block and polls each peripheral. The integration therefore emulates the Wi-Fi module on the bus:
- it keeps a persistent connection to the USR gateway and answers the board's
status polls with the correct device identity (
reg 3009 = 0x0102) and a small reply hold-off (~60 ms, like the real touch panel); - it mirrors the pump's settings and, to apply a change, raises a flag, serves the edited settings block when the board reads it back, then the board commits.
Same USR-TCP232 wiring and web-UI settings as the InverFlow (TCP Server, 9600
8N1, RFC2217 off). Wire the pump's RS-485 A/B/GND to the USR A+/B-/GND.
Settings → Devices & Services → Add Integration → USR Modbus Bridge
- Gateway step: IP / port / 9600 / 8N1 (as for any device).
- Device type step: choose Hayward pool heat pump, give it a name.
The Modbus address is fixed to
0x02and is not asked (it is structural for the Wi-Fi slot). - Done. Within a few seconds the settings blocks are pulled (auto-refresh) and the climate entity + sensors become available.
Options (post-setup): click Configure to tune the reply hold-off (ms) if a command is ever not picked up (50–90 ms is the useful range).
| Entity | Type | Description |
|---|---|---|
| Heat pump | climate |
on/off, heat / cool / auto, target temperature, current temp (water inlet), heating/cooling action |
| Water inlet / outlet | Sensor | Loop temperatures |
| Suction / Coil / Ambient / Exhaust | Sensor | Circuit temperatures |
| Compressor current | Sensor | A |
| AC voltage | Sensor | V |
| Fan speed | Sensor | rpm |
| Per-mode setpoint memory (cool/heat/auto) | Sensor (diag) | Memorised setpoints from block 1091 |
Prefer a direct ESP32 on the bus instead of a USR gateway? A dedicated ESPHome
external component provides the same climate entity + sensors, standalone:
ha-esphome-hayward. Use
an auto-direction RS-485 transceiver (e.g. MAX13487E) — no DE pin needed.
Full read + write support for the EMEC WDPHRH pool water controller (pH + Redox regulation with proportional or ON/OFF dosing), reverse-engineered in 2026 by sniffing the BT ETH gateway module talking to the pump over RS-485 and correlating every field with the Nimbus / MyEmec cloud UI.
Unlike the Modbus devices, the WDPHRH speaks an ASCII protocol from the ERMES family with three quirks:
- Heartbeat every ~500 ms (
34tb00 #0#\r\n) or the pump goes silent - Double-CR terminator on commands (
3401<cmd>\r\r) — a single\ris interpreted as a malformed heartbeat and ignored - Distinct framing (ASCII fields separated by
#, terminated by\r) so it must live on its own bus, never mixed with Modbus RTU slaves
Read reply and write ACK look like:
→ 3401setpntr\r\r
← 34gpd01&WD#0770#0730#040#000#00#0#0630#0670#050#000#00#0#setpntrend\r
→ 3401setpnw0760073004000000006300670050000000setpnwend\r\r
← 34gpd01&setpnwokend\r
The write payload is the read fields concatenated without # separators,
each field zero-padded to its known fixed width.
The 500 ms heartbeat traffic and the 38400 baud rate are incompatible with sharing a Modbus RTU bus (which requires ≥ 3.5-char inter-frame silence). Wire the WDPHRH on its own USR-TCP232 + own MAX13487E (or equivalent auto-direction transceiver). Terminate the bus with 120 Ω at each end.
Pump RS-485 wiring (verified from the EMEC BT MODBUS documentation):
- Pin 1: A = RS-485+
- Pin 2: B = RS-485−
The pump was enrolled on e-nimbus.com during the
reverse-engineering sessions, so writes have only been tested with an active
Nimbus session in the background. It is not proven that Nimbus enrolment
is required for HA writes to work — the initial #no#change replies we got
early on were most likely caused by wrong command names (we were trying
setpntw and changesp before discovering that the real write is setpnw).
If your unit is not enrolled and writes fail with #no#change, please open
an issue — that would be the data point needed to actually pin the requirement
down.
Note:
ermes-server.comis being phased out; new BT ETH modules must be enrolled one-nimbus.comif you want the cloud UI.
Settings → Devices & Services → Add Integration → USR Modbus Bridge
- Gateway step: IP / port of the WDPHRH's USR / 38400 / 8N1.
- Device type step: choose EMEC WDPHRH, give it a name.
- EMEC controller step: RS-485 slave address (usually
1) and poll interval (10 s is a good default).
The setup step probes with a valuer query and only creates the entry if the
pump answers.
Everything is organised on the device page under three sections.
Main controls — the live view and the acknowledge button:
| Entity | Type | Description |
|---|---|---|
| pH | Sensor | Live pH (device_class ph) |
| Redox | Sensor | Live Redox in mV (device_class voltage) |
| pH- reservoir empty | Binary sensor | Alarm bit a1 |
| Chlorine reservoir empty | Binary sensor | Alarm bit a2 |
| No flow | Binary sensor | Alarm bit a3 |
| Standby | Binary sensor | Alarm bit a8 |
| Reset alarms | Button | Sends resalw to clear latched alarms |
Configuration — everything writable to the pump:
| Entity | Type | Range / options | Writes via |
|---|---|---|---|
| pH high / low setpoint | Number | 0.00 – 14.00, step 0.05 | setpnw |
| pH high / low dose % | Number | 0 – 100 % | setpnw |
| pH waiting time | Number | 0 – 99 min (used in ON/OFF mode) | setpnw |
| pH working mode | Select | PROPORTIONAL / ON-OFF | setpnw |
| Redox low / high setpoint | Number | 0 – 999 mV, step 5 | setpnw |
| Redox low / high dose % | Number | 0 – 100 % | setpnw |
| Redox waiting time | Number | 0 – 99 min | setpnw |
| Redox working mode | Select | PROPORTIONAL / ON-OFF | setpnw |
| Max strokes pH / Redox | Number | 1 – 180 P/m | setskw |
| Passcode | Number | 4-digit | paramw |
| Feeding delay | Number | 0 – 60 min | paramw |
| Priority mode | Select | No priority / pH / Redox | paramw |
| Dosing alarm pH / Redox threshold | Number | 0 (off) or ≥ 1 min | alldow |
| Dosing alarm pH / Redox mode | Select | DOSE / STOP | alldow |
| Probe failure pH / Redox threshold | Number | 0 (off) or 100 – 250 min | allprw |
| Probe failure pH / Redox mode | Select | DOSE / STOP | allprw |
| Flow mode | Select | Direct / Reverse / Disable | flowsw |
| Flow time | Number | 0 – 99 min | flowsw |
| DI Standby / pH level / Redox level is N.C. | Switch | Contact type N.O. / N.C. | diginw |
| Sync clock to HA | Button | Pushes HA time to pump via clockw |
clockw |
Diagnostic — troubleshooting and unmapped fields:
| Entity | Type | Purpose |
|---|---|---|
| Service counter | Sensor | Total service hours |
| Service offset | Sensor | Internal offset |
| Output 1 / 2 / 3 | Binary sensor | Relay states from outptr |
| Alarm 4 / 5 / 6 / 7 (unknown) | Binary sensor | Un-mapped alarm bits |
| Dosing alarm pH / Redox enabled | Binary sensor | Derived (minutes > 0) |
| Probe failure pH / Redox enabled | Binary sensor | Derived (minutes > 0) |
| valuer field 3 (unconfirmed) | Sensor | Suspected current dose %, always 0 in captures — awaiting live confirmation |
| valuer field 4 (raw) | Sensor | Trailing status flag from valuer |
| Restart connection | Button | Reconnects the TCP session |
Writable enums encode as small integers/strings on the wire:
- Working mode (pH / Redox): 0 = PROPORTIONAL, 1 = ON/OFF
- Priority: 1 = No priority, 2 = pH priority, 3 = Redox priority
- Flow mode: 0 = Disable, 1 = Reverse, 2 = Direct
- Dosing alarm mode / Probe failure mode: 0 = DOSE, 1 = STOP
- DI contact type: 0 = N.O., 1 = N.C.
- Enabled via minutes = 0: dosing alarm and probe failure are considered disabled when the corresponding minutes field is 0; setting a non-zero value both enables the feature and configures its threshold.
The write layouts of every command are documented at the top of
bridge/devices/emec_wdphrh.py.
| Parameter | Value |
|---|---|
| Work Mode | TCP Server |
| Local Port | 8899 |
| Baud Rate | 9600 |
| Data Size | 8 bit |
| Parity | None |
| Stop Bits | 1 |
| Similar RFC2217 | ❌ OFF (critical — TX stays 0 if enabled) |
| TCP Server-kick off old connection | ❌ OFF |
| Modbus Type | None |
| InverFlow connector | USR terminal |
|---|---|
| PIN 6 (RS485 A / DATA+) | A+ |
| PIN 7 (RS485 B / DATA−) | B− |
| PIN 5 (GND) | GND (optional) |
- HACS → Integrations → ⋮ → Custom repositories
- Add
https://github.com/Elwinmage/ha-usr-modbus-bridgeas Integration - Install USR Modbus Bridge → Restart HA
Copy custom_components/usr_modbus_bridge/ into your HA custom_components/ folder and restart.
Settings → Devices & Services → Add Integration → USR Modbus Bridge
Step 1 — Gateway
| Field | Description |
|---|---|
| Gateway model | USR-TCP232-304 / USR-TCP232-306 / USR-N510 / Other |
| IP address | IP of the USR converter |
| TCP port | Default: 8899 |
| Baud rate | 9600 |
| Data bits / Parity / Stop bits | 8 / None / 1 |
Step 2 — Device
| Field | Description |
|---|---|
| Device type | InverFlow Eco |
| Modbus address | 170 (shown in pump menu) |
| Friendly name | e.g. "Pool pump" |
| Poll interval | 2–300 s (default 10 s) |
Options (post-setup): click Configure to change baud rate, bus parameters, or poll interval without re-adding the integration.
| Component | Notes |
|---|---|
| ESP32 dev board | Any ESP32 board |
| MAX485 module | C25B or equivalent (~2€) |
ESP32 GPIO14 (TX) ──→ MAX485 DI
ESP32 GPIO15 (RX) ←── MAX485 RO
ESP32 GPIO23 ──→ MAX485 RE ┐ tie together
ESP32 GPIO23 ──→ MAX485 DE ┘
MAX485 VCC ←── 5V
MAX485 GND ──── GND
MAX485 A (DATA+) ──── InverFlow PIN 6
MAX485 B (DATA−) ──── InverFlow PIN 7
RE and DE are tied together on the module — GPIO23 HIGH = transmit, LOW = receive.
Full YAML: doc/inverflow_esphome.yaml
# Flash
esphome run doc/inverflow_esphome.yamlBoth options expose equivalent entities:
| Entity | Type | Description |
|---|---|---|
| Power | Switch | ON (restores last speed) / OFF (setpoint=0) |
| Speed | Sensor | Actual running speed % |
| Power | Sensor | Instant power consumption W |
| Speed setpoint | Number | Target speed slider |
| Restart / Wake-up | Button | Force reconnect + RS-485 wake-up |
| Error | Text sensor (diag) | Decoded error bitmask from reg 2001 |
| Firmware constants | Sensors (diag) | Raw firmware constants, hidden by default |
| Situation | Option A (HA integration) | Option B (ESPHome) |
|---|---|---|
| Pump asleep after power cut | Wake-up ping at startup + after every reconnect | Wake-up ping at boot |
| All registers ERR × 3 polls | Auto TCP reconnect + wake-up | ESPHome Modbus retry |
| No valid data for 5 min | Auto TCP reconnect + wake-up | Periodic wake-up every 5 min |
| Manual recovery | Restart connection button | Wake-up RS485 button |
⚠️ Only one TCP client can connect to the USR converter at a time. Never run diagnostic scripts (socat, Python monitor) while the HA integration is active.
- Create
custom_components/usr_modbus_bridge/bridge/devices/mydevice.py - Subclass
ModbusDevicefrombridge/devices/base.py - Declare
READ_REGISTERS, implementset_speed(), setWAKE_UP_REGISTERif needed - Register in
const.py → DEVICE_PROFILES
from .base import ModbusDevice, RegisterDef
class MyDevice(ModbusDevice):
DEVICE_KEY = "my_device"
DEVICE_NAME = "My Pool Pump"
MODBUS_ADDRESS = 0x01
WAKE_UP_REGISTER = 0x0001 # optional
READ_REGISTERS = [
RegisterDef(0x0001, "speed", "Speed", "%", 1, 0),
RegisterDef(0x0002, "power", "Power", "W", 1, 0),
]
async def set_speed(self, client, speed_pct: int) -> bool:
return await client.write_register(self.MODBUS_ADDRESS, 0x0010, speed_pct)
@property
def sensor_keys(self): return ["speed", "power"]
@property
def switch_key(self): return "speed"Pump does not respond after power cut → Press Restart connection (Option A) or Wake-up RS485 (Option B). The wake-up ping re-activates the RS-485 interface.
TX counter stays at 0 on USR web interface → Disable Similar RFC2217 in the USR web UI → Save → power cycle the USR physically.
All entities show unavailable → Verify no other client (socat, Python script) is connected to port 8899. Check HA logs for connection errors.
Integration does not find the device during setup → Confirm Modbus address in pump menu (InverFlow Eco default: 170). Ensure pump is powered and PIN6/PIN7 wired correctly.
ESPHome: pump not responding after ESP32 reboot → The boot wake-up ping handles this automatically. If it persists, press the Wake-up RS485 button in HA.
- ha-usr-r16-component — USR-R16 relay controller integration
- ESPHome InverFlow community config — original ESPHome YAML inspiration
MIT


