Sprinkler automation with ESPHome, a complete project

Cześć,
Can you share the valves you used? Looking for models available in Poland.

Hi Paul,
Thx for the feedback. Glad it’s working.
Please select the post that fixed it / solution.

Thx

The valves were installed a few years ago by a company specializing in irrigation systems. When my controller broke, I made my own and used what was already in the ground. So I’m not really sure what kind of valves they are. I had a Hunter Hydrawise controller.

Thanks, I also used your project, I added a schedule to it at the ESP level, I actually copied it from another project, if anyone wants I’ll be happy to share because I made such a “hybrid”.

1 Like

it will work on any valves, it’s a matter of what you give to the COM of the relay. You have valves for 24VAC, you give 24VAC, you have 24VDC, you give 24VDC… etc.

I will be grateful for sharing

Siema! Of course we want you to share!

Hey there,
I’m using the sprinkler component in ESPHome as well. I’ve organized my 9 valves into 2 main controllers: one with 5 valves (no pump) and another with 4 valves (no pump). So far, everything works fine.

Within each main controller, there’s something like an “interlock,” so only one valve can be active at a time — perfect.

The issue comes when, by mistake, someone manually activates a valve or a full cycle in the other main controller. There’s no “interlock” between the two main controllers.

What I’d like is this:
If main controller 1 is active, and something on main controller 2 is turned on (a valve or the full cycle), then:
Pause main controller 1
Let main controller 2 run
Once everything on main controller 2 turns off, resume main controller 1

And the same the other way around.

I tried using the on_turn_on and on_turn_off triggers on the main controllers, but I’m getting this error:

ERROR Circular dependency detected! Please run with -v option to see what functions failed to complete.

Do you have any ideas on how to achieve this behavior?

Thank you so much!

EDIT: this is my code for the sprinkler

