DIY 8 station irrigation based on ESPhome on KC868-A8 relay board

Hi bremby.
I don’t have any schematic as I just build up as I go. The connections are this:

The four buttons share one common ground, and each button goes to an input on ‘IN5 - IN8’.

The 16x2 LCD has a I2C converter attached like found on AliExpress or Ebay.
https://www.aliexpress.com/item/32713904220.html?
It connects to the header designated ‘IIC’ but has ground connected to the spare ground pin on header ‘P2’.

Board power comes from the 24V AC irrigation transformer (black wires seen in the bottom of build image), the transformer is also connected to each relay by ‘daisy chained’ wire links as seen in the image.
The 24V AC is converted to 12V DC for the KC868 board. To do that I used a small 2 Amp bridge rectifier that in turn feeds a 36V DC to 12V DC converter module (which sits at the top behind the LCD). Both are soldered to small Vero board (AKA perf board) strip to make it easy to mount to the box.
My DC-DC module is obsolete, but any module with greater than 36V DC input and at least 12V DC output at 2A or more will be fine…
The 12V DC output is wired direct to the KC868 +12V power input.

Output for the 8x irrigation valves is via the Normally Open contact of each zone relay (not yet connected). Each relay Common contact is connected to one side of the 24V AC transformer via the row of tinned wire links.

KC868A8

This is my code currently below. I’ve added ability to change the LCD display to follow the status of the running zone. I don’t have any internal scheduling yet, and I’m not sure I need too, maybe a default program if HA is off-air and one button assigned to start a manual watering.
Not sure yet if I should use HA automations for scheduling or create of borrow something for Node-Red…

# 8 Zone Version and compiled via ESPhome and Python3.10.4
substitutions:
  devicename: irrigation-controller

esphome:
  name: $devicename

esp32:
  board: nodemcu-32s
  framework:
    type: arduino

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esp32-Irrigation"
#    password: !secret ap_password

# Enable logging
logger:

# Enable Home Assistant API
api:
  reboot_timeout: 0s
  services:
    - service: pause
      then:
        - sprinkler.pause: esp32_sprinkler_ctrlr  
    - service: resume
      then:
        - sprinkler.resume: esp32_sprinkler_ctrlr 

ota:
  
# I2C bus config
i2c:
  sda: 4
  scl: 5
  scan: true
  id: bus_a

# I2C I/O expander config
pcf8574:
  - id: 'pcf8574_output_hub'
    address: 0x24
    pcf8575: false
  - id: 'pcf8574_input_hub'
    address: 0x22
    
# 16x2 LCD config
display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    update_interval: 2s
    lambda: |-

        switch (id(esp32_sprinkler_ctrlr).active_valve().value_or(-1)) {

          case 0: //valve0, zone1, internal valve numbers not zone_valve_swX numbers
          it.printf(0, 0, "Front Lawn: %s", id(zone_valve_sw1).state ? "ON" : "OFF");
          if (id(zone_valve_sw1).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(0) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(0) / 60);
          }
          break;
          
          case 1: //valve1, zone2, 
          it.printf(0, 0, "Back Lawn: %s", id(zone_valve_sw2).state ? "ON" : "OFF");
          if (id(zone_valve_sw2).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(1) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(1) / 60);
          }
          break;

          case 2:  //valve2, zone3,
          it.printf(0, 0, "Front middle: %s", id(zone_valve_sw3).state ? "ON" : "OFF");
          if (id(zone_valve_sw3).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(2) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(2) / 60);
          }
          break;
          
          case 3:  //valve3, zone4
          it.printf(0, 0, "West Fence: %s", id(zone_valve_sw4).state ? "ON" : "OFF");
          if (id(zone_valve_sw4).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(3) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(3) / 60);
          }
          break;

          case 4: //valve4, zone5
          it.printf(0, 0, "Front South: %s", id(zone_valve_sw5).state ? "ON" : "OFF");
          if (id(zone_valve_sw5).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(4) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(4) / 60);
          }
          break;

          case 5:  //valve5, zone6
          it.printf(0, 0, "Back Wall W: %s", id(zone_valve_sw6).state ? "ON" : "OFF");
          if (id(zone_valve_sw6).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(5) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(5) / 60);
          }
          break;

          case 6: //valve6, zone7
          it.printf(0, 0, "Back Wall E: %s", id(zone_valve_sw7).state ? "ON" : "OFF");
          if (id(zone_valve_sw7).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(6) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(6) / 60);
          }
          break;          

          case 7:  //valve7, zone8
          it.printf(0, 0, "Verandas: %s", id(zone_valve_sw8).state ? "ON" : "OFF");
          if (id(zone_valve_sw8).state) {
          it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
          it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(7) / 60);
          } else {
          it.printf(0, 1, "Mins Set %2d     ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(7) / 60);
          }
          break;

          case -1:
          it.printf(0, 0, "Watering OFF");
          it.strftime(0, 1,"Time is %H:%M:%S", id(homeassistant_time).now());
          break;

          default:
          break;         
        }
         

# Main sprinkler code
sprinkler:
  - id: esp32_sprinkler_ctrlr
    main_switch: "Master Run/Stop"
    auto_advance_switch: "Zones Auto Advance"
    reverse_switch: "Zones Reverse"
    valve_overlap: 1s
    valves:
      - valve_switch: "Zone 1"
        enable_switch: "Zone 1 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw1
      - valve_switch: "Zone 2"
        enable_switch: "Zone 2 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw2
      - valve_switch: "Zone 3"
        enable_switch: "Zone 3 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw3
      - valve_switch: "Zone 4"
        enable_switch: "Zone 4 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw4
      - valve_switch: "Zone 5"
        enable_switch: "Zone 5 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw5
      - valve_switch: "Zone 6"
        enable_switch: "Zone 6 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw6
      - valve_switch: "Zone 7"
        enable_switch: "Zone 7 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw7
      - valve_switch: "Zone 8"
        enable_switch: "Zone 8 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw8

# Valve control outputs config via I/O expander       
switch:
  - platform: gpio
    id: zone_valve_sw1
    pin:
      pcf8574: pcf8574_output_hub
      # Use pin number 0
      number: 0
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw2
    pin:
      pcf8574: pcf8574_output_hub
      # Use pin number 1
      number: 1
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw3
    pin:
      pcf8574: pcf8574_output_hub
      # Use pin number 2
      number: 2
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw4
    pin:
      pcf8574: pcf8574_output_hub
      # Use pin number 3
      number: 3
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw5
    pin:
      pcf8574: pcf8574_output_hub
      # Use pin number 4
      number: 4
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw6
    pin:
      pcf8574: pcf8574_output_hub
      # Use pin number 5
      number: 5
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw7
    pin:
      pcf8574: pcf8574_output_hub
      # Use pin number 6
      number: 6
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw8
    pin:
      pcf8574: pcf8574_output_hub
      # Use pin number 7
      number: 7
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true

  - platform: template #this switch doesn't work properly. Can pause via HA frontend, but will not resume...  Via Services does work...
    id: pause_switch
    name: "Pause Irrigation Switch"
    turn_on_action:
      then:
        - sprinkler.pause: esp32_sprinkler_ctrlr
    turn_off_action:
      then:
        - sprinkler.resume: esp32_sprinkler_ctrlr    

# Configuration to set multiplier via number
number:
  - platform: template
    id: esp32_ctrlr_multiplier
    name: "Run Duration Multiplier"
    min_value: 1.0
    max_value: 3.0
    step: 0.1
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).multiplier();"
    set_action:
      - sprinkler.set_multiplier:
          id: esp32_sprinkler_ctrlr
          multiplier: !lambda 'return x;'  
          
# Configuration to set valve run duration via number
  - platform: template
    id: sprinkler_valve_1_duration
    name: "Zone 1 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
#    update_interval: 10s
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(0) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 0
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_2_duration
    name: "Zone 2 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(1) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 1
          run_duration: !lambda "return x * 60;" 

  - platform: template
    id: sprinkler_valve_3_duration
    name: "Zone 3 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(2) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 2
          run_duration: !lambda "return x * 60;" 
          
  - platform: template
    id: sprinkler_valve_4_duration
    name: "Zone 4 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(3) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 3
          run_duration: !lambda "return x * 60;"  
          
  - platform: template
    id: sprinkler_valve_5_duration
    name: "Zone 5 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(4) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 4
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_6_duration
    name: "Zone 6 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(5) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 5
          run_duration: !lambda "return x * 60;" 
          
  - platform: template
    id: sprinkler_valve_7_duration
    name: "Zone 7 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(6) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 6
          run_duration: !lambda "return x * 60;" 
          
  - platform: template
    id: sprinkler_valve_8_duration
    name: "Zone 8 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(7) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 7
          run_duration: !lambda "return x * 60;"   
          
