This is a port of my original OPNpool to the ESPHome platform.
OPNpool meets ESPHome brings advanced pool automation to your smart home. By bridging legacy pool controllers with modern IoT platforms, OPNpool enables real-time monitoring, remote control, and seamless integration with Home Assistant. Whether you want to automate your pool pump based on temperature, monitor chlorinator status, or simply enjoy the convenience of remote access, OPNpool provides a robust and extensible platform for pool management.
How it works
At its core, OPNpool connects an ESP32 microcontroller to your pool controller’s RS-485 bus. The ESP32 runs ESPHome firmware, which translates pool equipment data into Home Assistant entities. This allows you to view and control your pool’s thermostats, pumps, circuits, and chlorinator directly from your smart home dashboard.
Features
- Smart Home Integration: Native support for Home Assistant and ESPHome.
- Remote Monitoring & Control: Access your pool’s status and controls from anywhere.
- Community Driven: Built on the work of pool automation enthusiasts and reverse engineers.
- Open Source: Fully transparent hardware and software—customize and extend as needed.
Getting started
The documentation below will guide you through hardware assembly, wiring, firmware installation, and Home Assistant integration. If you have questions or need help, join this discussion for community support.
This device was tested with the Pentair SunTouch controller with firmware 2.080, connected to an IntelliFlo pump and IntelliChlor salt water chlorinator.
This open source and hardware project is intended to comply with the October 2016 exemption to the Digital Millennium Copyright Act allowing “good-faith” testing," in a controlled environment designed to avoid any harm to individuals or to the public.
Acknowledgements
We proudly acknowledge the work of reverse engineering pioneers Joshua Bloch, Michael Russe, and George Saw. (Drop me a line if if I forgot you.)
Usage
Start with installing the ESPHome environment on a beefy computer. In my case, this cut the compilation time to a minute, compared to half an hour when running it as an addon to Home Assistant.
In an empty directory, create a opnpool-1.yaml configuration file as shown below.
substitutions:
device_name: opnpool-1
friendly_name: "OPNpool meets ESPHome"
description: "External component, see https://github.com/cvonk/OPNpool_meets_ESPHome"
esphome:
name: ${device_name}
comment: ${description}
friendly_name: ${friendly_name}
esp32:
variant: esp32c6 # or esp32 for <= r3 boards
board: esp32-c6-devkitc-1 # or lolin_d32 for <= r3 boards
framework:
type: esp-idf
wifi:
domain: !secret domain_name # only needed if not on the same subnet
min_auth_mode: WPA2
reboot_timeout: 0s
networks:
- ssid: !secret wifi_ssid
password: !secret wifi_password
api:
ota:
- platform: esphome
password: !secret ota_password
external_components:
- source: github://cvonk/OPNpool_meets_ESPHome
components: [ opnpool ]
logger:
level: VERBOSE # build includes ESP_LOGx up to VERBOSE
initial_level: WARN # only show up to WARN globally
logs:
poolstate_rx: VERBOSE # show decoded messages
opnpool:
id: opnpool_1
RS-485:
tx_pin: 21 # or 26 for <= r3 boards
rx_pin: 22 # or 25 for <= r3 boards
rts_pin: 23 # or 27 for <= r3 boards
Specify your own secrets in secrets.yaml
wifi_ssid: "REDACTED"
wifi_password: "REDACTED"
domain_name: ".iot.example.com" # appended to base fname to create FQDN for OTA updates
ota_password: "REDACTED"
api_encryption_key: "REDACTED"
Compile and upload the code using
esphome run opnpool-1.yaml
The first time, you need to upload over USB Serial, but after that you can use over-the-air updates.
In the output, you should see something like:
[16:26:54.983]I (423) boot: Loaded app from partition at offset 0x10000
[16:26:55.024]I (465) boot: Set actual ota_seq=1 in otadata[0]
[16:26:55.029]I (465) boot: Disabling RNG early entropy source...
[16:26:55.268][W][component:333]: api set Warning flag: unspecified
[16:26:55.273][W][component:342]: wifi set Warning flag: scanning for networks
[16:27:01.293][W][component:373]: wifi cleared Warning flag
[16:27:25.143][E][RS-485:108][pool_req_task]: tx_q full
[16:27:55.144][E][RS-485:108][pool_req_task]: tx_q full
In the above trace, the tx_q full indicates that it can’t transmit to the pool controller.
If you haven’t already, add the ESPHome integration to Home Assistant.
Under Integrations, you will find now notice that Home Assistant auto discovered your new device.
Add it, and specify your API key. Name the device and assign it an area. You should then see the entities although their values are unknown. Time to populate those entities by connecting it to the pool controller ![]()
Connect
At the core this project is an ESP32 module and a 3.3 Volt RS-485 adapter. You can breadboard this using:
- Any ESP32 module that has an USB connector and three GPIO pins available.
- Any “Max485 Module TTL”. To make it 3.3 Volt compliant, change the chip to a
MAX3485CSA+. While you’re at it, you may as well remove the 10 kΩ pullup resistors (typically labeledR1toR4). - A piece of Cat5 ethernet cable to connect to the pool controller.
If you prefer to make this a more permanent solution, I suggest rolling a printed circuit board and housing it in a IP68 waterproof case with IP68 connectors. More about this later.
THIS PROJECT IS OFFERED AS IS. IF YOU USE IT YOU ASSUME ALL RISKS. NO WARRANTIES. At the very least, turn off the power while you work on your pool equipment. Be careful, THERE IS ALWAYS A RISK OF BREAKING YOUR POOL EQUIPMENT.
Understanding the above warning … the RS-485 header can be found on the back of the control board. There are probably already wires connected that go to the devices such as pump and chlorinator.
To minimize electromagnetic interference, use a twisted pairs from e.g. CAT-5 cable to connect the A/B pair to the RS-485 adapter as shown in the table below.
| Controller | RS-485 adapter | idle state |
|---|---|---|
-DATA (green) |
A |
negative |
+DATA (yellow) |
B |
positive |
Connect the RS-485 adapter to the ESP32 module. I also pulled GPIO#27 down with a 10 kΩ resistor, to keep it from transmitting while the ESP32 is booting.
| RS-485 adapter | ESP32 module |
|---|---|
| RO | GPIO#25 |
| DI | GPIO#26 |
| DE and RE | GPIO#27 |
| GND | GND |
The serial monitor will start to show decoded messages such as:
{CTRL_VERSION_REQ: {}}
{CTRL_VERSION_RESP: {"firmware":"2.80"}}
{CTRL_TIME_REQ: {}}
{CTRL_TIME_RESP: {"tod":{"time":"18:51","date":"2026-01-18"}}}
{CTRL_HEAT_REQ: {}}
{CTRL_HEAT_RESP: {"thermos":{"POOL":{"temp":54,"sp":63,"src":"NONE"},"SPA":{"temp":54,"sp"}
If you don’t see such messages, make sure you didn’t swap the data leads, or oddly enough some people report that they do need to swap the data leads. To debug the datalink framing, set the
datalink_rxlogger level toVERBOSEand build and upload the code again. See further below.
In Home Assistant the entities should populate, and show on your favorite Lovelace UI.
PCB
For a robust and weatherproof installation, we recommend building a custom printed circuit board (PCB) to house the ESP32-C6 module, RS-485 adapter, and DC/DC converter. This approach ensures reliable connections, easier mounting, and long-term durability—especially for outdoor or poolside environments.
Note that the photo above is from a rev 1 board.
Schematic
The hardware design features a buck converter that supplies 5V to the 5V0 pin on the ESP32-C6 DevKitC-1-N8 daughterboard. The scotty diode prevent issues when the board is also powered via USB.
The main data path runs between the RS-485 connector and the ESP32-C6 on the DevKitC-1-N8. An optional termination resistor is included to minimize signal reflections on the RS-485 bus.
Board Layout
The entire schematic fits comfortably on a compact two-layer PCB. The board was designed using the free version of Autodesk EAGLE, and all source files — including layout and schematics — are available in the hardware directory of this repository.
Bill of materials
| Name | Description | Suggested mfr/part# | Price paid |
|---|---|---|---|
| PBC r4 | Printed circuit board | OSHPark | $9.43 |
| Enclosure | 158x90x60mm ABS Plastic Junction Box, IP65 | white label | $9.81 |
| RS-485_CONN | 5P Plug+socket, 5-pin, 16mm aviation, IP68 | SP16 5P | $3.98 |
| RS-485-TERM | Fixed terminal block, 4-pin, screwless, 5 mm pitch | Phoenix-Contact 1862437 | $2.02 |
| ESP32-C6 DevKitC-1-N8 | ESP32-C6 dev board with 8MB flash, based on ESP32-C6-WROOM | ESP32-C6 DevKitC-1-N8 | $9.00 |
| MAX3485 | Maxim MAX3485CSA, RS-485/UART interface IC 3.3V, 8-SOIC | Analog-Devices MAX3485CSA+T | $5.64 |
| DC1 | DC/DC Converter R-78E5.0-0.5, 7-28V to 5V, 0.5A, 3-SIP | RECOM-Power R-78E5.0-0.5 | $3.30 |
| D1, D2 | Schottky Diode, 1N5818, 30V, 1A, DO-41 | Diodes Incorporated 1N5818-T | $0.30 |
| LED1 | LED, Green Clear 571nm, 1206 | Lite-On LTST-C150KGKT | $0.15 |
| LED2 | LED, Amber Clear 602nm, 1206 | Lite-On LTST-C150AKT | $0.15 |
| C1, C2 | Capacitor, 10 µF, 25 V, multi-layer ceramic, 0805 | KEMET C0805C106K3PACTU | $0.22 |
| C3 | Capacitor, 0.1 µF, 6.3 V, multi-layer ceramic, 0805 | KEMET C0805C104M3RACTU | $0.12 |
| R1, R2 | Resistor, 68 Ω, 1/8 W, 0805 | YAGEO RC0805FR-0768RL | $0.20 |
| R3 | Not stuffed, resistor, 120 Ω, 1/4 W, 0805 | KAO SG73S2ATTD121J | $0.00 |
| PCB Screws | Machine screw, #6-32 x x 3/16", panhead | Keystone-Electronics 9306 | $0.10 |
| CONN Screws | Machine screw, M2-0.4 x 16 mm, cheese head | Essentra 50M020040D016 | $0.28 |
| CONN Nuts | Hex nut, M2-0.4, nylon | Essentra 04M020040HN | $0.15 |
Note that tariffs and shipping will add to the cost.
Troubleshooting
Not all controller firmwares are created equally. If you are not using firmware version 2.080, you will need dive down to the byte level and to tweak the network layer. If you succeed, please send me an pull request, and I will iinclude it in the next release.
To show more (or less) debug information, specify the levels in opnpoool-1.yaml
logger:
level: VERBOSE # build includes ESP_LOGx up to VERBOSE
initial_level: WARN # only show up to WARN globally
logs:
rs485: WARN
datalink_rx: WARN
datalink_tx: WARN
network_rx: WARN
network_create: WARN
pool_task: WARN
ipc: WARN
poolstate: WARN
poolstate_rx: VERBOSE
opnpool: WARN
opnpool_climate: WARN
opnpool_switch: WARN
opnpool_sensor: WARN
opnpool_binary_sensor: WARN
opnpool_text_sensor: WARN
enum_helpers: WARN
For the logger component, it is recommended to use the following levels: WARN, which shows only warnings and errors; INFO, which includes informational messages such as configuration details, warnings, and errors; and VERBOSE, which provides very detailed logs, info, warnings, and errors. Be careful not to enable too much logging, as excessive output can negatively impact the connection between Home Assistant and the ESP32-C6.