sprinkler:
  - id: irrigatori_giardino
    main_switch:
      name: "Irrigatori"
      id: "irrigatori_main"
      icon: mdi:sprinkler
      on_turn_on:
        - light.turn_on: led_blue
      on_turn_off:
        - light.turn_off: led_blue
    auto_advance_switch:
      name: "Attiva Ciclo Irrigatori"
      icon: mdi:fast-forward
    multiplier_number: 
      name: "Moltiplica Durata Irrigatori"
      min_value: 0.25
      max_value: 2
      step: 0.25
    # repeat_number: 
    #   name: "Ripetizioni Ciclo Irrigatori"
    #   min_value: 0
    #   max_value: 2
    #   step: 1
    #   icon: mdi:repeat-variant
    next_prev_ignore_disabled: True
    valves:
      - valve_switch:
          name: "Scale"
          icon: mdi:sprinkler
        enable_switch:
          name: "Abilita Scale"
          icon: mdi:water-check
        run_duration_number: 
          name: "Minuti Scale"
          unit_of_measurement: min
          min_value: 10
          step: 10
          icon: mdi:wrench-clock
        valve_switch_id: valve8
      - valve_switch: 
         name: "Portici Dietro"
         icon: mdi:sprinkler
        enable_switch:
          name: "Abilita Portici Dietro"
          icon: mdi:water-check
        run_duration_number: 
          name: "Minuti Portici Dietro"
          unit_of_measurement: min
          min_value: 10
          step: 10
          icon: mdi:wrench-clock
        valve_switch_id: valve4
      - valve_switch: 
          name: "Portici Davanti"
          icon: mdi:sprinkler
        enable_switch:
          name: "Abilita Portici Davanti"
          icon: mdi:water-check
        run_duration_number: 
          name: "Minuti Portici Davanti"
          unit_of_measurement: min
          min_value: 10
          step: 10
          icon: mdi:wrench-clock
        valve_switch_id: valve3
      - valve_switch: 
          name: "Prato Davanti"
          icon: mdi:sprinkler
        enable_switch: 
          name: "Abilita Prato Davanti"
          icon: mdi:water-check
        run_duration_number: 
          name: "Minuti Prato Davanti"
          unit_of_measurement: min
          min_value: 10
          step: 10
          icon: mdi:wrench-clock
        valve_switch_id: valve2
      - valve_switch: 
          name: "Prato Dietro"
          icon: mdi:sprinkler
        enable_switch: 
          name: "Abilita Prato Dietro"
          icon: mdi:water-check
        run_duration_number: 
          name: "Minuti Prato Dietro"
          unit_of_measurement: min
          min_value: 10
          step: 10
          icon: mdi:wrench-clock
        valve_switch_id: valve1
  - id: goccia_giardino
    main_switch:
      name: "Goccia a Goccia"
      id: "goccia_main"
      on_turn_on:
        - light.turn_on: led_blue
      on_turn_off:
        - light.turn_off: led_blue
      icon: mdi:water
    auto_advance_switch: 
      name: "Attiva Ciclo Goccia a Goccia"
      icon: mdi:fast-forward
    multiplier_number: 
      name: "Moltiplica Durata Goccia a Goccia"
      min_value: 0.25
      max_value: 2
      step: 0.25
    # repeat_number: 
    #   name: "Ripetizioni Ciclo Goccia a Goccia"
    #   min_value: 0
    #   max_value: 2
    #   step: 1
    #   icon: mdi:repeat-variant
    next_prev_ignore_disabled: True
    valves:
      - valve_switch:
          name: "Scalinata SX"
          icon: mdi:water
        enable_switch:
          name: "Abilita Scalinata SX"
          icon: mdi:water-check
        run_duration_number: 
          name: "Minuti Scalinata SX"
          unit_of_measurement: min
          min_value: 10
          step: 10
          icon: mdi:wrench-clock
        valve_switch_id: valve5
      - valve_switch:
          name: "Scalinata DX"
          icon: mdi:water
        enable_switch: 
          name: "Abilita Scalinata DX"
          icon: mdi:water-check
        run_duration_number: 
          name: "Minuti Scalinata DX"
          unit_of_measurement: min
          min_value: 10
          step: 10
          icon: mdi:wrench-clock
        valve_switch_id: valve6
      - valve_switch:
          name: "Orto"
          icon: mdi:water
        enable_switch: 
          name: "Abilita Orto"
          icon: mdi:water-check
        run_duration_number: 
          name: "Minuti Orto"
          unit_of_measurement: min
          min_value: 10
          step: 10
          icon: mdi:wrench-clock
        valve_switch_id: valve7
      - valve_switch:
          name: "Circuito"
          icon: mdi:water
        enable_switch: 
          name: "Abilita Circuito"
          icon: mdi:water-check
        run_duration_number: 
          name: "Minuti Circuito"
          unit_of_measurement: min
          min_value: 10
          step: 10
          icon: mdi:wrench-clock
        valve_switch_id: valve9

Why did you split it up? Is it due to a hardware limitation?

I think your best approach is to have a supervisor outside of the two devices. That is, control the behaviour from HA.

Did you post the code you’re getting the error for? I only see lights turned on and off for the handlers you’ve mentioned.

hey @parautenbach thank you for your reply!

I split it up because the first 5 valves are 1 hour each minimum and it takes the whole night. The second main controller runs the following night.

The code I got the error is:

sprinkler:
  - id: irrigatori_giardino
    main_switch:
      name: "Irrigatori"
      id: "irrigatori_main"
      icon: mdi:sprinkler
      on_turn_on:
        - light.turn_on: led_blue
        - sprinkler.pause: goccia_giardino
      on_turn_off:
        - light.turn_off: led_blue
        - sprinkler.resume: goccia_giardino
.....
  - id: goccia_giardino
    main_switch:
      name: "Goccia a Goccia"
      id: "goccia_main"
      on_turn_on:
        - light.turn_on: led_blue
        - sprinkler.pause: irrigatori_giardino
      on_turn_off:
        - light.turn_off: led_blue
        - sprinkler.resume: irrigatori_giardino
      icon: mdi:water

It is just a call of sprinkler.pause and sprinkler.resume and I don’t really see how they could generate a loop.

I have an input_boolean in Home Assistant linked to esphome and it controls the pause for both the controllers.

  - platform: homeassistant
    id: pause_irrigation
    entity_id: input_boolean.pausa_irrigazione
    on_turn_on:
      then:
        - sprinkler.pause: irrigatori_giardino
        - sprinkler.pause: goccia_giardino
    on_turn_off:
      then:
        - sprinkler.resume: irrigatori_giardino
        - sprinkler.resume: goccia_giardino