#Rain Sensor from my HA
sensor:
  - platform: homeassistant
    id: rain_today
    entity_id: sensor.daily_rain_rate
    
  - platform: wifi_signal
    name: "ESP Irrigation WiFi Signal Sensor"
    update_interval: 300s  
    
  - platform: template
    name: "Zone Time Remaining Sensor"
    icon: mdi:progress-clock    
    unit_of_measurement: 'Min'
    lambda: "return id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60;"
    update_interval: 30s   
    
#  - platform: template
#    name: "Zone Active Sensor"
##    unit_of_measurement: ''
#    lambda: "return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN);" #Valves are numbered from 0-7 internally which is an issue when displaying !
#    update_interval: 30s


# Time source config
time:
  - platform: homeassistant
    id: homeassistant_time
    timezone: Australia/Adelaide
    #timezone: CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00  
    

@mr.sneezy thank you so much,
I worked a little bit around you example and I stolen also something from ESPHome Sprinkler YAML - ESPHome - Home Assistant Community (home-assistant.io)

sharing back where I’m now

ESP Code

# Enable logging
logger:

###################################################################################################
#####  I/O expander hub definition
###################################################################################################
mcp23017:
  - id: 'mcp23017_hub'
    address: 0x20
#nodemcu devkit v4.0
i2c:
  sda: 21
  scl: 22
  scan: True
###################################################################################################
###################################################################################################

###################################################################################################
# Enable Home Assistant APIs
api:
  reboot_timeout: 0s
  services:
    - service: set_multiplier
      variables:
        multiplier: float
      then:
        - sprinkler.set_multiplier:
            id: esp32_sprinkler_ctrlr
            multiplier: !lambda 'return multiplier;'

    - service: start_full_cycle
      then:
        - sprinkler.start_full_cycle: esp32_sprinkler_ctrlr

    - service: start_single_valve
      variables:
        valve: int
      then:
        - sprinkler.start_single_valve:
            id: esp32_sprinkler_ctrlr
            valve_number: !lambda 'return valve;'

    - service: next_valve
      then:
        - sprinkler.next_valve: esp32_sprinkler_ctrlr

    - service: previous_valve
      then:
        - sprinkler.previous_valve: esp32_sprinkler_ctrlr

    - service: shutdown
      then:
        - sprinkler.shutdown: esp32_sprinkler_ctrlr

    - service: pause
      then:
        - sprinkler.pause: esp32_sprinkler_ctrlr

    - service: resume
      then:
        - sprinkler.resume: esp32_sprinkler_ctrlr

    - service: resume_or_full_cycle
      then:
        - sprinkler.resume_or_start_full_cycle: esp32_sprinkler_ctrlr

    - service: repeat_2
      then:
        - sprinkler.set_repeat:
            id: esp32_sprinkler_ctrlr
            repeat: 2  # would run three cycles

    - service: repeat_3
      then:
        - sprinkler.set_repeat:
            id: esp32_sprinkler_ctrlr
            repeat: 3  # would run three cycles

# Main sprinkler code
sprinkler:
  - id: esp32_sprinkler_ctrlr
    main_switch: "Master Run/Stop"
    auto_advance_switch: "Zones Auto Advance"
    reverse_switch: "Zones Reverse"
    valve_open_delay: 2s
    valves:
      - valve_switch: "Zone 1"
        enable_switch: "Zone 1 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw1
      - valve_switch: "Zone 2"
        enable_switch: "Zone 2 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw2
      - valve_switch: "Zone 3"
        enable_switch: "Zone 3 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw3
      - valve_switch: "Zone 4"
        enable_switch: "Zone 4 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw4
      - valve_switch: "Zone 5"
        enable_switch: "Zone 5 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw5
      - valve_switch: "Zone 6"
        enable_switch: "Zone 6 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw6

# Valve control outputs config via I/O expander       
switch:
#################################################################################################
####### CONTROL SWITCH
#################################################################################################
  - platform: template
    id: esp32_sprinkler_ctrlr_run
    name: "Sprinkler Controller Run"
    optimistic: true
    on_turn_on:
      - text_sensor.template.publish:
          id: esp32_sprinkler_ctrlr_status
          state: "Running"
      - sprinkler.resume_or_start_full_cycle: esp32_sprinkler_ctrlr
      - switch.turn_off: esp32_sprinkler_ctrlr_pause
      - switch.turn_off: esp32_sprinkler_ctrlr_stop

  - platform: template
    id: esp32_sprinkler_ctrlr_stop
    name: "Sprinkler Controller Stop"
    optimistic: true
    on_turn_on:
      - text_sensor.template.publish:
          id: esp32_sprinkler_ctrlr_status
          state: "Stopped"
      - sprinkler.shutdown: esp32_sprinkler_ctrlr
      - switch.turn_off: esp32_sprinkler_ctrlr_pause
      - switch.turn_off: esp32_sprinkler_ctrlr_run

  - platform: template
    id: esp32_sprinkler_ctrlr_pause
    name: "Sprinkler Controller Pause"
    optimistic: true
    
    turn_on_action:
      - delay: 500ms
      - lambda: |-
          if(id(esp32_sprinkler_ctrlr_status).state != "Running")
          {
            id(esp32_sprinkler_ctrlr_pause).turn_off();
          }
      - lambda: |-
          if(id(esp32_sprinkler_ctrlr_status).state == "Running")
          {
            id(esp32_sprinkler_ctrlr_status).publish_state("Paused");
            id(esp32_sprinkler_ctrlr).pause();
          }

    on_turn_off:
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state == "Paused")
        {
          id(esp32_sprinkler_ctrlr_status).publish_state("Running");
          id(esp32_sprinkler_ctrlr).resume();
        } 

  - platform: template
    id: esp32_sprinkler_ctrlr_resume
    name: "Sprinkler Controller Resume"
    optimistic: true
     
#################################################################################################
####### I/0  SWITCH
#################################################################################################
  - platform: gpio
    id: zone_valve_sw1
    name: "MCP23017 Pin B2"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B2
      number: 10
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw2
    name: "MCP23017 Pin B3"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B3
      number: 11
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw3
    name: "MCP23017 Pin B4"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B4
      number: 12
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw4
    name: "MCP23017 Pin B5"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B5
      number: 13
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw5
    name: "MCP23017 Pin B6"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B6
      number: 14
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw6
    name: "MCP23017 Pin B7"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B7
      number: 15
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true

  #- platform: template #this switch doesn't work properly. Can pause via HA frontend, but will not resume...  Via Services does work...
  #  id: pause_switch
  #  name: "Pause Irrigation Switch"
  #  turn_on_action:
  #    then:
  #      - sprinkler.pause: esp32_sprinkler_ctrlr
  #  turn_off_action:
  #    then:
  #      - sprinkler.resume: esp32_sprinkler_ctrlr    


number:
# Configuration to set multiplier via number
  - platform: template
    id: sprinkler_ctrlr_multiplier
    name: "Run Duration Multiplier"
    min_value: 1.0
    max_value: 3.0
    step: 0.1
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).multiplier();"
    set_action:
      - sprinkler.set_multiplier:
          id: esp32_sprinkler_ctrlr
          multiplier: !lambda 'return x;'  
          
# Configure repeat
  - platform: template
    id: sprinkler_ctrlr_repeat_cycles
    name: "Sprinkler Repeat Cycles"
    min_value: 0
    max_value: 300
    step: 1
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).repeat();"
    set_action:
      - sprinkler.set_repeat:
          id: esp32_sprinkler_ctrlr
          repeat: !lambda 'return x;'

# Configuration to set valve run duration via number
  - platform: template
    id: sprinkler_valve_1_duration
    name: "Zone 1 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: 10s
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(0) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 0
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_2_duration
    name: "Zone 2 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: 10s
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(1) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 1
          run_duration: !lambda "return x * 60;" 

  - platform: template
    id: sprinkler_valve_3_duration
    name: "Zone 3 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: 10s
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(2) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 2
          run_duration: !lambda "return x * 60;" 
          
  - platform: template
    id: sprinkler_valve_4_duration
    name: "Zone 4 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: 10s
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(3) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 3
          run_duration: !lambda "return x * 60;"  
          
  - platform: template
    id: sprinkler_valve_5_duration
    name: "Zone 5 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: 10s
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(4) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 4
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_6_duration
    name: "Zone 6 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: 10s
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(5) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 5
          run_duration: !lambda "return x * 60;" 
          
