New Pentair pool automation setup – full write-up

Hi all,

With my 2020 Pentair Intelliconnect recently having died, I declined the pool store’s alternative due to the cost (2.3k dollars for something that’s basically quite basic).

I have already done some work on integrating my pool heater into HA (see: How I added a Hayward solar pool control to HA via ESPhome) so that got me thinking it must be possible to do better this time around.

I saw some other projects out there like OpnPool, raspipool and some others however I wanted it tailored for my setup. And bragging rights for doing it myself, as well, I guess :blush:

I’ve found quite a bit of useful info on the Pentair RS485 protocol and, luckily, found some ready-to-use code out there for controlling the pump (more on that below).

The pool kit I have and wanted to integrate:

  • Pentair Intelliflo VSP
  • Pentair iChlor 30
  • Valve actuator to push water to the solar heating panels (to replace the Hayward controller)
  • Valve actuator for the waterfall water feature
  • Pentair pool light

The functionalities I wanted:

  • Able to run without depending on Home Assistant:
    • all configuration to be done on the ESP via webserver
    • all automations to run on the ESP
  • All sensors & settings are exposed to Home Assistant and can be altered there
  • Ability to select the pump program (set locally on the pump, eg 1000 RPM or 47 GPM) at any time (during or outside of a schedule)
  • Run the pump in line with 2 time based schedules, each with their respective program
  • Switch on the power for the chlorinator based on pump status (should only be powered when pump is running)
  • Set pool heating target temperature
  • Monitor the pool & roof temperature, open the valve to start heating the water (if temps are fine) and change the pump program to ensure sufficient flow to the panels.
  • Freeze protection to run the pump if temperature goes below a modifiable threshold and other schedules should overrule the freeze protect mode
  • Ability to define if chlorinator should be on or off during freeze protect
  • Switch on the waterfall based on a time based schedule
  • Ability to switch on the light
  • Ability to fully disable any components on an individual basis (e.g. in case chlorinator is broken or solar heating is leaking)

What equipment I purchased:

  • 1 x Waveshare 6-channel ESP32-S3 relay module (38 USD) – reason for this board:
    • 6 relays – perfect for my setup
    • Integrated RS-485
    • External wifi antenna connector
    • Plenty of extra GPIO pins
  • 1 x ADS1115 (I2C ADC) (5 USD) – chosen over onboard ADCs for precision purposes – I soldered this to a small separate board to accommodate the 10Kohm resistors and some terminals for the wires
  • 1 x Switchbot BLE outdoor thermometer (13 USD) – for outdoor temperature reading to enable freeze protect of the pump
  • 1 x RS485 to TTL (5 USD) – currently unused (more on that later)
  • 2 x 240VAC to 24VAC transformers (34 USD) – to power the actuators (probably could have gotten by with 1 but wanted ample power for them) -
  • 1 x 24VAC to 5VDC transformer (21 USD) – to provide 5 V for the board
  • 1 x RF coaxial antenna extension cable (3.25 USD)

I repurposed the outdoor case from the Pentair Intelliconnect so that one was free (kind of) and I still had some power switches (so used 2 of those to switch both phases of the 240V).

This is what it looks like:

The code:

I then wrote scripts that are run through every 2 seconds:

  • The “pump schedule check” verifies if current time is within any of the schedules and, if so, switches on pump and / or changes pump program
  • The “pump program check” verifies if any changes were done to pump programs and if a change was done in the currently running schedule, then update immediately
  • The “ichlor on / off script” checks if the pump has been running or stopped for a modifiable period of time (delay) and switches the power relays on or off
  • The “waterfall open / close” script verifes if the current time is within the waterfall schedule and, if so, opens the waterfall valve
  • The “pool heating check” script verifies if the temperature of the roof is greater than the pool (plus a differential to not start heating if the roof is only marginally hotter than the pool) and, if so, opens the heating valve and adjusts the pump program to an appropriate setting (based on GPM)
  • The “freeze protect check” script is run only when the pump is idle and verifies if the outside temperature is below a modifiable minimum and, if so, runs the pump at a low RPM to prevent the pipes from freezing (I’m in Florida so this is only an issue a few days a year).