if there is nothing to pause/resume, it just doesn’t do anything and the log shows “no valve to resume”, so… I don’t get what it could be the problem.

Anyway, I can of course handle it with Home Assistant, but I would like to understand what is the problem and how to address it.

I’ve glanced at the code. It uses an FSM (finite state machine). You can see that there’s no paused state really as such.

Also note the comment on the pause() function. It’s the same as shutting down, but just saving some additional state, which you can see here.

So, what I think is happening, is that when you invoke sprinkler.pause for the first sprinkler, it triggers the on_turn_off automation for the first sprinkler, which then causes the first to pause the second, but the second will now trigger the first, etc.

The only way to really resolve a tie like this is as I said before: conceptually, you need to control this independently. I would personally not use your alternative of the ESPHome device accessing an HA helper – this seems backwards to me. Just have an automation in HA to monitor the sprinkler states and act accordingly: If the one turns on, turn the other off and you’re done.

Btw, did you try this with the -v option to get more details?

EDIT: One more point. If you want to insist on doing it on the device, you could try to write a lambda that checks if there is a paused valve to break the loop. Search for " …determine if the sprinkler controller is paused and, if so, which valve is paused?" in the docs.

thank you very much for your explanation :slight_smile: it makes a lot of sense now.

I am not that good in coding, actually I never studied it nor tried to learn it in a proper way. I just try to make my things work.

I don’t want to insist on doing it on the device, I thought it was easier to invoce directly the sprinkler.pause inside esphome. I will work on HA. I am very new in esphome, actually this is my first project.

1 Like

so, I ended up with 2 separated switch template to pause the main controllers:

switch:
  - platform: template
    name: "Pausa Irrigatori"
    id: pausa_irrigatori
    optimistic: True
    icon: mdi:play-pause
    turn_on_action:
      - sprinkler.pause: irrigatori_giardino
    turn_off_action:
      - sprinkler.resume: irrigatori_giardino

  - platform: template
    name: "Pausa Goccia a Goccia"
    id: pausa_goccia
    optimistic: True
    icon: mdi:play-pause
    turn_on_action:
      - sprinkler.pause: goccia_giardino
    turn_off_action:
      - sprinkler.resume: goccia_giardino

and a simple automation in home assistant that takes care of the “interlock”:

alias: Interlock irrigazione
description: ""
triggers:
  - trigger: state
    entity_id:
      - switch.4ch_pro_goccia_a_goccia
    to: "on"
    id: goccia_on
  - trigger: state
    entity_id:
      - switch.4ch_pro_goccia_a_goccia
    to: "off"
    id: goccia_off
  - trigger: state
    entity_id:
      - switch.4ch_pro_irrigatori
    to: "on"
    id: irrigatori_on
  - trigger: state
    entity_id:
      - switch.4ch_pro_irrigatori
    to: "off"
    id: irrigatori_off
conditions: []
actions:
  - choose:
      - conditions:
          - condition: trigger
            id:
              - goccia_on
          - condition: state
            entity_id: switch.4ch_pro_irrigatori
            state: "on"
        sequence:
          - action: switch.turn_on
            metadata: {}
            data: {}
            target:
              entity_id: switch.4ch_pro_pausa_irrigatori
  - choose:
      - conditions:
          - condition: trigger
            id:
              - goccia_off
          - condition: state
            entity_id: switch.4ch_pro_pausa_irrigatori
            state: "on"
        sequence:
          - action: switch.turn_off
            metadata: {}
            data: {}
            target:
              entity_id: switch.4ch_pro_pausa_irrigatori
  - choose:
      - conditions:
          - condition: trigger
            id:
              - irrigatori_on
          - condition: state
            entity_id: switch.4ch_pro_goccia_a_goccia
            state: "on"
        sequence:
          - action: switch.turn_on
            metadata: {}
            data: {}
            target:
              entity_id: switch.4ch_pro_pausa_goccia_a_goccia
  - choose:
      - conditions:
          - condition: trigger
            id:
              - irrigatori_off
          - condition: state
            entity_id: switch.4ch_pro_pausa_goccia_a_goccia
            state: "on"
        sequence:
          - action: switch.turn_off
            metadata: {}
            data: {}
            target:
              entity_id: switch.4ch_pro_pausa_goccia_a_goccia