sensor:
### SENSORS
  - platform: template
    name: "Zone Time Remaining Sensor"
    icon: mdi:progress-clock    
    unit_of_measurement: 'Min'
    accuracy_decimals: 0
    update_interval: 10s   
    lambda: |-
      if(id(esp32_sprinkler_ctrlr_status).state != "Paused")
      {
        return id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60;
      }
      else
      {
        return {};
      }
  
  - platform: template
    name: "Zone Active Sensor"
    unit_of_measurement: ''
    accuracy_decimals: 0
    #Valves are numbered from 0-7 internally which is an issue when displaying !
    lambda: |-
      if(id(esp32_sprinkler_ctrlr_status).state == "Stopped")
      {
        return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN);
      }
      else
      {
        return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN) + 1;
      }
    update_interval: 10s

text_sensor:
    - platform: template
      id: esp32_sprinkler_ctrlr_status
      name: "Sprinklers Status"
      update_interval: 1s

# Time source config
time:
  - platform: homeassistant
    id: homeassistant_time

LOVELACE

  - theme: Backend-selected
    title: Sprinkler
    path: sprinkler
    type: panel
    badges: []
    cards:
      - type: vertical-stack
        cards:
          - type: horizontal-stack
            cards:
              - show_name: true
                show_icon: true
                show_state: true
                type: glance
                entities:
                  - entity: sensor.sprinklers_status
                  - entity: sensor.zone_active_sensor
                  - entity: sensor.zone_time_remaining_sensor
                state_color: false
              - type: entities
                entities:
                  - entity: number.run_duration_multiplier
                  - entity: number.sprinkler_repeat_cycles
          - type: horizontal-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_1
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_2
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_3
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_4
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_5
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_6
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
          - type: horizontal-stack
            cards:
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_1_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_2_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_3_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_4_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_5_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_6_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
          - type: horizontal-stack
            cards:
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_1_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_2_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_3_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_4_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_5_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_6_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
          - square: false
            columns: 3
            type: grid
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_previous_valve
                  data: {}
                  target: {}
                icon: mdi:skip-previous
                show_state: false
                hold_action:
                  action: none
                name: Previous
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:stop
                show_state: false
                hold_action:
                  action: none
                name: Stop
                entity: switch.sprinkler_controller_stop
                tap_action:
                  action: toggle
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_next_valve
                  data: {}
                  target: {}
                icon: mdi:skip-next
                show_state: false
                hold_action:
                  action: none
                name: Next
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:refresh-auto
                show_state: false
                hold_action:
                  action: none
                name: Full Circle
                entity: switch.zones_auto_advance
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:arrow-collapse-right
                show_state: false
                hold_action:
                  action: none
                name: Start
                entity: switch.sprinkler_controller_run
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:keyboard-tab-reverse
                show_state: false
                hold_action:
                  action: none
                name: Reverse
                entity: switch.zones_reverse
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:play-pause
                show_state: false
                hold_action:
                  action: none
                name: Resume
                entity: switch.sprinkler_controller_resume
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:pause
                show_state: false
                hold_action:
                  action: none
                name: Pause
                entity: switch.sprinkler_controller_pause
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: navigate
                  navigation_path: /lovelace/sprinkler-control
                hold_action:
                  action: none
                icon: mdi:dots-horizontal
                name: Details
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_start_single_valve
                  data: {}
                  target: {}
                icon: mdi:sprinkler-variant
                show_state: true
                hold_action:
                  action: none
                name: Single
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_repeat_2
                  data: {}
                  target: {}
                icon: mdi:water-sync
                show_state: true
                hold_action:
                  action: none
                name: 2 Cycle
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_repeat_3
                  data: {}
                  target: {}
                icon: mdi:water-sync
                show_state: true
                hold_action:
                  action: none
                name: 3 Cycle
  - theme: Backend-selected
    title: SPRINKLER Control
    path: sprinkler-control
    type: panel
    subview: true
    badges: []
    cards:
      - type: grid
        cards:
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_1
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_1_duration
              - type: entities
                entities:
                  - entity: number.zone_1_duration
                  - entity: switch.zone_1_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_2
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_2_duration
              - type: entities
                entities:
                  - entity: number.zone_2_duration
                  - entity: switch.zone_2_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_3
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_3_duration
              - type: entities
                entities:
                  - entity: number.zone_3_duration
                  - entity: switch.zone_3_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_4
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_4_duration
              - type: entities
                entities:
                  - entity: number.zone_4_duration
                  - entity: switch.zone_4_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_5
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_5_duration
              - type: entities
                entities:
                  - entity: number.zone_5_duration
                  - entity: switch.zone_5_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_6
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_6_duration
              - type: entities
                entities:
                  - entity: number.zone_6_duration
                  - entity: switch.zone_6_enable
                    state_color: true

AUTOMATION

- id: '1665523175659'
  alias: Sprinkler Controller
  description: ''
  trigger:
  - platform: state
    entity_id:
    - switch.sprinkler_controller_stop
    to: 'on'
    id: SprinklerStop
  - platform: state
    entity_id:
    - switch.master_run_stop
    from: 'on'
    to: 'off'
    id: EndCycle
  - platform: state
    entity_id:
    - switch.sprinkler_controller_resume
    to: 'on'
    id: SprinklerResume
  condition: []
  action:
  - if:
    - condition: trigger
      id: SprinklerStop
    then:
    - delay:
        hours: 0
        minutes: 0
        seconds: 2
        milliseconds: 0
    - type: toggle
      device_id: 2a2143a5268d8661b4ce50e2807dcd62
      entity_id: switch.sprinkler_controller_stop
      domain: switch
  - if:
    - condition: and
      conditions:
      - condition: not
        conditions:
        - condition: state
          entity_id: sensor.sprinklers_status
          state: Paused
    then:
    - type: toggle
      device_id: 2a2143a5268d8661b4ce50e2807dcd62
      entity_id: switch.sprinkler_controller_stop
      domain: switch
  - if:
    - condition: trigger
      id: SprinklerResume
    then:
    - type: turn_off
      device_id: 2a2143a5268d8661b4ce50e2807dcd62
      entity_id: switch.sprinkler_controller_pause
      domain: switch
    - type: turn_off
      device_id: 2a2143a5268d8661b4ce50e2807dcd62
      entity_id: switch.sprinkler_controller_resume
      domain: switch
  mode: single

Here how it looks like


I have something to fix/understand:

  1. when I disable a zone, if I use the Next or Previous button the zone isn’t skipped. It works if I toggle the switch. any idea where is my error?
  2. I would like to have to total amout cycle remaing time and if i’m running more cycles what is the actual cycle eg 2/3, 3/6, etc
  3. can be possible to pass the zone value with sprinkler.start_single_valve directly by the UI with a drop down list menu? eg. press the single button and then select which valve to control
  4. how to use/interact with the queue (add/remove, see the queue list, etc)

I found a response for point 1

  • The next_valve, previous_valve and start_single_valve actions ignore whether a valve is enabled via its enable switch.

Not sure I can help with points 2, 3, 4 as your Yaml skills are well above mine. Nice frontend cards by the way :slight_smile:
I found an issue with ‘resume’ myself but have not tried to fault find it. I can Pause via GUI but Resume does not work.
Yes it would be better if previous/next did check the valve enable state. Perhaps this should be raised in the ESPhome Sprinkler git issues.

For my build I probably won’t use any Repeat cycle function, as I think the Multiplier is the way I’d change a cycle.

I’m experimenting a little bit with the “challenge” n#2