Then this is the final yaml with all the above combined:

This is what it looks like in Home Assistant




(note: iChlor override is temporary to prevent the device from being powered on/off through the script because currently the RS485 cable is bridged with the pump cable so the iChlor can monitor if the pump is running or not and doesn’t produce chlorine when it isn’t)



TO DO

  • Fully integrate with the iChlor so I can set the output percentage → while I’ve found some code for this - see: GitHub - wolfson292/intellichlor: Intellichlor ESPHome Custom Component - I’m currently not able to make this work. The ESP simply reboots after 1 minute of uptime. Could use some help here :blush:
  • Add a screen inside the box to interact with the ESP without going through the webserver
4 Likes

Oddly, I have an almost identical setup as you.
I got the WaveShare 6-channel this summer and hooked it up to my Intelliflo pump, intlelliChlor, and waterfall valve. I used 1wire ds18b20b temperature probes for air and water temp instead, but haven’t gotten around to hooking up the pentair pool light.
I was having trouble with EspHome so I programmed everything in c/c++ for ESP Rainmaker instead. That’s what I’m using now, but I’m not completely happy with it, because the Rainmaker UI is pretty restrictive and it does not integrate well with HA.
I just got an n150 based mini computer and configured it with Proxmox and HAOS. So I thought I would search again for pool automation tools and your article came up.
It looks pretty nice. I’ll try integration on my system in a few days, but I had a few questions before then.
You show a photo of your system, but it’s pretty hard to tell the full wiring diagram. Have you thought about posting a drawing of the wiring?
It looks like the second RS485 isn’t connected to anything and the pump and chlorinator are attached to the board RS485 port. That’s how I have mine configured too. While I’ve gotten past most of the issues talking to the pump, the only thing I can get from the chlorinator is its name.
What is the board next to the 2nd RS485 for?
Once I get the basics running and move on to the temperature and programming the light patterns, I’ll send you updates for your integration so it can cover a few more things.
Thanks again for the work.
That’s it for now. I’m sure I’ll have few more when I get further into things.

Hi FreezerEagle,

Glad to hear you’re making the jump to Home Assistant - I’ve been using it for a very long time and can no longer imagine running my home without it.

From the picture, it’s indeed not easy to see the wiring so I’ve created a (quick and dirty, if I may say so) diagram:


Now, you may not need all parts - all depends what you want to integrate. Note, the distribution blocks are mounted vertically to save space (by using 3D printed supports).

The little blue board is an ACS1115 which allows me to measure the pool & roof temperatures (with the 2 wire sensors that were installed when the solar heating was put in place) much more accurately than using the onboard ADCs - I have a separate diagram for that (note - the ESP in that picture is of course the one on the main board):

The extra RS485 board I added during my attempts to interface with the iChlor separately - but still haven’t been successful so that’s why it’s just sitting there idle. I’ve not been able to figure out how to combine the 2 different code repos I found (Intelliflo & iChlor - I’m just using the former).
For now, I’m resorting to manually updating the percentage but, as the both are connected to the same RS485, the iChlor does recognize the flow signal from the Intelliflo so it stops producing chlorine as soon as the pump becomes idle.

Hope this helps - happy to answer any other questions and looking forward to hear how you’re faring…

B.

I got the integration to work.
I was having a lot of issues, but finally figured out if the NTP server isn’t synced, nothing works and had to update the time config.
I might add a sensor that says when the time is synced and available. Also, the pump status returns the current time so that can be a backup until the NTP time is available.
Now I just need to customize it to reflect my system.
I think it could also use the ability set the speed directly instead of selecting a program. So, I’ll looking into that.

@berniedp I’ve made some good progress based off your work. But, I decided to go with setting the speed directly instead of using the preset program speeds.

This did require fixing a bug in the pentair_intelliflo external component, because it was using an incorrect code to set the speed. I also enhanced the status sensors to send back the time remaining on the current action (this is usually 1 min, but will tell you how long is left in the stages of any internal schedule being run). And the pumps clock. The clock only know hours and minutes not day or date, so i just return it an a time duration and added a formatter in the YAML Down the road I see if the might be a way to set the clock if it is off)