mode: single

thanks for your help :slight_smile:

1 Like

You’re welcome.

You did very well.

I get why you wanted to do it that way, and normally I’d agree it’s best to keep your logic on the device to allow it to work standalone (in this case, without HA). It’s just that this is a tricky scenario.

that was the reason, exactly. But… since the schedules to run the sprinklers are inside home assistant, there is no webserver on the device and no phisical buttons to start the cycles, it is impossible to run the 2 main controllers or any valves if HA in not running. And if HA is running, there is the HA automation to take care of it.

1 Like
# Processing irrigation (board: nodemcuv2; framework: arduino; platform: platformio/[email protected])
# --------------------------------------------------------------------------------
# HARDWARE: ESP8266 80MHz, 80KB RAM, 4MB Flash

# Based on ESPHome Sprinkler Controller - https://esphome.io/components/sprinkler.html
# Change Log
# 2023 01 XX
  # Initial version
  
# 2023 04 09 V03
  # fix run duration to seconds 
# 2023 04 22 V04
  # fix GPIO order to match relay 1- 4
  # added % at the lambda return for progress sensor return value
# 2023 04 25 V05
  # added nodemcu as sensor to display in HA ui
  # added includes for api key en ota password
# 2023 05 07 V06
  # added a NTC temp sensor to  watch the enclosure temperature
# 2023 05 10 V07
  # addjusted settings reference voltage to adjust to actual temp (default 3.3)
# 2023 06 06 V08
  # adjusted settings for valves corresponding to switches
  # added repeat function
# 2024 04 04 V09
  # Corrected the value of KEY ESPHOME_PROJECT_VERSION due to compiling error esphome v2024.3.1
  # initializer-string for 'char [30]' is too long
  # changed 2023 06 06 V08 to 20240404_V09
  # removed the text "Irrigation Controller.,"  
# 2025 03 12 V10
  # breaking change, configure platform explicitly

# Establish Substitutions
substitutions:
### Modify only the following 6 lines.
  zone_1_name: Zraszacze A
  zone_2_name: Zraszacze B
  zone_3_name: Zraszacze C
  zone_4_name: Linia kroplujaca
  software_version: 20240404_V09
  sensor_update_frequency: 1s
  log_level: debug # Enable levels logging https://esphome.io/components/logger.html
  # none, error, warn, info, debug (default), verbose, very_verbose
##############################################
#  DO NOT CHANGE ANYTHING BELOW THIS LINE  ###
##############################################
  zone_1_valve_id: valve_0
  zone_2_valve_id: valve_1
  zone_3_valve_id: valve_2
  zone_4_valve_id: valve_3
  esphome_name: nawadnianie
  esphome_board: esp32dev
  esphome_comment: Czterozaworowy sterownik nawadniania
  esphome_project_name: jaya.Irrigation Controller
  esphome_project_version: $software_version
  devicename: ESP32_sterownik
  upper_devicename: "Podlewanie"
  uom: Min # this overrides the uom in sprinkler -> run_duration 


#Define Project Deatils and ESP Board Type
esphome:
  name: $esphome_name
  friendly_name: ESP32 Nawadnianie
  comment: $esphome_comment
  project:
    name: $esphome_project_name
    version: $esphome_project_version
  on_boot:
    priority: -100
    then:
      # Set default state for Valve Status
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
      # Set multiplier to 60, convert seconds to minutes
      - sprinkler.set_multiplier:
          id: $devicename
          multiplier: 60

esp32:
  board: $esphome_board

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "$esphome_name Fallback Hotspot"
    password: "PhsY6ZYdhDyg"
  
logger:
  level: ${log_level}
  logs:
    text_sensor: WARN
  
ota:
  - platform: esphome
    password: "3a27fcf74514c0454edf0e1110f6bd56"