UI

  - theme: Backend-selected
    title: Sprinkler
    path: sprinkler
    type: panel
    badges: []
    cards:
      - type: vertical-stack
        cards:
          - type: horizontal-stack
            cards:
              - type: entities
                entities:
                  - entity: sensor.sprinklers_status
                    icon: mdi:list-status
                    secondary_info: none
                  - entity: timer.sprinklercountdown
                    name: Sprinkler Countdown
                    secondary_info: none
                  - entity: sensor.zone_active_sensor
                    icon: mdi:map-marker-path
                    secondary_info: none
                  - entity: sensor.zone_time_remaining_sensor
                    icon: mdi:history
                    secondary_info: none
                state_color: true
              - type: entities
                entities:
                  - entity: number.run_duration_multiplier
                    icon: mdi:chart-multiline
                    secondary_info: none
                  - entity: number.sprinkler_repeat_cycles
                    icon: mdi:repeat
                    secondary_info: none
                state_color: true
          - type: horizontal-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_1
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_2
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_3
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_4
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_5
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - show_name: true
                show_icon: true
                type: button
                entity: switch.zone_6
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
          - type: horizontal-stack
            cards:
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_1_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_2_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_3_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_4_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_5_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
              - show_name: false
                show_icon: true
                type: button
                entity: switch.zone_6_enable
                icon: mdi:toggle-switch-outline
                show_state: false
                hold_action:
                  action: none
          - type: horizontal-stack
            cards:
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_1_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_2_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_3_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_4_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_5_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
              - show_name: false
                show_icon: false
                type: button
                entity: number.zone_6_duration
                icon: ''
                show_state: true
                hold_action:
                  action: none
          - square: false
            columns: 3
            type: grid
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_previous_valve
                  data: {}
                  target: {}
                icon: mdi:skip-previous
                show_state: false
                hold_action:
                  action: none
                name: Previous
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:stop
                show_state: false
                hold_action:
                  action: none
                name: Stop
                entity: switch.sprinkler_controller_stop
                tap_action:
                  action: toggle
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_next_valve
                  data: {}
                  target: {}
                icon: mdi:skip-next
                show_state: false
                hold_action:
                  action: none
                name: Next
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:refresh-auto
                show_state: false
                hold_action:
                  action: none
                name: Full Circle
                entity: switch.zones_auto_advance
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:arrow-collapse-right
                show_state: false
                hold_action:
                  action: none
                name: Start
                entity: switch.sprinkler_controller_run
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:keyboard-tab-reverse
                show_state: false
                hold_action:
                  action: none
                name: Reverse
                entity: switch.zones_reverse
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:play-pause
                show_state: false
                hold_action:
                  action: none
                name: Resume
                entity: switch.sprinkler_controller_resume
              - show_name: true
                show_icon: true
                type: button
                icon: mdi:pause
                show_state: false
                hold_action:
                  action: none
                name: Pause
                entity: switch.sprinkler_controller_pause
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: navigate
                  navigation_path: /lovelace/sprinkler-control
                hold_action:
                  action: none
                icon: mdi:dots-horizontal
                name: Details
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_start_single_valve
                  data: {}
                  target: {}
                icon: mdi:sprinkler-variant
                show_state: true
                hold_action:
                  action: none
                name: Single
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_repeat_2
                  data: {}
                  target: {}
                icon: mdi:water-sync
                show_state: true
                hold_action:
                  action: none
                name: 2 Cycle
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: call-service
                  service: esphome.sprinkler_repeat_3
                  data: {}
                  target: {}
                icon: mdi:water-sync
                show_state: true
                hold_action:
                  action: none
                name: 3 Cycle
  - theme: Backend-selected
    title: SPRINKLER Control
    path: sprinkler-control
    type: panel
    subview: true
    badges: []
    cards:
      - type: grid
        cards:
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_1
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_1_duration
              - type: entities
                entities:
                  - entity: number.zone_1_duration
                  - entity: switch.zone_1_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_2
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_2_duration
              - type: entities
                entities:
                  - entity: number.zone_2_duration
                  - entity: switch.zone_2_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_3
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_3_duration
              - type: entities
                entities:
                  - entity: number.zone_3_duration
                  - entity: switch.zone_3_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_4
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_4_duration
              - type: entities
                entities:
                  - entity: number.zone_4_duration
                  - entity: switch.zone_4_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_5
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_5_duration
              - type: entities
                entities:
                  - entity: number.zone_5_duration
                  - entity: switch.zone_5_enable
                    state_color: true
          - type: vertical-stack
            cards:
              - show_name: true
                show_icon: true
                type: button
                tap_action:
                  action: toggle
                entity: switch.zone_6
                icon: mdi:sprinkler
                show_state: false
                hold_action:
                  action: none
              - type: entity
                entity: number.zone_6_duration
              - type: entities
                entities:
                  - entity: number.zone_6_duration
                  - entity: switch.zone_6_enable
                    state_color: true

AUTOMATION

- id: '1665523175659'
  alias: Sprinkler Controller
  description: ''
  trigger:
  - platform: state
    entity_id:
    - switch.sprinkler_controller_stop
    to: 'on'
    id: SprinklerStop
    from: 'off'
  - platform: state
    entity_id:
    - switch.master_run_stop
    from: 'on'
    to: 'off'
    id: EndCycle
  - platform: state
    entity_id:
    - switch.sprinkler_controller_resume
    to: 'on'
    id: SprinklerResume
  - platform: state
    entity_id:
    - switch.sprinkler_controller_run
    to: 'on'
    id: Sprinkler Controller Start
    from: 'off'
  - platform: state
    entity_id:
    - switch.sprinkler_controller_pause
    id: Sprinkler Controller Pause
    from: 'off'
    to: 'on'
  condition: []
  action:
  - choose:
    - conditions:
      - condition: trigger
        id: SprinklerStop
      sequence:
      - delay:
          hours: 0
          minutes: 0
          seconds: 2
          milliseconds: 0
      - type: toggle
        device_id: 2a2143a5268d8661b4ce50e2807dcd62
        entity_id: switch.sprinkler_controller_stop
        domain: switch
      - service: timer.finish
        data: {}
        target:
          entity_id: timer.sprinklercountdown
    - conditions:
      - condition: trigger
        id: EndCycle
      - condition: and
        conditions:
        - condition: not
          conditions:
          - condition: state
            entity_id: sensor.sprinklers_status
            state: Paused
      sequence:
      - type: toggle
        device_id: 2a2143a5268d8661b4ce50e2807dcd62
        entity_id: switch.sprinkler_controller_stop
        domain: switch
    - conditions:
      - condition: trigger
        id: SprinklerResume
      sequence:
      - type: turn_off
        device_id: 2a2143a5268d8661b4ce50e2807dcd62
        entity_id: switch.sprinkler_controller_pause
        domain: switch
      - type: turn_off
        device_id: 2a2143a5268d8661b4ce50e2807dcd62
        entity_id: switch.sprinkler_controller_resume
        domain: switch
      - service: timer.start
        data: {}
        target:
          entity_id: timer.sprinklercountdown
    - conditions:
      - condition: trigger
        id: Sprinkler Controller Start
      sequence:
      - service: timer.start
        data:
          duration: '{{  ( (states(''number.zone_1_duration'')|int*60)+ (states(''number.zone_2_duration'')|int*60)+
            (states(''number.zone_3_duration'')|int*60)+           (states(''number.zone_4_duration'')|int*60)+
            (states(''number.zone_5_duration'')|int*60)+ (states(''number.zone_5_duration'')|int*60)
            )  | timestamp_custom("%H:%M:%S", 0) }}'
        target:
          entity_id: timer.sprinklercountdown
    - conditions:
      - condition: trigger
        id: Sprinkler Controller Pause
      sequence:
      - service: timer.pause
        data: {}
        target:
          entity_id: timer.sprinklercountdown
    alias: SprinklerChoices
  mode: single

Cool. Nothing quite so interesting here. Installed the four physical buttons on the box lid and configured ESPhome to read them. Working fine. Now to do something useful with them.
(also adding a power input fuse to the box from the 24V AC transformer)

1 Like

thank you @mr.sneezy , as you are some “steps” ahead me… shall you share how you wired the buttons and how configured the inputs?
I will try my best to get to the same page:D

I added four buttons using normally open momentary switches I had in my parts box. Kinconys diagram shows how to wire them. I picked I2C I/O hub inputs 5-8 because they were close to the ground pin which needs to be connected to one side of each button. You could use 1-4 etc to be more logical.
buttons2
Here is a closer image of the wiring as seen on the circuit board showing the 5 wires to the I2C I/O hub inputs.


Here’s a image of the back of the buttons in case it helps, really nothing to see though.

I’ve added a small inline fuse and holder to the main 24V AC input, as the transformer has none. It’s to protect the transformer if a valve cable in the garden is damged (shorted).
Like the LCD mount and the button mount, it’s 3D printed on my old but reliable Prusa i3 clone.

Code associated with the buttons for testing them. Results are seen in the ESPhome logs live view after flashing if you don’t have the LCD…

# I2C bus config
i2c:
  sda: 4
  scl: 5
  scan: true
  id: bus_a

# I2C I/O expander config
pcf8574:
  - id: 'pcf8574_output_hub'
    address: 0x24
    pcf8575: false
  - id: 'pcf8574_input_hub'
    address: 0x22

