Sprinkler automation with ESPHome, a complete project

Its time for me to share after all the help I got here and hope it will be useful to someone.
I got inspired by the Sprinkler Controller, available in ESPHome (for me the argument, convincing my wife, building it myself :wink:).
The project is described from beginning to end including the 3d Prints (.stl files) I used for mounting the enclosure and electronics.

I learned a lot along the way and (special thanks to @rcblackwell, who inspired me for the ESPCode using substitutions and changelog). I used his code as a start and changed to my wishes.

####### Update V09, April 2024
Due to a compilation error since esphome v2024.3.1, itā€™s needed to short the ESPHOME_PROJECT_VERSION to max 30 chars.

####### Update V2, May 2023 ############
Edited 23 May: updated to v2 adding NTC to watch internal enclosure temperature and updated schematics, since it contained an error in wiring the power.
Edited 24th May: updated schematics to V2, added 3K3 resistor, so GPIO15 can be used as Output
######################################

Project Introduction

Home Assistant sprinkler / irrigation system controller is able to control four electromagnetic valves. The controller enclosure will be installed indoor (utility room) where as the valves are positioned in the garden. The goal was to have full control with Home Assistant. The system should be able to work fully automatic and have a manual override. The system should be extended in the future with one or more soil moisture sensors as an indicator / starter for an automated run. Without the soil moisture sensors it needs to be started manual. One of the beauties of Home Assistant is that it allows ESPHome devices to connect directly to Home Assistant with the native ESPHome API.

Although the following project is designed for Home Assistant, it should not be hard to run or integrate it at another way.

Approach and requirements

The project should be build with additional following requirements:

  • Reusing components I already had
  • additional components should be easy to purchase and installation (as less as possible single electronics)
  • For human physical indication:
    • if system is powered (green led)
    • system status (red led). More information can be found here.
      • Blink slowly (about every second) when a warning is active
      • Blink quickly (multiple times per second) when an error is active
  • Easy to use for my family members
  • Have the board type available in the frontend (since I use multiple devices)

End result

Components

(for reference)
At Amazon.nl (April 10th 2023): total: ā‚¬ 78,78.

Enclosure

Dimensions / measures

Based on this enclose, measures are taken

Electronics

Schematic

Home assistant

Integration is done with ESPHome and makes use of the

Documentation: https://esphome.io/components/sprinkler.html

https://community.home-assistant.io/t/how-do-i-format-lambda-for-text-sensor-esphome-sprinkler/461135/4

ESPHome YAML configuration

YAML configuration for the nodeMCU or ESP32

# 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.,"  


# Establish Substitutions
substitutions:
### Modify only the following 6 lines.
  zone_1_name: Drip Line A
  zone_2_name: Drip Lane B
  zone_3_name: Sprinkler A
  zone_4_name: Sprinkler B
  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: irrigation
  esphome_platform: ESP8266
  esphome_board: nodemcuv2
  esphome_comment: Four Valve Irrigation Controller
  esphome_project_name: jaya.Irrigation Controller
  esphome_project_version: $software_version
  devicename: irrigation_controller
  upper_devicename: "Irrigation Controller"
  uom: Min # this overrides the uom in sprinkler -> run_duration 


#Define Project Deatils and ESP Board Type
esphome:
  name: $esphome_name
  platform: $esphome_platform
  board: $esphome_board
  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

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: !secret esphome_ap_password
  
logger:
  level: ${log_level}
  logs:
    text_sensor: WARN
  
ota:
  password: !secret esphome_ota_password

# Enable Web server.
web_server:
  port: 80

# Sync time with Home Assistant.
time:
  - platform: homeassistant
    id: homeassistant_time
    
###############################################
# Enable Home Assistant API
###############################################
api:
  encryption:
    key: !secret esphome_api_encrypt_key
  reboot_timeout: 0s

###############################################
# 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: $esphome_name ESPHome Version
    hide_timestamp: false
  - platform: wifi_info
    ip_address:
      name: "$esphome_name IP"
    ssid:
      name: "$esphome_name SSID"
    bssid:
      name: "$esphome_name BSSID"
  