# Enable Web server.
web_server:
  port: 80

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

###############################################
# Binary Sensor.
###############################################
binary_sensor:
  - platform: homeassistant
    # prevent deep sleep - Needs further investigation on usefullness
    id: prevent_deep_sleep
    name: "$upper_devicename Prevent Deep Sleep"
    entity_id: input_boolean.prevent_deep_sleep  

###############################################
# Text sensors with general information.
###############################################
text_sensor:
  - platform: version # Expose ESPHome version as sensor.
    name: $upper_devicename ESPHome Version
    hide_timestamp: false
  - platform: wifi_info
    ip_address:
      name: "$upper_devicename IP"
    ssid:
      name: "$upper_devicename SSID"
    bssid:
      name: "$upper_devicename BSSID"
  
# Expose Time Remaining as a sensor.
  - platform: template
    id: time_remaining
    name: $upper_devicename Pozostały Czas
    update_interval: $sensor_update_frequency
    icon: "mdi:timer-sand"
    lambda: |-
      int seconds = round(id($devicename).time_remaining_active_valve().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()};

  # Expose Progress Percent as a sensor.
  - platform: template
    id: progress_percent
    name: $upper_devicename Postęp %
    update_interval: $sensor_update_frequency
    icon: "mdi:progress-clock"
    lambda: |-
      int progress_percent = round(((id($devicename).valve_run_duration_adjusted(id($devicename).active_valve().value_or(0)) - id($devicename).time_remaining_active_valve().value_or(0)) * 100 / id($devicename).valve_run_duration_adjusted(id($devicename).active_valve().value_or(0))));
      std::string progress_percent_as_string = std::to_string(progress_percent);
      return progress_percent_as_string +"%";

  # Expose Valve Status as a sensor.
  - platform: template
    id: valve_status
    name: $upper_devicename Status
    update_interval: never
    icon: "mdi:information-variant"

  - platform: template # Expose the board type as a sensor
    id: espboard_type
    icon: "mdi:developer-board"
    entity_category: "diagnostic"
    name: $upper_devicename ESPBoard
    lambda: |-
      return to_string("${esphome_board}");

sensor:
  - platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
    name: "WiFi Signal dB"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"
  - platform: copy # Reports the WiFi signal strength in %
    source_id: wifi_signal_db
    name: "WiFi Signal Percent"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "%"
    entity_category: "diagnostic"
  - platform: uptime
    name: Uptime
    entity_category: "diagnostic"

###############################################
# Configuration to set multiplier via number 
############################################### 
number:
  - platform: template
    id: $zone_1_valve_id
    name: $zone_1_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(0);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 0
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_2_valve_id
    name: $zone_2_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(1);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 1
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_3_valve_id
    name: $zone_3_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(2);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 2
          run_duration: !lambda 'return x;'
  - platform: template
    id: $zone_4_valve_id
    name: $zone_4_name
    min_value: 1
    max_value: 60
    step: 1
    unit_of_measurement: $uom
    icon: "mdi:timer-outline"
    mode: box # Defines how the number should be displayed in the UI
    lambda: "return id($devicename).valve_run_duration(3);"
    set_action:
      - sprinkler.set_valve_run_duration:
          id: $devicename
          valve_number: 3
          run_duration: !lambda 'return x;'
  - platform: template
    id: sprinkler_ctrlr_repeat_cycles
    name: "Sprinkler Repeat Cycles"
    min_value: 0
    max_value: 4
    step: 1
    mode: box
    icon: "mdi:water-sync"
    lambda: "return id($devicename).repeat();"
    set_action:
      - sprinkler.set_repeat:
          id: $devicename
          repeat: !lambda 'return x;'

###############################################
# Main Sprinkler Controller
###############################################
sprinkler:
  - id: $devicename
    main_switch:
      name: "Start/Stop/Resume"
      id: main_switch
    auto_advance_switch: "Auto Advance"
    valve_open_delay: 2s
    repeat_number: "Repeat"
    valves:
      - valve_switch: $zone_1_name
        enable_switch: Enable $zone_1_name
        run_duration: 15s
        valve_switch_id: ${devicename}_1
      - valve_switch: $zone_2_name
        enable_switch: Enable $zone_2_name
        run_duration: 15s
        valve_switch_id: ${devicename}_2
      - valve_switch: $zone_3_name
        enable_switch: Enable $zone_3_name
        run_duration: 10s
        valve_switch_id: ${devicename}_3
      - valve_switch: $zone_4_name
        enable_switch: Enable $zone_4_name
        run_duration: 10s
        valve_switch_id: ${devicename}_4
  