binary_sensor:
  - platform: gpio
    id: button_1 #push button on front of controller enclosure
    pin:
      pcf8574: pcf8574_input_hub
      # Use pin number 4 (starting at 0)
      number: 4
      # One of INPUT or OUTPUT
      mode:
        input: true
      inverted: true
    internal: true
    on_click:
    - min_length: 50ms
      max_length: 350ms
      then:
        - sprinkler.resume_or_start_full_cycle: esp32_sprinkler_ctrlr

  - platform: gpio
    id: button_2 #push button on front of controller enclosure
    pin:
      pcf8574: pcf8574_input_hub
      # Use pin number 5 (starting at 0)
      number: 5
      # One of INPUT or OUTPUT
      mode:
        input: true
      inverted: true
    internal: true
    on_click:
    - min_length: 50ms
      max_length: 350ms
      then:
        - sprinkler.next_valve: esp32_sprinkler_ctrlr

  - platform: gpio
    id: button_3 #push button on front of controller enclosure
    pin:
      pcf8574: pcf8574_input_hub
      # Use pin number 6 (starting at 0)
      number: 6
      # One of INPUT or OUTPUT
      mode:
        input: true
      inverted: true
    internal: true
    on_click:
    - min_length: 50ms
      max_length: 350ms
      then:
        - sprinkler.previous_valve: esp32_sprinkler_ctrlr

  - platform: gpio
    id: button_4 #push button on front of controller enclosure
    pin:
      pcf8574: pcf8574_input_hub
      # Use pin number 7 (starting at 0)
      number: 7
      # One of INPUT or OUTPUT
      mode:
        input: true
      inverted: true
    internal: true
    on_click:
    - min_length: 50ms
      max_length: 350ms
      then:
        - sprinkler.pause: esp32_sprinkler_ctrlr
    - min_length: 500ms
      max_length: 2000ms
      then:
        - sprinkler.shutdown: esp32_sprinkler_ctrlr

Now that the buttons do useful stuff, I need to show that on the LCD (i.e paused status).
The paused valve number apparently is available internally form what I see on Github references, but I will need to make my LCD cases more complex or rethink that method…

paused_valve()

This is awesome! I just finished a basic irrigation controller 4 or 5 months before they introduced the sprinkler component but, doing it over had been on my to do list. Reading through the docs, I’ve been excitedl to take on this challenge and thanks to your awesome post I’ll have something to look st for help. Thank you for taking your time and sharing this with us! I appreciate this very much!

@mr.sneezy , I did something to simplify (I guess) the code for the SWITCH portion; this should safe a lot of rows…, let me know what do you think

lambda: |-
        switch (id(esp32_sprinkler_ctrlr).active_valve().value_or(-1)) {

          case -1:
          it.strftime(0, 0, "%a", id(homeassistant_time).now());
          it.strftime(10, 0, "%d-%m-%Y", id(homeassistant_time).now());
          it.strftime(12, 1,"%H:%M:%S", id(homeassistant_time).now());
          it.printf(0, 3, "Status: %s", id(esp32_sprinkler_ctrlr_status).state.c_str());
          
          if (id(esp32_sprinkler_ctrlr_status).state == "Paused") {
          it.printf(0, 2, "Zone %s", id(zone_active_sensor).state.c_str());
          it.printf(10, 2, "T: %s", id(zone_time_remaining_sensor).state.c_str());
          }

          break;
          
          default:
          it.printf(0, 0, "Zone %s:", id(zone_active_sensor).state.c_str());
          it.printf(8, 0, "%s", id(zone_active_sensor).state.c_str() ? "ON" : "OFF");
          it.printf(0, 1, "Time Letf: %s", id(zone_time_remaining_sensor).state.c_str());
          break;
        }

I also made the LCD “dimmable” following these instructions from @LUNZ :clap:

[Dimmable PCF8574 LCD Display with ESPHome]
(Dimmable PCF8574 LCD Display with ESPHome)

here is the full code, just in case

# Establish Substitutions
substitutions:
  device_name: sprinkler
  friendly_name: "Sprinkler"
  device_platform: ESP32
  device_board: esp32dev
  device_ip: X.X.X.X
  sensor_update_frequency: 1s
  sprinkler_name: esp32_sprinkler_ctrlr

packages:
  wifi: !include common/device_wifi.yaml
  device_base: !include common/device_base_ESP32.yaml
  home_assistant_api: !include common/device_api.yaml
  sensor_wifi_ip_address: !include common/sensor_wifi_ip_address.yaml  

# Enable logging
logger:
###################################################################################################
#####  I/O expander hub definition
###################################################################################################
mcp23017:
  - id: 'mcp23017_hub'
    address: 0x20
#nodemcu devkit v4.0
i2c:
  sda: 21
  scl: 22
  scan: True
  frequency: 200kHz
###################################################################################################
# 10x4 LCD config
display:
  - platform: lcd_pcf8574
    dimensions: 20x4
    address: 0x27
    update_interval: 1s
    lambda: |-
        switch (id(esp32_sprinkler_ctrlr).active_valve().value_or(-1)) {

          case -1:
          it.strftime(0, 0, "%a", id(homeassistant_time).now());
          it.strftime(10, 0, "%d-%m-%Y", id(homeassistant_time).now());
          it.strftime(12, 1,"%H:%M:%S", id(homeassistant_time).now());
          it.printf(0, 3, "Status: %s", id(esp32_sprinkler_ctrlr_status).state.c_str());
          
          if (id(esp32_sprinkler_ctrlr_status).state == "Paused") {
          it.printf(0, 2, "Zone %s", id(zone_active_sensor).state.c_str());
          it.printf(10, 2, "T: %s", id(zone_time_remaining_sensor).state.c_str());
          }

          break;
          
          default:
          it.printf(0, 0, "Zone %s:", id(zone_active_sensor).state.c_str());
          it.printf(8, 0, "%s", id(zone_active_sensor).state.c_str() ? "ON" : "OFF");
          it.printf(0, 1, "Time Letf: %s", id(zone_time_remaining_sensor).state.c_str());
          break;
        }

output:
  - platform: ledc
    pin: GPIO14
    id: sprinkler_backlight

light:
  - platform: monochromatic
    output: sprinkler_backlight
    name: "LCD Display Sprinkler Backlight"
    id: light_backlight
    restore_mode: ALWAYS_ON

###################################################################################################
###################################################################################################
# Enable Home Assistant APIs
api:
  reboot_timeout: 0s
  services:
    - service: set_multiplier
      variables:
        multiplier: float
      then:
        - sprinkler.set_multiplier:
            id: esp32_sprinkler_ctrlr
            multiplier: !lambda 'return multiplier;'
    - service: start_full_cycle
      then:
        - sprinkler.start_full_cycle: esp32_sprinkler_ctrlr
    - service: start_single_valve
      variables:
        valve: int
      then:
        - sprinkler.start_single_valve:
            id: esp32_sprinkler_ctrlr
            valve_number: !lambda 'return valve;'
    - service: next_valve
      then:
        - sprinkler.next_valve: esp32_sprinkler_ctrlr
    - service: previous_valve
      then:
        - sprinkler.previous_valve: esp32_sprinkler_ctrlr
    - service: shutdown
      then:
        - sprinkler.shutdown: esp32_sprinkler_ctrlr
    - service: pause
      then:
        - sprinkler.pause: esp32_sprinkler_ctrlr
    - service: resume
      then:
        - sprinkler.resume: esp32_sprinkler_ctrlr
    - service: resume_or_full_cycle
      then:
        - sprinkler.resume_or_start_full_cycle: esp32_sprinkler_ctrlr
    - service: repeat_2
      then:
        - sprinkler.set_repeat:
            id: esp32_sprinkler_ctrlr
            repeat: 2  # would run three cycles
    - service: repeat_3
      then:
        - sprinkler.set_repeat:
            id: esp32_sprinkler_ctrlr
            repeat: 3  # would run three cycles

# Main sprinkler code
sprinkler:
  - id: esp32_sprinkler_ctrlr
    main_switch: "Master Run/Stop"
    auto_advance_switch: "Zones Auto Advance"
    reverse_switch: "Zones Reverse"
    valve_open_delay: 2s
    valves:
      - valve_switch: "Zone 1"
        enable_switch: "Zone 1 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw1
      - valve_switch: "Zone 2"
        enable_switch: "Zone 2 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw2
      - valve_switch: "Zone 3"
        enable_switch: "Zone 3 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw3
      - valve_switch: "Zone 4"
        enable_switch: "Zone 4 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw4
      - valve_switch: "Zone 5"
        enable_switch: "Zone 5 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw5
      - valve_switch: "Zone 6"
        enable_switch: "Zone 6 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw6