# Expose Time Remaining as a sensor.
  - platform: template
    id: time_remaining
    name: $upper_devicename Time Remaining
    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 Progress %
    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"
    name: $esphome_name ESPBoard
    lambda: |-
      return to_string("${esphome_board}");

# https://esphome.io/devices/nodemcu_esp8266.html
# Enable On-Board Status LED.
status_led:
  pin:
    # Pin D2 / GPIO4
    number: GPIO04
    inverted: false

sensor:
  # Uptime sensor.
  - platform: uptime
    name: $upper_devicename Uptime

  # WiFi Signal sensor.
  - platform: wifi_signal
    name: $upper_devicename WiFi Signal
    update_interval: 60s
  
  # temperature sensor https://esphome.io/components/sensor/ntc.html using  a switch every 1 minute. Pull Upp GPIO16 (3.3V). prevents self-heating NTC
  - platform: ntc
    sensor: resistance_sensor
    name: $upper_devicename Temperature
    calibration:
      b_constant: 3950
      reference_temperature: 25Ā°C
      reference_resistance: 10kOhm
  - platform: resistance
    id: resistance_sensor
    sensor: source_sensor
    configuration: DOWNSTREAM
    resistor: 10kOhm
    name: Resistance Sensor
    reference_voltage: 3.1V
  - platform: adc
    pin: A0
    id: source_sensor
    # Added:
    update_interval: never
    filters:
      - multiply: 3.3

interval:
  - interval: 60s
    then:
      - switch.turn_on: ntc_vcc
      - component.update: source_sensor
      - switch.turn_off: ntc_vcc
      - logger.log: "Measure Temp"

###############################################
# 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:
  - platform: restart
    name: "Restart $devicename"

# Switch for the NTC https://esphome.io/components/sensor/ntc.html, Prevent self heating by switching the voltage on a GPIO
  - platform: gpio
    pin: 
      number: GPIO5 # Pin D1
      inverted: false
    id: ntc_vcc

####################################################
# 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 Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin: GPIO14 # D5
    inverted: true
  - 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 Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin: GPIO12 # D6
    inverted: true
  - 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 Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin: GPIO13 # D7
    inverted: true
  - 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 Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin: GPIO15 # D8
    inverted: true

Print parts

Base plate and Wall mount

For easy mount and dismount the enclosure, a baseplate, two frame pieces and bracket can be used. (this saves the trouble of unscrewing the entire box).
Base_platewall_mount_part_2-2wall_mount_part_1-2Wall_mount_bracket

  • If you want the .stl files, please sent me a pm. (Itā€™s not possible to attach here)

Future additional Features

Next step is to build a soil moisture based on an ESP device that can live on battery and placed in the garden. Things like deep sleepā€¦

16 Likes

@Alaric , thank you for sharing your project. The detail provided will be helpful to anyone wishing to build out the ESP Sprinkler. Your code, project box layout and drawings are appreciated.

Regards

Hi,

Thanks for this project it works great!

Iā€™ve just started using ESPHome so I would like to have a question. Would it be possible to save the running time so it would remember it even after a restart? Or itā€™s easier just to use an automation to set it after a restart?

Also, for me setting the running time is a little bit slow, sometimes it saves it sometimes not both on the device and Lovelace page. Have you experienced it?

If anyone interested Iā€™ve added soil moisture and connected a rain sensor (Rainbird if I remember correctly) to my ESP32:

Capacitive Moisture Sensor V2:

sensor:
  - platform: adc
    pin: GPIO32
    name: "Soil Moisture"
    update_interval: 15s
    unit_of_measurement: "%"
    attenuation: 11db
    filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 1
# It needs calibration. With a multimeter measure voltage on GND and AUOT when dry and fully wet.
    - calibrate_linear:
        - 2.50 -> 0.00
        - 1.20 -> 100.00
    - lambda: |
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);

Rain sensor connected to GND and GPIO25