button:
  - platform: template
    id: sprinkler_pause
    name: "Pause"
    icon: "mdi:pause"
    on_press:
      then:
        - text_sensor.template.publish:
            id: valve_status
            state: "Paused"
        - sprinkler.pause: $devicename

####################################################
# Switch Control to restart the irrigation system.   
####################################################
switch:
   # day of week toggle switches
  - platform: template
    id: ${devicename}_sunday
    name:  Sunday
    icon: "mdi:calendar-range"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
  - platform: template
    id: ${devicename}_monday
    name:  Monday
    icon: "mdi:calendar-range"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
  - platform: template
    id: ${devicename}_tuesday
    name:  Tuesday
    icon: "mdi:calendar-range"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
  - platform: template
    id: ${devicename}_wednesday
    name:  Wednesday
    icon: "mdi:calendar-range"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
  - platform: template
    id: ${devicename}_thursday
    name:  Thursday
    icon: "mdi:calendar-range"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
  - platform: template
    id: ${devicename}_friday
    name:  Friday
    icon: "mdi:calendar-range"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
  - platform: template
    id: ${devicename}_saturday
    name:  Saturday
    icon: "mdi:calendar-range"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
    
  - platform: restart
    name: "Restart $upper_devicename"
    

  # scheduled time enable switches
  - platform: template
    id: ${devicename}_schedule1_enabled
    name: Enable Schedule 1
    icon: "mdi:clock-outline"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
  - platform: template
    id: ${devicename}_schedule2_enabled
    name: Enable Schedule 2
    icon: "mdi:clock-outline"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
  - platform: template
    id: ${devicename}_schedule3_enabled
    name: Enable Schedule 3
    icon: "mdi:clock-outline"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
  
  # rain delay switches (can be cancelled, auto-resetting)
  - platform: template
    id: ${devicename}_raindelay_24h_enabled
    name: Enable 24h Rain Delay
    icon: "mdi:weather-cloudy-clock"
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true
    turn_on_action:
      then:
        - delay: 
            hours: 24
        - switch.turn_off: ${devicename}_raindelay_24h_enabled
  - platform: template
    id: ${devicename}_raindelay_48h_enabled
    name: Enable 48h Rain Delay
    icon: "mdi:weather-cloudy-clock"
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true
    turn_on_action:
      then:
        - delay: 
            hours: 48
        - switch.turn_off: ${devicename}_raindelay_48h_enabled





####################################################
# Hidden I/O  Switches to control irrigation valve relays
####################################################
  - platform: gpio
    name: Relay Board Pin IN1
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_1
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status
          state: "$zone_1_name Aktywne"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Brak"
    pin: GPIO16 # D5

  - platform: gpio
    name: Relay Board Pin IN2
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_2
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status
          state: "$zone_2_name Aktywne"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Brak"
    pin: GPIO17 # D6

  - platform: gpio
    name: Relay Board Pin IN3
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_3
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status
          state: "$zone_3_name Aktywne"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Brak"
    pin: GPIO18 # D7
  
  - platform: gpio
    name: Relay Board Pin IN4
    restore_mode: RESTORE_DEFAULT_OFF # Prevents GPIO pin from going high during boot
    internal: true # Prevents GPIO switch NAME from showing up in Home Assistant
    id: ${devicename}_4
    on_turn_on:
      - text_sensor.template.publish:
          id: valve_status
          state: "$zone_4_name Aktywne"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Brak"
    pin: GPIO19 # D8
  