# Valve control outputs config via I/O expander       
switch:
#################################################################################################
####### CONTROL SWITCH
#################################################################################################
  - platform: template
    id: esp32_sprinkler_ctrlr_run
    name: "Sprinkler Controller Run"
    optimistic: true
    on_turn_on:
      - text_sensor.template.publish:
          id: esp32_sprinkler_ctrlr_status
          state: "Running"
      - sprinkler.resume_or_start_full_cycle: esp32_sprinkler_ctrlr
      - switch.turn_off: esp32_sprinkler_ctrlr_pause
      - switch.turn_off: esp32_sprinkler_ctrlr_stop
  - platform: template
    id: esp32_sprinkler_ctrlr_stop
    name: "Sprinkler Controller Stop"
    optimistic: true
    on_turn_on:
      - text_sensor.template.publish:
          id: esp32_sprinkler_ctrlr_status
          state: "Stopped"
      - sprinkler.shutdown: esp32_sprinkler_ctrlr
      - switch.turn_off: esp32_sprinkler_ctrlr_pause
      - switch.turn_off: esp32_sprinkler_ctrlr_run
  - platform: template
    id: esp32_sprinkler_ctrlr_pause
    name: "Sprinkler Controller Pause"
    optimistic: true
    
    turn_on_action:
      - delay: 500ms
      - lambda: |-
          if(id(esp32_sprinkler_ctrlr_status).state != "Running")
          {
            id(esp32_sprinkler_ctrlr_pause).turn_off();
          }
      - lambda: |-
          if(id(esp32_sprinkler_ctrlr_status).state == "Running")
          {
            id(esp32_sprinkler_ctrlr_status).publish_state("Paused");
            id(esp32_sprinkler_ctrlr).pause();
          }
    on_turn_off:
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state == "Paused")
        {
          id(esp32_sprinkler_ctrlr_status).publish_state("Running");
          id(esp32_sprinkler_ctrlr).resume();
        } 
  - platform: template
    id: esp32_sprinkler_ctrlr_resume
    name: "Sprinkler Controller Resume"
    optimistic: true
     
#################################################################################################
####### I/0  SWITCH
#################################################################################################
  - platform: gpio
    id: zone_valve_sw1
    name: "MCP23017 Pin B2"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B2
      number: 10
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw2
    name: "MCP23017 Pin B3"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B3
      number: 11
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw3
    name: "MCP23017 Pin B4"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B4
      number: 12
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw4
    name: "MCP23017 Pin B5"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B5
      number: 13
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw5
    name: "MCP23017 Pin B6"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B6
      number: 14
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw6
    name: "MCP23017 Pin B7"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B7
      number: 15
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  #- platform: template #this switch doesn't work properly. Can pause via HA frontend, but will not resume...  Via Services does work...
  #  id: pause_switch
  #  name: "Pause Irrigation Switch"
  #  turn_on_action:
  #    then:
  #      - sprinkler.pause: esp32_sprinkler_ctrlr
  #  turn_off_action:
  #    then:
  #      - sprinkler.resume: esp32_sprinkler_ctrlr    

number:
# Configuration to set multiplier via number
  - platform: template
    id: sprinkler_ctrlr_multiplier
    name: "Run Duration Multiplier"
    min_value: 1.0
    max_value: 3.0
    step: 0.1
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).multiplier();"
    set_action:
      - sprinkler.set_multiplier:
          id: esp32_sprinkler_ctrlr
          multiplier: !lambda 'return x;'  
          
# Configure repeat
  - platform: template
    id: sprinkler_ctrlr_repeat_cycles
    name: "Sprinkler Repeat Cycles"
    min_value: 0
    max_value: 300
    step: 1
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).repeat();"
    set_action:
      - sprinkler.set_repeat:
          id: esp32_sprinkler_ctrlr
          repeat: !lambda 'return x;'

# Configuration to set valve run duration via number
  - platform: template
    id: sprinkler_valve_1_duration
    name: "Zone 1 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(0) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 0
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_2_duration
    name: "Zone 2 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(1) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 1
          run_duration: !lambda "return x * 60;" 

  - platform: template
    id: sprinkler_valve_3_duration
    name: "Zone 3 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(2) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 2
          run_duration: !lambda "return x * 60;" 
          
  - platform: template
    id: sprinkler_valve_4_duration
    name: "Zone 4 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(3) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 3
          run_duration: !lambda "return x * 60;"  
          
  - platform: template
    id: sprinkler_valve_5_duration
    name: "Zone 5 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(4) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 4
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_6_duration
    name: "Zone 6 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(5) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 5
          run_duration: !lambda "return x * 60;" 
          
sensor:
### SENSORS
  - platform: template
    name: "Cycle Total Time Sensor"
    icon: mdi:progress-clock    
    unit_of_measurement: 'Min'
    accuracy_decimals: 0
    update_interval: $sensor_update_frequency  
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(0)/60 + id(esp32_sprinkler_ctrlr).valve_run_duration(1)/60;"
    #lambda: |-
    #  {% set ns = namespace(states=[]) %}
    #  {% for s in states.sensor %}
    #    {% if s.object_id.startswith('valve_run_') and s.object_id.endswith('_duration') %}
    #      {% set ns.states = ns.states + [ s.state | float ] %}
    #    {% endif %}
    #  {% endfor %}
    #  {{ ns.states | sum | round(2) }}
#  - platform: template
#    name: "Zone Active Sensor"
#    id: zone_active_sensor
#    unit_of_measurement: ''
#    accuracy_decimals: 0
#    #Valves are numbered from 0-7 internally which is an issue when displaying !
#    lambda: |-
#      if(id(esp32_sprinkler_ctrlr_status).state == "Stopped")
#      {
#        return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN);
#      }
#      else
#      {
#        return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN) + 1;
#      }
#    update_interval: $sensor_update_frequency
  - platform: homeassistant
    id: homeassistant_sprinklercountdown
    entity_id: timer.sprinklercountdown
    internal: false

text_sensor:
    - platform: template
      id: esp32_sprinkler_ctrlr_status
      name: "Sprinklers Status"
      update_interval: $sensor_update_frequency
    - platform: template
      name: "Zone Active Sensor"
      id: zone_active_sensor
      #unit_of_measurement: ''
      #accuracy_decimals: 0
      #Valves are numbered from 0-7 internally which is an issue when displaying !
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state == "Stopped")
        {
          int zone_active = id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN);
          std::string zone_active_as_string = esphome::to_string(zone_active);
          return zone_active_as_string;
        }
        else
        {
          int zone_active = id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN) + 1;
          std::string zone_active_as_string = esphome::to_string(zone_active);
          return zone_active_as_string;
        }
      update_interval: $sensor_update_frequency

#  # Expose Valve Progress Percent as a sensor.
    - platform: template
      id: progress_percent_valve
      name: "Zone Progress"
      #unit_of_measurement: '%'
      #accuracy_decimals: 0
      update_interval: $sensor_update_frequency
      icon: "mdi:progress-clock"
      lambda: |-
        int valve_progress_percent = round(((id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(id(esp32_sprinkler_ctrlr).active_valve().value_or(0)) - id(esp32_sprinkler_ctrlr).time_remaining().value_or(0)) * 100 / id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(id(esp32_sprinkler_ctrlr).active_valve().value_or(0))));
        std::string valve_progress_percent_as_string = esphome::to_string(valve_progress_percent);
        return valve_progress_percent_as_string;

# # Expose Zone Time Remaining
    - platform: template
      name: "Zone Time Remaining Sensor"
      id: zone_time_remaining_sensor
      icon: mdi:progress-clock    
      #unit_of_measurement: 'Min'
      #accuracy_decimals: 0
      update_interval: $sensor_update_frequency
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state != "Paused")
        {
          int seconds = round(id(esp32_sprinkler_ctrlr).time_remaining().value_or(0));
          int days = seconds / (24 * 3600);
          seconds = seconds % (24 * 3600);
          int hours = seconds / 3600;
          seconds = seconds % 3600;
          int minutes = seconds /  60;
          seconds = seconds % 60;
          return {
            ((days ? String(days) + "d " : "") +
            (hours ? String(hours) + "h " : "") +
            (minutes ? String(minutes) + "m " : "") +
            (String(seconds) + "s")
            ).c_str()};
        }
        else
        {
          return {};
        }