binary_sensor:
  - platform: gpio
    pin:
      number: 25
      inverted: false
      mode:
        input: true
        pullup: true
    name: "Rain Sensor"

Thanks

1 Like

Hi @Csupi003 and welcome to the forum.
Thanks for commenting the project and feedback. Appreciated :slight_smile: Also you addition is welcome. Itā€™s on my todo list, building an outdoor soil moisture sensor and use it as a binary to start the sprinkler sequence.
Please note that I updated the schematics and added a NTC for watching the internal enclosure temp.

Regarding your questions:

  • I think that putting effort in remembering values after a restart is not needed and continue the countdown. If it restart (for some reason), it will be in rare cases. For that reason I think its fine to restart the sequence all over. Iā€™m pretty sure that the answer youā€™re looking for can be found on the forum.
  • Slider delay: I donā€™t experience any delay when adjusting the slider, so Iā€™m not sure why you experience this phenomenon.

@Alaric, I plan to make your project next weeks, what kind of valves do you use?

Hi @beresb
It does not matter what brand of valves you choose. From the design these are valves that are 24VAC. (Think thatā€™s pretty standardā€¦)
Good luck and fun building.

Thank you very much for this post, itā€™s very helpful.

I have a few dozen esphome devices in operation, but most of them have been configured with cut-n-paste from other people. Much like Iā€™ve done with yours here :wink:

My use case is to use this controller to operate few regular valves in my garden, but more importantly to operate the mist system in my softwood propagation bed. For this reason, I need to be able to set the duration of operations in seconds, not minutes.

Iā€™ve tried editing the supplied code, including overriding the unit of measure at the zone level, but when I start a zone it continues to run for the number entry in minutes, e.g. if I set it to run 5 seconds, it actually starts counting down from 5 minutes.

Would there be a simple way to adapt your code to allow for a runtime in the seconds instead of minutes?

Thanks again!

The default uom for sprinkler controller is seconds. Remove or comment out the lines stating the ā€œunit_of_measurement: $uomā€ for each valve. Doing so will cause the default, seconds, to be used.

Hi Alaric,
thanks for sharing this project. I shall give it a try, but I have questions before I start to implement it in my HA.
Adding more zones means simply copy and paste and adapt the relevant passages in the yaml code I expect.
What happens, if one zone is active and the wifi connection gets lost for some time, coming up and gets lost again? How does this influence the behavior of the irrigation cycle?
What is the use of the temperature measurement inside the box?
Do you have a mains fuse somewhere (not to see in your fotos, but is recommended)?

Thatā€™s not my experience here. I tried just commenting out the unit_of_measurement: $uom in each valve, but nothing changed. I confirmed on the ESPhome site that seconds is indeed the default for duration. As another attempt, I changed the uom variable to be s, uncommented the directive in each valve, and Iā€™m still seeing everything run as minutes:
From the web view:

You can see that I set the duration for drip line A to be 15 seconds and turned it on. At the time of the screenshot, there is 5m 16s remaining, having completed 64% of the time.

Iā€™m guessing that thereā€™s something happening in one or more of the calculations, and I could probably figure it out given enough time, but I thought there might be more users that would want this functionality and maybe it could be made even more easily configurable.

Thanks for input!

You may have hit up on something; other code calculations.

If youā€™re using the code from the first post in this thread, I see where the problem is. In the boot section thereā€™s a calculation that converts seconds to minutes. Thereā€™s a remark indicating such. If you comment out those lines and the lines I mentioned previously, the controller should start working in seconds.

Post your FULL code so we may have a look at it.

Sure thing, here it is. Only thing changed was the board info, as Iā€™ll be using an ESP32 variant for this project.

Thanks for taking a look!

# 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)

# TLH  Changed the board info to fit
esp32:
  board: esp32dev
  framework:
    type: arduino