datetime:
  - platform: template
    name: Schedule 1 Start Time
    entity_category: config
    id: ${devicename}_s1t
    type: time
    optimistic: true
    restore_value: true
  - platform: template
    name: Schedule 2 Start Time
    entity_category: config
    id: ${devicename}_s2t
    type: time
    optimistic: true
    restore_value: true
  - platform: template
    name: Schedule 3 Start Time
    entity_category: config
    id: ${devicename}_s3t
    type: time
    optimistic: true
    restore_value: true



# set up time and check every minute (or second) for scheduled actions
time:
  - platform: homeassistant
    id: ha_time
    on_time:
      # Every 1 minute
      - seconds: 0
        minutes: /1
        then:
          - script.execute: ${devicename}_script1
    on_time_sync:
      then:
        - logger.log: "Synchronized system clock"

script:
    # check what day of the week it is currently, and if we have the schedule enabled today
    - id: ${devicename}_script1 
      then:
        lambda: |-
          int dow = id(ha_time).now().day_of_week;
          if      ((dow == 1 && id(${devicename}_sunday).state == true)
          ||       (dow == 2 && id(${devicename}_monday).state == true)
          ||       (dow == 3 && id(${devicename}_tuesday).state == true)
          ||       (dow == 4 && id(${devicename}_wednesday).state == true)
          ||       (dow == 5 && id(${devicename}_thursday).state == true)
          ||       (dow == 6 && id(${devicename}_friday).state == true)
          ||       (dow == 7 && id(${devicename}_saturday).state == true)) {
            id(${devicename}_script2).execute();
          }
    # if current time is equal to any of the scheduled start times, start the sprinkler cycle
    - id: ${devicename}_script2
      then:
        lambda: |-
          int hour = id(ha_time).now().hour;
          int minute = id(ha_time).now().minute;
          if      ((hour == id(${devicename}_s1t).hour && minute == id(${devicename}_s1t).minute && id(${devicename}_schedule1_enabled).state == true && id(${devicename}_raindelay_24h_enabled).state == false && id(${devicename}_raindelay_48h_enabled).state == false)
          ||      (hour == id(${devicename}_s2t).hour && minute == id(${devicename}_s2t).minute && id(${devicename}_schedule2_enabled).state == true && id(${devicename}_raindelay_24h_enabled).state == false && id(${devicename}_raindelay_48h_enabled).state == false)
          ||      (hour == id(${devicename}_s3t).hour && minute == id(${devicename}_s3t).minute && id(${devicename}_schedule3_enabled).state == true && id(${devicename}_raindelay_24h_enabled).state == false && id(${devicename}_raindelay_48h_enabled).state == false)) {
            id(${devicename}_script3).execute();
          }
    # start the sprinkler cycle
    - id: ${devicename}_script3
      then:
        - sprinkler.start_full_cycle: ${devicename}
        - lambda: ESP_LOGI("main", "Sprinkler cycle has begun!");

Impossible? Absolutely not Impossible. Esphome is made to run independently and only requires HA if that’s how you set it up to be.

Nope, they arent inside HA. There are ways to use HA to create an irrigation schedule just like there’s an option to create a schedule in Esphome. You should really read the documentation and start learning or else you’ll always be new to this.

Why is there no webserver? You set up a web server so it’s there. Do you just not know how to use it? You should probably consider adding some physical buttons and even an LCD screen and you might want a rotary encoder too.

I would also strongly suggest ditching the 2 controller configuration your doing now and put everything under a single controller.

It’s not illegal to split up watering over 2 days of you have to and just water 1 section today, other section tomorrow and just stagger them like that or however you need to even if its beginning of the week and end of the week, it doesnt matter and actually can help with mowing too. Also, you mentioned zones taking all night to finish… You really dont want to be doing any irritating at night. You want the foliage to have enough time to dry before the sun sets or else you greatly increase the risk for getting nasty fungal infections, especially during warm and humid nights, your just asking for trouble! You always want to try doing early morning watering and if you need to water 2x day then your second should be during late afternoon so that you still have a few hours of sunshine to dry out the foliage before dark.

Another thing to consider is adding a flow meter to the water main that feeds your irrigation. This will help you catch leaks as soon as they happen as well as monitor and/or log your water use for irrigation.

Creating or importing a HA sensor that will check for rain or high probability of rain forecasted and also accumulated rain total over past however many days so that you can dial the water back if for example it rained 3" over the last 4 days and you can take necessary steps with your system to adjust it up or down based on relevant factors.