### Input to managet the display backlight
    - platform: homeassistant    
      id: display_sprinkler_backlight
      entity_id: input_number.sprinklerbacklightlevel
      internal: true
      on_value:
        then:
          - output.turn_on: sprinkler_backlight
          - output.set_level:
              id: sprinkler_backlight
              level: !lambda |-
                return atoi(id(display_sprinkler_backlight).state.c_str()) / 100.0;

# Time source config
time:
  - platform: homeassistant
    id: homeassistant_time
    timezone: Europe/Rome
3 Likes

@mr.sneezy Very nice implementation of your ESPHome based irrigation system. I have an old RainMachine that is about to die and their replacement is both expensive and old so I think I will build my own. One thing I was curious about your setup is how the time is tracked as I did not see any reference to an RTC on the board. Did you add one? Or is time completely dependent on HA which may be an issue in case the system needs to operate on its own for some time.

I didn’t worry about RTC until it proves necessary yet. So far the network time has been reliable so the watering has been reliable.
Oddly enough I did another recent small project for somebody else that uses a $5 RTC module on an ESP32. I used this one below, but it needed four pull up resistors removed to make the SDA and SCL lines 3.3V compatible. As this module is I2C driven it should be not hard to add to the 8ch relay board in parallel with the LCD if I need too.
RTC Clock & EEPROM Module, I2C, DS1307, AT24C32 - 99Tech

Hi,
Great work. I am looking for a solution where the controller can connect to Home Assistant via ie. ethernet. I see this board has ethernet connector. Could this be connected to Home Assistant with ethernet or the eth. connector is for other purposes?

I believe so yes. ESPhome supports a good number of Ethernet chips and the one on this board is the first one they mention in the ESPhome list.
I have used Ethernet with ESPhome fine on other boards, just not this one yet.

hello, I’m trying to build an automatic watering system based on slightly modified your code, unfortunately I keep getting the error “class esphome: :sprinkler : :Sprinkler:” has no member named “time_remaining”. Do you know what could be causing this?

My YAML

# Establish Substitutions
substitutions:
  device_name: sprinkler
  friendly_name: "Sprinkler"
  device_platform: ESP32
  device_board: esp32dev
  sensor_update_frequency: 1s
  sprinkler_name: esp32_sprinkler_ctrlr

esphome:
  name: sprinkler
  friendly_name: Sprinkler

esp32:
  board: esp32dev
  framework:
    type: arduino


# Enable logging
logger:
###################################################################################################
#####  I/O expander hub definition
###################################################################################################
mcp23017:
  - id: 'mcp23017_hub'
    address: 0x26
#nodemcu devkit v4.0
i2c:
  sda: 33
  scl: 32
  scan: True
  frequency: 400kHz

ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO17_OUT
  phy_addr: 0
  power_pin: GPIO4
###################################################################################################
# 10x4 LCD config
display:
  - platform: lcd_pcf8574
    dimensions: 20x4
    address: 0x27
    update_interval: 1s
    lambda: |-
        switch (id(esp32_sprinkler_ctrlr).active_valve().value_or(-1)) {

          case -1:
          it.strftime(0, 0, "%a", id(homeassistant_time).now());
          it.strftime(10, 0, "%d-%m-%Y", id(homeassistant_time).now());
          it.strftime(12, 1,"%H:%M:%S", id(homeassistant_time).now());
          it.printf(0, 3, "Status: %s", id(esp32_sprinkler_ctrlr_status).state.c_str());
          
          if (id(esp32_sprinkler_ctrlr_status).state == "Paused") {
          it.printf(0, 2, "Zone %s", id(zone_active_sensor).state.c_str());
          it.printf(10, 2, "T: %s", id(zone_time_remaining_sensor).state.c_str());
          }

          break;
          
          default:
          it.printf(0, 0, "Zone %s:", id(zone_active_sensor).state.c_str());
          it.printf(8, 0, "%s", id(zone_active_sensor).state.c_str() ? "ON" : "OFF");
          it.printf(0, 1, "Time Letf: %s", id(zone_time_remaining_sensor).state.c_str());
          break;
        }

output:
  - platform: ledc
    pin: GPIO14
    id: sprinkler_backlight

light:
  - platform: monochromatic
    output: sprinkler_backlight
    name: "LCD Display Sprinkler Backlight"
    id: light_backlight
    restore_mode: ALWAYS_ON

###################################################################################################
###################################################################################################
# Enable Home Assistant APIs
api:
  reboot_timeout: 0s
  encryption:
    key: "FkNGBbOhmbWTgfvvZuVbM51UuFlb4EIb+dFi7whRA/g="
  services:
    - service: set_multiplier
      variables:
        multiplier: float
      then:
        - sprinkler.set_multiplier:
            id: esp32_sprinkler_ctrlr
            multiplier: !lambda 'return multiplier;'
    - service: start_full_cycle
      then:
        - sprinkler.start_full_cycle: esp32_sprinkler_ctrlr
    - service: start_single_valve
      variables:
        valve: int
      then:
        - sprinkler.start_single_valve:
            id: esp32_sprinkler_ctrlr
            valve_number: !lambda 'return valve;'
    - service: next_valve
      then:
        - sprinkler.next_valve: esp32_sprinkler_ctrlr
    - service: previous_valve
      then:
        - sprinkler.previous_valve: esp32_sprinkler_ctrlr
    - service: shutdown
      then:
        - sprinkler.shutdown: esp32_sprinkler_ctrlr
    - service: pause
      then:
        - sprinkler.pause: esp32_sprinkler_ctrlr
    - service: resume
      then:
        - sprinkler.resume: esp32_sprinkler_ctrlr
    - service: resume_or_full_cycle
      then:
        - sprinkler.resume_or_start_full_cycle: esp32_sprinkler_ctrlr
    - service: repeat_2
      then:
        - sprinkler.set_repeat:
            id: esp32_sprinkler_ctrlr
            repeat: 2  # would run three cycles
    - service: repeat_3
      then:
        - sprinkler.set_repeat:
            id: esp32_sprinkler_ctrlr
            repeat: 3  # would run three cycles
      
ota:
  password: "7001db87bfa399a3e551f7206250c3dd"

# Main sprinkler code
sprinkler:
  - id: esp32_sprinkler_ctrlr
    main_switch: "Master Run/Stop"
    auto_advance_switch: "Zones Auto Advance"
    reverse_switch: "Zones Reverse"
    valve_open_delay: 2s
    valves:
      - valve_switch: "Zone 1"
        enable_switch: "Zone 1 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw1
      - valve_switch: "Zone 2"
        enable_switch: "Zone 2 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw2
      - valve_switch: "Zone 3"
        enable_switch: "Zone 3 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw3
      - valve_switch: "Zone 4"
        enable_switch: "Zone 4 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw4
      - valve_switch: "Zone 5"
        enable_switch: "Zone 5 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw5
      - valve_switch: "Zone 6"
        enable_switch: "Zone 6 Enable"
        run_duration: 900s
        valve_switch_id: zone_valve_sw6

# Valve control outputs config via I/O expander       
switch:
#################################################################################################
####### CONTROL SWITCH
#################################################################################################
  - platform: template
    id: esp32_sprinkler_ctrlr_run
    name: "Sprinkler Controller Run"
    optimistic: true
    on_turn_on:
      - text_sensor.template.publish:
          id: esp32_sprinkler_ctrlr_status
          state: "Running"
      - sprinkler.resume_or_start_full_cycle: esp32_sprinkler_ctrlr
      - switch.turn_off: esp32_sprinkler_ctrlr_pause
      - switch.turn_off: esp32_sprinkler_ctrlr_stop
  - platform: template
    id: esp32_sprinkler_ctrlr_stop
    name: "Sprinkler Controller Stop"
    optimistic: true
    on_turn_on:
      - text_sensor.template.publish:
          id: esp32_sprinkler_ctrlr_status
          state: "Stopped"
      - sprinkler.shutdown: esp32_sprinkler_ctrlr
      - switch.turn_off: esp32_sprinkler_ctrlr_pause
      - switch.turn_off: esp32_sprinkler_ctrlr_run
  - platform: template
    id: esp32_sprinkler_ctrlr_pause
    name: "Sprinkler Controller Pause"
    optimistic: true
    
    turn_on_action:
      - delay: 500ms
      - lambda: |-
          if(id(esp32_sprinkler_ctrlr_status).state != "Running")
          {
            id(esp32_sprinkler_ctrlr_pause).turn_off();
          }
      - lambda: |-
          if(id(esp32_sprinkler_ctrlr_status).state == "Running")
          {
            id(esp32_sprinkler_ctrlr_status).publish_state("Paused");
            id(esp32_sprinkler_ctrlr).pause();
          }
    on_turn_off:
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state == "Paused")
        {
          id(esp32_sprinkler_ctrlr_status).publish_state("Running");
          id(esp32_sprinkler_ctrlr).resume();
        } 
  - platform: template
    id: esp32_sprinkler_ctrlr_resume
    name: "Sprinkler Controller Resume"
    optimistic: true
     