# Establish Substitutions
substitutions:
### Modify only the following 6 lines.
  zone_1_name: Sprinkler A
  zone_2_name: Sprinkler B
  zone_3_name: Drip Line A
  zone_4_name: Drip Lane B
  software_version: 2023 05 10 V07
  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: irrigation
  esphome_platform: ESP8266
  esphome_board: nodemcuv2
  esphome_comment: Four Valve Irrigation Control
  esphome_project_name: JAYA.Four Valve Irrigation Control
  esphome_project_version: Irrigation Controller, $software_version
  devicename: irrigation_controller
  upper_devicename: "Irrigation Controller"
  uom: s # this overrides the uom in sprinkler -> run_duration 


#Define Project Deatils and ESP Board Type
esphome:
  name: $esphome_name
  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

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: !secret esphome_ap_password
  
logger:
  level: ${log_level}
  logs:
    text_sensor: WARN
  
ota:


# Enable Web server.
web_server:
  port: 80

# Sync time with Home Assistant.
time:
  - platform: homeassistant
    id: homeassistant_time
    
###############################################
# Enable Home Assistant API
###############################################
api:

###############################################
# 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: $esphome_name ESPHome Version
    hide_timestamp: false
  - platform: wifi_info
    ip_address:
      name: "$esphome_name IP"
    ssid:
      name: "$esphome_name SSID"
    bssid:
      name: "$esphome_name BSSID"
  
# Expose Time Remaining as a sensor.
  - platform: template
    id: time_remaining
    name: $upper_devicename Time Remaining
    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 Progress %
    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"
    name: $esphome_name ESPBoard
    lambda: |-
      return to_string("${esphome_board}");

# https://esphome.io/devices/nodemcu_esp8266.html
# Enable On-Board Status LED.
status_led:
  pin:
    # Pin D2 / GPIO4
    number: GPIO04
    inverted: false

sensor:
  # Uptime sensor.
  - platform: uptime
    name: $upper_devicename Uptime

  # WiFi Signal sensor.
  - platform: wifi_signal
    name: $upper_devicename WiFi Signal
    update_interval: 60s

  # temperature sensor https://esphome.io/components/sensor/ntc.html using  a switch every 1 minute. Pull Upp GPIO16 (3.3V). prevents self-heating NTC
  - platform: ntc
    sensor: resistance_sensor
    name: $upper_devicename Temperature
    calibration:
      b_constant: 3950
      reference_temperature: 25Ā°C
      reference_resistance: 10kOhm
  - platform: resistance
    id: resistance_sensor
    sensor: source_sensor
    configuration: DOWNSTREAM
    resistor: 10kOhm
    name: Resistance Sensor
    reference_voltage: 3.1V
  - platform: adc
    pin: A0
    id: source_sensor
    # Added:
    update_interval: never
    filters:
      - multiply: 3.3

interval:
  - interval: 60s
    then:
      - switch.turn_on: ntc_vcc
      - component.update: source_sensor
      - switch.turn_off: ntc_vcc
      - logger.log: "Measure Temp"

###############################################
# 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;'

###############################################
# Main Sprinkler Controller
###############################################
sprinkler:
  - id: $devicename
    main_switch:
      name: "Start/Stop/Resume"
      id: main_switch
    auto_advance_switch: "Auto Advance"
    valve_open_delay: 2s
    valves:
      - valve_switch: $zone_1_name
        enable_switch: Enable $zone_1_name
        run_duration: 20s
        valve_switch_id: ${devicename}_1
      - valve_switch: $zone_2_name
        enable_switch: Enable $zone_2_name
        run_duration: 20s
        valve_switch_id: ${devicename}_2
      - valve_switch: $zone_3_name
        enable_switch: Enable $zone_3_name
        run_duration: 15s
        valve_switch_id: ${devicename}_3
      - valve_switch: $zone_4_name
        enable_switch: Enable $zone_4_name
        run_duration: 15s
        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:
  - platform: restart
    name: "Restart $devicename"

# Switch for the NTC https://esphome.io/components/sensor/ntc.html, Prevent self heating by switching the voltage on a GPIO
  - platform: gpio
    pin: 
      number: GPIO5 #GPIO16 # Pin D0
      inverted: false
    id: ntc_vcc