I switched out the temperature sensors to use Dallas DS18b20 one-wire probes, because that’s what I have.

I don’t have solar heating so I removed the heating and freeze protection (I live in south Florida it doesn’t freeze here).

Below is what the main part of my HA dashboard looks like.
I tried to keep it tight so I could see everything at once. The blue arrow on the left shows what part of the schedule is currently running.

Tapping on the start, speed, and waterfall entities will take you where you can edit the value. I need to get decent at the web_server configuration to get a better web page. Currently I only know how to do an entity per line.

I also added a feature you may be interested in: Auto Waterfall. It turns on the waterfall when the pump rpm is between 1940 and 3400 (my waterfall doesn’t run well below 1950 rpm and I didn’t want it to run during priming (3450 rpm))

image

I also used the packages feature to help separate my base, temperature and Pentair schedule features into separate files. This work well and I used substitutions to pass down thing like GPIO pin and temperature sensor ids to each package.

substitutions:
  waterfall_pin: GPIO1
  one_wire_pin: GPIO10
  air_temp_id: 0xeaaf9e231e64ff28
  water_temp_id: 0x91f895231e64ff28
  pentair_tx_pin: GPIO17
  pentair_rx_pin: GPIO18
  right_arrow: "➡️"

esphome:
  name: my-pool-controller
  friendly_name: Pool Controller
  min_version: 2025.9.0
  name_add_mac_suffix: false

esp32:
  variant: esp32s3
  framework:
    type: esp-idf

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "your encryption key"

# Allow Over-The-Air updates
ota:
  - platform: esphome
    password: "your ota password"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Pool Controller Hotspot"
    password: !secret ap_password

captive_portal:

# Enable web server for direct access
web_server:
  port: 80
  version: 3
 
packages:
  temperature: !include Include/temperature.yaml
  pump: !include Include/schedule.yaml

I’ll upload my updated component code and YAML after I have a chance to clean it up some.

My next steps are my IntelliChlor and IntelliBrite color light. The light on/off is pretty straight forward, but I want to add the ability to select the color pattern to use (this is just a matter of turning the light off and on rapidly a certain number of times).

I asked GitHub Copilot (Claude 4,5) to document the Intelichlor component. After a few back and forths, I got a decent document from it that covers much of the component. I have create quick test from the info and it does give me some info.

This is the YAML for the test:

external_components:
  - source: components/Pool_Automation/components
    components: [intellichlor]
    refresh: 0s

uart:
  - id: uart_bus
    tx_pin: ${pentair_tx_pin}
    rx_pin: ${pentair_rx_pin}
    baud_rate: 9600

intellichlor:
  id: my_intellichlor
  uart_id: uart_bus

sensor:
  - platform: intellichlor
    salt_ppm:
      name: "Salt Level"
      id: salt_ppm
    water_temp:
      name: "Chlorinator Water Temperature"
      id: water_temp
    status:
      name: "Chlorinator Status"
      id: chlorinator_status
    error:
      name: "Chlorinator Error"
      id: chlorinator_error
    set_percent:
      name: "Chlorinator Output %"
      id: chlorinator_set_percent

binary_sensor:
  - platform: intellichlor
    no_flow:
      name: "No Flow Alarm"
      id: no_flow_alarm
    low_salt:
      name: "Low Salt Alarm"
      id: low_salt_alarm
    high_salt:
      name: "High Salt Alarm"
      id: high_salt_alarm
    clean:
      name: "Clean Cell Required"
      id: clean_cell_alarm
    high_current:
      name: "High Current Alarm"
      id: high_current_alarm
    low_volts:
      name: "Low Voltage Alarm"
      id: low_voltage_alarm
    low_temp:
      name: "Low Temperature Alarm"
      id: low_temp_alarm
    check_pcb:
      name: "Check PCB"
      id: check_pcb_alarm