#################################################################################################
####### I/0  SWITCH
#################################################################################################
  - platform: gpio
    id: zone_valve_sw1
    name: "MCP23017 Pin B2"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B2
      number: 10
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw2
    name: "MCP23017 Pin B3"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B3
      number: 11
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw3
    name: "MCP23017 Pin B4"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B4
      number: 12
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw4
    name: "MCP23017 Pin B5"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B5
      number: 13
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw5
    name: "MCP23017 Pin B6"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B6
      number: 14
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  - platform: gpio
    id: zone_valve_sw6
    name: "MCP23017 Pin B7"
    pin:
      mcp23xxx: mcp23017_hub
      # Use pin B7
      number: 15
      # One of INPUT or OUTPUT
      mode:
        output: true
      inverted: true
    internal: true
  #- platform: template #this switch doesn't work properly. Can pause via HA frontend, but will not resume...  Via Services does work...
  #  id: pause_switch
  #  name: "Pause Irrigation Switch"
  #  turn_on_action:
  #    then:
  #      - sprinkler.pause: esp32_sprinkler_ctrlr
  #  turn_off_action:
  #    then:
  #      - sprinkler.resume: esp32_sprinkler_ctrlr    

number:
# Configuration to set multiplier via number
  - platform: template
    id: sprinkler_ctrlr_multiplier
    name: "Run Duration Multiplier"
    min_value: 1.0
    max_value: 3.0
    step: 0.1
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).multiplier();"
    set_action:
      - sprinkler.set_multiplier:
          id: esp32_sprinkler_ctrlr
          multiplier: !lambda 'return x;'  
          
# Configure repeat
  - platform: template
    id: sprinkler_ctrlr_repeat_cycles
    name: "Sprinkler Repeat Cycles"
    min_value: 0
    max_value: 300
    step: 1
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).repeat();"
    set_action:
      - sprinkler.set_repeat:
          id: esp32_sprinkler_ctrlr
          repeat: !lambda 'return x;'

# Configuration to set valve run duration via number
  - platform: template
    id: sprinkler_valve_1_duration
    name: "Zone 1 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(0) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 0
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_2_duration
    name: "Zone 2 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(1) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 1
          run_duration: !lambda "return x * 60;" 

  - platform: template
    id: sprinkler_valve_3_duration
    name: "Zone 3 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(2) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 2
          run_duration: !lambda "return x * 60;" 
          
  - platform: template
    id: sprinkler_valve_4_duration
    name: "Zone 4 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(3) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 3
          run_duration: !lambda "return x * 60;"  
          
  - platform: template
    id: sprinkler_valve_5_duration
    name: "Zone 5 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(4) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 4
          run_duration: !lambda "return x * 60;"
          
  - platform: template
    id: sprinkler_valve_6_duration
    name: "Zone 6 Duration"
    icon: mdi:timer
    unit_of_measurement: Min
    min_value: 1
    max_value: 120
    step: 1.0
    update_interval: $sensor_update_frequency
    mode: box
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(5) / 60;"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: esp32_sprinkler_ctrlr
          valve_number: 5
          run_duration: !lambda "return x * 60;" 
          
sensor:
### SENSORS
  - platform: template
    name: "Cycle Total Time Sensor"
    icon: mdi:progress-clock    
    unit_of_measurement: 'Min'
    accuracy_decimals: 0
    update_interval: $sensor_update_frequency  
    lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(0)/60 + id(esp32_sprinkler_ctrlr).valve_run_duration(1)/60;"
    #lambda: |-
    #  {% set ns = namespace(states=[]) %}
    #  {% for s in states.sensor %}
    #    {% if s.object_id.startswith('valve_run_') and s.object_id.endswith('_duration') %}
    #      {% set ns.states = ns.states + [ s.state | float ] %}
    #    {% endif %}
    #  {% endfor %}
    #  {{ ns.states | sum | round(2) }}
#  - platform: template
#    name: "Zone Active Sensor"
#    id: zone_active_sensor
#    unit_of_measurement: ''
#    accuracy_decimals: 0
#    #Valves are numbered from 0-7 internally which is an issue when displaying !
#    lambda: |-
#      if(id(esp32_sprinkler_ctrlr_status).state == "Stopped")
#      {
#        return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN);
#      }
#      else
#      {
#        return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN) + 1;
#      }
#    update_interval: $sensor_update_frequency
  - platform: homeassistant
    id: homeassistant_sprinklercountdown
    entity_id: timer.sprinklercountdown
    internal: false

text_sensor:
    - platform: template
      id: esp32_sprinkler_ctrlr_status
      name: "Sprinklers Status"
      update_interval: $sensor_update_frequency
    - platform: template
      name: "Zone Active Sensor"
      id: zone_active_sensor
      #unit_of_measurement: ''
      #accuracy_decimals: 0
      #Valves are numbered from 0-7 internally which is an issue when displaying !
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state == "Stopped")
        {
          int zone_active = id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN);
          std::string zone_active_as_string = esphome::to_string(zone_active);
          return zone_active_as_string;
        }
        else
        {
          int zone_active = id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN) + 1;
          std::string zone_active_as_string = esphome::to_string(zone_active);
          return zone_active_as_string;
        }
      update_interval: $sensor_update_frequency

#  # Expose Valve Progress Percent as a sensor.
    - platform: template
      id: progress_percent_valve
      name: "Zone Progress"
      #unit_of_measurement: '%'
      #accuracy_decimals: 0
      update_interval: $sensor_update_frequency
      icon: "mdi:progress-clock"
      lambda: |-
        int valve_progress_percent = round(((id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(id(esp32_sprinkler_ctrlr).active_valve().value_or(0)) - id(esp32_sprinkler_ctrlr).time_remaining().value_or(0)) * 100 / id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(id(esp32_sprinkler_ctrlr).active_valve().value_or(0))));
        std::string valve_progress_percent_as_string = esphome::to_string(valve_progress_percent);
        return valve_progress_percent_as_string;

# # Expose Zone Time Remaining
    - platform: template
      name: "Zone Time Remaining Sensor"
      id: zone_time_remaining_sensor
      icon: mdi:progress-clock    
      #unit_of_measurement: 'Min'
      #accuracy_decimals: 0
      update_interval: $sensor_update_frequency
      lambda: |-
        if(id(esp32_sprinkler_ctrlr_status).state != "Paused")
        {
          int seconds = round(id(esp32_sprinkler_ctrlr).time_remaining().value_or(0));
          int days = seconds / (24 * 3600);
          seconds = seconds % (24 * 3600);
          int hours = seconds / 3600;
          seconds = seconds % 3600;
          int minutes = seconds /  60;
          seconds = seconds % 60;
          return {
            ((days ? String(days) + "d " : "") +
            (hours ? String(hours) + "h " : "") +
            (minutes ? String(minutes) + "m " : "") +
            (String(seconds) + "s")
            ).c_str()};
        }
        else
        {
          return {};
        }

### Input to managet the display backlight
    - platform: homeassistant    
      id: display_sprinkler_backlight
      entity_id: input_number.sprinklerbacklightlevel
      internal: true
      on_value:
        then:
          - output.turn_on: sprinkler_backlight
          - output.set_level:
              id: sprinkler_backlight
              level: !lambda |-
                return atoi(id(display_sprinkler_backlight).state.c_str()) / 100.0;

# Time source config
time:
  - platform: homeassistant
    id: homeassistant_time
    timezone: Europe/Rome
    ```

Anyone can share an automated schedule?
Do you do the automation built in the esphome yaml or do you build it via HA automations? I want it to be on the esphome so if the network is down it wont fail
Thanks

I use HA automations to trigger the schedules via a HA Calender. My understanding is the zone times are stored local to the controller in ESPH, I can trigger a schedule locally via a button if the network is down (or not). Not sure if schedule timing works after the network is down. Mostly the issue here would be power failure on the mains, and that means the controller power is down also.

I use a template sensor to measure the daily mean temperature as a condition to allow trigger of a second schedule that can occur inbetween the main schedule days.

image