Thank you for your reply. I am repurposing an old Orbit irrigation system that was damaged during a thunderstorm. I had a Sonoff 4CH Pro installed right beneath it, so I created an extension inside the Orbit unit to drive the original triacs that control the 10 valves I have.

Just a few considerations:

  1. I don’t need the webserver, so I didn’t include it.

  2. I don’t need any physical buttons, LCD, or encoder — I had them with the Orbit irrigation system, but I much prefer to control everything from my tablet, phone, or PC.

  3. I will keep the two main controllers — it’s more logical this way. Every irrigation system, including the Orbit, uses “groups” to manage different schedules.

  4. I know I could include the scheduling inside the ESP, but I deliberately chose not to. It’s much easier to create the schedule I need with all the specific conditions I require, such as weather forecasts, humidity, guest presence, and so on.

  5. I cannot run the irrigation during the day/early morning for several reasons, including the fact that guests use the lawn until the evening — and I can’t splash them!

  6. There isn’t enough time in the early morning to complete the full schedule.

  7. The flow meter is already integrated into Home Assistant.

  8. It’s impossible to run the two main controllers at the same time with the current arrangement of my system, and I’m not planning to change it. Only Home Assistant is allowed to run them. Remember: no buttons, no encoder, no internal schedules.

  9. I read the doc :slight_smile:

Yes, I’m aware that ideally the system should be able to run independently in case Home Assistant goes down — but Home Assistant is more critical than the irrigation system, as it handles many other services. So if something happens, I’ll have to restore Home Assistant within a few hours at most, and I’ve made plans to be able to do that. It is just time consuming to realize a system that run by itself if HA doesn’t work.

1 Like

You don’t? What happens if your hardware running HA fails or maybe the hard drive goes out? How do you plan to access your irrigation system if you needed to?

Which is just an alternative to saying “zones” and refers to the group of heads for each valve. An additional controller wouldn’t be a group because It controls 1 or more groups.

That would be perfectly logical if Esphome didn’t have all those capabilities but, it does.

guests are using the lawn at from 3am - 7am when every other business or sports field runs their irrigation? Are “guests” a group of homeless people sleeping in tents on the lawn?

You have to complete the entire cycle in one day? What happens if it starts pouring rain? You gioing to let it keep running because splitting the irrigation job up is unthinkable?

Just 1 flow meter? That sounds really cute!

I’m not sure why you think you must run both of them simultaneously but, since you won’t ever, not even in a million years or if it could save lives, you’re not gonna change it then… OK…

So no backup or manual shutoff option in case of emergency and your not there or unavailable? Sounds like a good idea!

I never forget anything, especially when I hear good ideas!

UUmm… No, It would take roughly the same amount of time to create automations in Esphome as it would in HA plus, you could still change any configuration options in the automations right from the HA UI and it would be a redundant way to have access to the controller but, things never crash or break so, why bother doing all that work!

You really should use your bitter sarcasm elsewhere :slightly_smiling_face: — it doesn’t help to have a proper discussion.

As I wrote, for me it’s more time-consuming to create a fail-safe system inside the ESP. If Home Assistant fails, I have bigger problems than worrying about the irrigation system. Since I would need to restore HA immediately, the irrigation system would be restored at the same time anyway.

I know ESPHome has the ability to handle all these conditions, but I already have them configured from my previous system, so again, it’s easier and faster this way.

3–7 AM is nighttime — that’s when my irrigation system runs.

My guests aren’t homeless — they’re staying at my place for vacation. They often have dinner in the garden or spend the evening/night outside enjoying wine or drinks until late.

And yes, it’s just one flow meter. The system was built decades ago, and it’s not easy or cheap (or even possible) to upgrade everything. I do the best I can with what I have. You shouldn’t judge others without knowing the full context.

So, all in all, it’s a matter of balancing the ideal system, the time required to build it, and the actual likelihood of the worst-case scenario.

You talk about an ideal, fail-proof system. I talk about a good compromise, considering the real situation and my needs.

Thanks for sharing your point of view, by the way.

8 Likes