text_sensor:
  - platform: intellichlor
    version:
      name: "Chlorinator Version"
      id: chlorinator_version
    swg_debug:
      name: "SWG Debug Info"
      id: swg_debug

number:
  - platform: intellichlor
    swg_percent:
      name: "Chlorine Output"
      id: swg_percent_number

switch:
  - platform: intellichlor
    takeover_mode:
      name: "Takeover Mode"
      id: takeover_mode_switch

button:
  - platform: template
    name: "Refresh Chlorinator"
    on_press:
      - lambda: |-
          id(my_intellichlor).read_all_info();

This is the readme:

Pentair IntelliChlor ESPHome Component

ESPHome component for interfacing with Pentair IntelliChlor salt chlorine generators via RS485.

How It Works

Internal Functions

The component uses the following internal methods to retrieve sensor data:

  • read_all_info() - Main polling function called by update() approximately once per second. Orchestrates all sensor reads.
  • get_version_() - Sends command 0x50, 0x14, 0x00 to retrieve firmware version
  • get_temp_() - Sends command 0x50, 0x15, 0x00 to retrieve water temperature
  • takeover_() - Sends command 0x50, 0x00, 0x00 to enable takeover mode
  • set_percent_(uint8_t percent) - Sends command 0x50, 0x11, percent to set chlorine output

Polling Sequence

Every update cycle (default 60 seconds), read_all_info() executes:

  1. Get Version command (0x50, 0x14, 0x00) → Updates version text sensor
  2. Get Temperature command (0x50, 0x15, 0x00) → Updates water_temp sensor

When takeover mode is enabled:
3. Takeover command (0x50, 0x00, 0x00) → Updates status sensor
4. Set Percent command (0x50, 0x11, percent) → Updates salt_ppm, error, set_percent sensors and all 8 binary sensors

The response handler (readline_()) parses incoming packets and publishes sensor values automatically.

Hardware Requirements

  • UART interface (RS485 transceiver)
  • TX/RX pins connected to IntelliChlor
  • Optional: Flow control pin

Configuration

Basic Setup

external_components:
  - source: components/Pool_Automation/components
    components: [intellichlor]
    refresh: 0s

uart:
  - id: uart_bus
    tx_pin: GPIO17
    rx_pin: GPIO18
    baud_rate: 9600

intellichlor:
  id: my_intellichlor
  uart_id: uart_bus
  update_interval: 60s  # Optional, default is 60s
  flow_control_pin: GPIO19  # Optional

Available Entities

Sensors

All sensors are optional. Configure only the ones you need:

sensor:
  - platform: intellichlor
    salt_ppm:
      name: "Salt Level"
      id: salt_ppm
      # Returns salt concentration in PPM
      
    water_temp:
      name: "Chlorinator Water Temperature"
      id: water_temp
      # Returns water temperature in °F
      
    status:
      name: "Chlorinator Status"
      id: chlorinator_status
      # Numeric status code
      
    error:
      name: "Chlorinator Error"
      id: chlorinator_error
      # Numeric error code
      
    set_percent:
      name: "Chlorinator Output %"
      id: chlorinator_set_percent
      # Current chlorine production percentage (0-100)

Binary Sensors

Monitor various alarm conditions:

binary_sensor:
  - platform: intellichlor
    no_flow:
      name: "No Flow Alarm"
      # Triggered when flow switch detects no water flow
      
    low_salt:
      name: "Low Salt Alarm"
      # Salt level too low
      
    high_salt:
      name: "High Salt Alarm"
      # Salt level too high
      
    clean:
      name: "Clean Cell Required"
      # Cell requires cleaning
      
    high_current:
      name: "High Current Alarm"
      # Excessive current draw
      
    low_volts:
      name: "Low Voltage Alarm"
      # Voltage below threshold
      
    low_temp:
      name: "Low Temperature Alarm"
      # Water temperature too low for operation
      
    check_pcb:
      name: "Check PCB"
      # PCB issue detected

Text Sensors