####################################################
# 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 Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin: GPIO14 # D5
    inverted: true
  - 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 Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin: GPIO12 # D6
    inverted: true
  - 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 Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin: GPIO13 # D7
    inverted: true
  - 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 Active"
    on_turn_off:
      - text_sensor.template.publish:
          id: valve_status
          state: "Idle"
    pin: GPIO15 # D8
    inverted: true

EDIT: Had already posted when I saw your edit about the multiplier. That was the key, thanks. Changed the on boot code to be:

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: 1

Left the unit of measure stuff commented out, and increased the max_value of each of the valve duration numbers to be 3600 to allow for up to an hour, and it all works as expected.

Thanks for your eyes and pointing me in the right direction!

1 Like

Glad I could help.

does anyone know how to convert this code to only use one slider for all valves? or a master slider?

You can try to do this like I did, set the valve run_duration to fixed value (60s) and use multiplier as slider. This way you can control all valves in one go.

sprinkler:
  - id: lawn_sprinkler_ctrlr
    main_switch: "Lawn Sprinklers"
    auto_advance_switch: "Lawn Sprinklers Auto Advance"
    multiplier_number:
      id: lawn_sprinkler_multiplier
      name: "Lawn Sprinkler Multiplier"
      initial_value: 15
      min_value: 5
      max_value: 60
      step: 5
    valve_overlap: 20s
    valves:
      - valve_switch: "Left Side"
        enable_switch: "Enable Left Side"
        run_duration: 60s
        valve_switch_id: lawn_sprinkler_valve_1
      - valve_switch: "Right Side"
        enable_switch: "Enable Right Side"
        run_duration: 60s
        valve_switch_id: lawn_sprinkler_valve_2

Hi,
Sorry for late response (just returned from annual holidays and work)

Iā€™m not sure what will happen exactly (since I just didnā€™t test it). I assume, that timing / logic will happen within the ESP, so it does not really matter for the working. However the HA gui will mis information. Maybe @rcblackwell knows more about this?

I added this sensor for two reasons: 1. utility room temp can be quit high, so I just wanted to know if temp inside the box is within Limits (take appr measurements if not). 2. I was just curious if this type of sensor would work (never had temp sensor and I wanted the experience)

You are correct, Its not in there yet, but should be definitely and its on my ToDO list :wink:

Hope this help. Good luck with the projects.

Love your work here, thanks for sharing.
Few questions about behavior:

  1. After flashing the timer automatically back to 20 min, is there a way to keep my preferred values during flash instead if the need to remember to set them up back every update?
  2. The daily start or weekly schedule, is designed to be controller from HA or something, right?
    so I just set auto advance and press the start ?
  3. Why when I do something manually, the auto advance is automatically disabled?

Awesome project! I think I might build this over the winter. Are there any plans to make it controllable from the panel? (My use case would be once a year the ā€œsprinkler guyā€ drives around with his huge compressor in tow and blows out all the lines before winter.) He does somethingā€¦ not sure what though!

I donā€™t know for sure however the following, quoted from the ESPHome Automations and Templates page of ESPHome documentation indicates it would;

Do Automations Work Without a Network Connection[(Automations and Templates ā€” ESPHome)

YES! All automations you define in ESPHome are executed on the ESP itself and will continue to work even if the WiFi network is down or the MQTT server is not reachable.

There is one caveat though: ESPHome automatically reboots if no connection to the MQTT broker can be made. This is because the ESPs typically have issues in their network stacks that require a reboot to fix. You can adjust this behavior (or even disable automatic rebooting) using the reboot_timeout option in the wifi component and mqtt component. (Beware that effectively disables the reboot watchdog, so you will need to power cycle the device if it fails to connect to the network without a reboot)

HI Alaric,
Thanks for the work on this project, Iā€™ve implemented it and its currently in use.
I was wondering if there is an automation that will allow the automatic adjustment of run times using the zone times calculated by the HACS Smart Irrigation integration?