text_sensor:
  - platform: intellichlor
    version:
      name: "Chlorinator Version"
      # Firmware version
      
    swg_debug:
      name: "SWG Debug Info"
      # Debug information string

Number Control

Control chlorine output percentage:

number:
  - platform: intellichlor
    swg_percent:
      name: "Chlorine Output"
      # Set chlorine production percentage (0-100)
      # Range: 0-100, Step: 1

Switch

switch:
  - platform: intellichlor
    takeover_mode:
      name: "Takeover Mode"
      # Enable/disable takeover mode
      # Allows ESP to control chlorinator settings

Complete Example

external_components:
  - source: components/Pool_Automation/components
    components: [intellichlor]
    refresh: 0s

uart:
  - id: uart_bus
    tx_pin: GPIO17
    rx_pin: GPIO18
    baud_rate: 9600

intellichlor:
  id: my_intellichlor
  uart_id: uart_bus
  update_interval: 60s

sensor:
  - platform: intellichlor
    salt_ppm:
      name: "Salt Level"
    water_temp:
      name: "Chlorinator Water Temperature"
    set_percent:
      name: "Chlorinator Output %"

binary_sensor:
  - platform: intellichlor
    no_flow:
      name: "No Flow Alarm"
    low_salt:
      name: "Low Salt Alarm"
    clean:
      name: "Clean Cell Required"

text_sensor:
  - platform: intellichlor
    version:
      name: "Chlorinator Version"

number:
  - platform: intellichlor
    swg_percent:
      name: "Chlorine Output"

switch:
  - platform: intellichlor
    takeover_mode:
      name: "Takeover Mode"

Notes

  • Requires 9600 baud RS485 connection
  • Poll interval default is 60 seconds
  • All entities are optional - configure only what you need
  • Takeover mode must be enabled to control the chlorinator from ESPHome
  • Water temperature is reported in Fahrenheit
  • Salt level is reported in PPM (parts per million)

Usage Examples

Manually Trigger Sensor Read

You can manually trigger read_all_info() from automations:

button:
  - platform: template
    name: "Refresh Chlorinator"
    on_press:
      - lambda: |-
          id(my_intellichlor).read_all_info();

Update Chlorine Output from Automation

When takeover mode is active, changes to the number control trigger set_swg_percent():

automation:
  - platform: time
    at: "10:00:00"
    then:
      - number.set:
          id: swg_percent_number
          value: 80
      # This automatically calls set_swg_percent() which triggers read_all_info()

Monitor Salt Level

automation:
  - platform: numeric_state
    entity_id: sensor.salt_ppm
    below: 2700
    then:
      - homeassistant.service:
          service: notify.mobile_app
          data:
            message: "Pool salt level low: {{ states('sensor.salt_ppm') }} PPM"

React to Alarms

automation:
  - platform: state
    entity_id: binary_sensor.no_flow
    to: 'on'
    then:
      - switch.turn_off: takeover_mode
      - logger.log: "No flow detected! Disabling takeover mode"

I took @berniedp’s work and adapted it for my environment. The repository is available here: Pool Controller Repo.

Key Improvements and Features

  1. Combined Pentair pump and chlorinator into a single component so they can share the same RS485 bus.
  2. Added a custom_web_handler component to host a custom web page directly on the ESP32.
  3. Added Pentair IntelliBrite light support, including mode selection (power‑cycle mode switching).
  4. Added support for Dallas (DS18B20) temperature probes for air and water measurements.
  5. Implemented scheduling logic based on pump RPM.
  6. Added chlorinator control and monitoring (status, salt PPM, errors, output percentage).
  7. Omitted heater support (not required in my setup).
  8. Created a web page hosted on the ESP32 that mirrors Home Assistant controls — UI improvements and CSS suggestions welcome.
  9. Added a Python tool to document and test the ESPHome REST API for any device (get_ids.py).

Notes & Next Steps

  • After further polishing I’ll do a standalone write-up/post.
  • Feedback appreciated on UI/CSS improvements for the ESP32-hosted dashboard and any other areas you have comments on.

Demo

This is what my current ESP32-hosted website looks like: You may need to zoom in to get everything: