Garden Irrigation

ok all, I have now got a much nicer interface and working watering day setup. In terms of what I want the end solution to be, its only missing the rain / weather conditions which I will sort out in the near future. I have to say a big thanks to @petro and @anon43302295 for their work helping me get the watering day side of things working.

again, this is in a package called reticulation.yaml

########################################
#
#   This is a full reticulation
#   control package using DarkSky
#   weather prediction to 
#   prevent watering if rain 
#   is expected, or occured recently
#   with local moisture sensor data
#   as well
#
########################################

#   Weather prediction setup
sensor:
  - platform: darksky
    api_key: !secret darksky_api_key
    name: Reticulation Weather
    forecast:
      - 1
    update_interval: '00:30:00'
    monitored_conditions:
      - precip_intensity
      - precip_probability
      - precip_intensity_max
#      - summary
#      - icon
#      - temperature
#      - apparent_temperature
#      - wind_speed
#      - wind_bearing
#      - cloud_cover
#      - humidity
#      - hourly_summary
#      - daily_summary
#      - temperature_max
#      - temperature_min
#      - apparent_temperature_max

  - platform: template    # determine if today is selected as a watering day for program 1
    sensors:
      retic_program1_watering_day:
        value_template:  >
          {% set sensor_names = [ 'monday', 'tuesday', 'wednesday','thursday','friday','saturday','sunday'] %}
          {% set today_name = sensor_names[now().weekday()] %}
          {% set entity_id = 'input_boolean.retic_program1_'+today_name %}
          {{ is_state(entity_id, 'on') }}

      retic_program2_watering_day:
        value_template:  >
          {% set sensor_names = [ 'monday', 'tuesday', 'wednesday','thursday','friday','saturday','sunday'] %}
          {% set today_name = sensor_names[now().weekday()] %}
          {% set entity_id = 'input_boolean.retic_program2_'+today_name %}
          {{ is_state(entity_id, 'on') }}

#   Calculations for rain to stop reticulation

# to be completed later












#   Reticulation control panel setup
    
input_datetime:
  retic_program1_start_time:    # program start time control
    name: Program 1 Start Time
    has_date: false
    has_time: true
    initial: '04:00'

  retic_program2_start_time:
    name: Program 2 Start Time
    has_date: false
    has_time: true
    initial: '18:00'


input_number:
  retic_program1_station1_run_time:   # station run time control
    name: Station 1 Run Time
    initial: 10
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer

  retic_program1_station2_run_time:
    name: Station 2 Run Time
    initial: 10
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer

  retic_program2_station1_run_time:
    name: Station 1 Run Time
    initial: 10
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer

  retic_program2_station2_run_time:
    name: Station 2 Run Time
    initial: 10
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer  

input_boolean:
  retic_program1_monday:   # watering day selections for program 1
    name: Monday

  retic_program1_tuesday:
    name: Tuesday

  retic_program1_wednesday:
    name: Wednesday

  retic_program1_thursday:
    name: Thursday
    
  retic_program1_friday:
    name: Friday

  retic_program1_saturday:
    name: Saturday
    
  retic_program1_sunday:
    name: Sunday

  retic_program2_monday:   # watering day selections for program 2
    name: Monday

  retic_program2_tuesday:
    name: Tuesday

  retic_program2_wednesday:
    name: Wednesday

  retic_program2_thursday:
    name: Thursday
    
  retic_program2_friday:
    name: Friday

  retic_program2_saturday:
    name: Saturday
    
  retic_program2_sunday:
    name: Sunday

  retic_station1_manual_run:
    name: Station 1 ON Manual
    icon: mdi:arrow-right-drop-circle

  retic_station2_manual_run:
    name: Station 2 ON Manual
    icon: mdi:arrow-right-drop-circle

  retic_program1_enable:
    name: Enable
    
  retic_program2_enable:
    name: Enable


group:                  # frontend interface setup
  retic_manual:
    control: hidden
    name: 'Manual Control'
    entities:
      - input_boolean.retic_station1_manual_run
      - input_boolean.retic_station2_manual_run
  
  retic_program1:
    control: hidden
    name: 'Program 1'
    entities:
      - input_boolean.retic_program1_enable
      - script.retic_program1_run
      - input_datetime.retic_program1_start_time
      - group.retic_program1_watering_days
      - group.retic_program1_run_times
    
  retic_program2:
    control: hidden
    name: 'Program 2'
    entities:
      - input_boolean.retic_program2_enable
      - script.retic_program2_run
      - input_datetime.retic_program2_start_time
      - group.retic_program2_watering_days
      - group.retic_program2_run_times

  retic_program1_watering_days:
    control: hidden
    name: 'Watering days'
    icon: mdi:calendar-multiple-check
    entities:
      - input_boolean.retic_program1_monday
      - input_boolean.retic_program1_tuesday
      - input_boolean.retic_program1_wednesday
      - input_boolean.retic_program1_thursday
      - input_boolean.retic_program1_friday
      - input_boolean.retic_program1_saturday
      - input_boolean.retic_program1_sunday

  retic_program2_watering_days:
    control: hidden
    name: 'Watering days'
    icon: mdi:calendar-multiple-check
    entities:
      - input_boolean.retic_program2_monday
      - input_boolean.retic_program2_tuesday
      - input_boolean.retic_program2_wednesday
      - input_boolean.retic_program2_thursday
      - input_boolean.retic_program2_friday
      - input_boolean.retic_program2_saturday
      - input_boolean.retic_program2_sunday

  retic_program1_run_times:
    control: hidden
    name: 'Run times'
    icon: mdi:camera-timer
    entities:
      - input_number.retic_program1_station1_run_time
      - input_number.retic_program1_station2_run_time

  retic_program2_run_times:
    control: hidden
    name: 'Run times'
    icon: mdi:camera-timer
    entities:
      - input_number.retic_program2_station1_run_time
      - input_number.retic_program2_station2_run_time
 
      
switch:                      # Solenoid control (will be signals to NodeMCU)
  - platform: mqtt
    name: "Retic Station 1 Valve"
    state_topic: "retic/station1/state"
    command_topic: "retic/station1/cmd"
    payload_on: "1"
    payload_off: "0"
    optimistic: false
    qos: 0
    retain: true
    
  - platform: mqtt
    name: "Retic Station 2 Valve"
    state_topic: "retic/station2/state"
    command_topic: "retic/station2/cmd"
    payload_on: "1"
    payload_off: "0"
    optimistic: false
    qos: 0
    retain: true


automation:
  - alias: Reticulation Run Program 1     # start program 1 at designated time if it is enabled and today is selected as a watering day
    initial_state: 'on'
    trigger:
      - platform: template
        value_template: '{{ states.sensor.time.state == (states.input_datetime.retic_program1_start_time.attributes.timestamp | int | timestamp_custom("%H:%M", False)) }}'
    condition:
      condition: and
      conditions:
        - condition: state
          entity_id: input_boolean.retic_program1_enable
          state: 'on'
        - condition: state
          entity_id: sensor.retic_program1_watering_day
          state: 'true'
    action:
      - service: script.turn_on
        entity_id: script.retic_program1_run

  - alias: Reticulation Run Program 2     # start program 2 at designated time if it is enabled and today is selected as a watering day
    initial_state: 'on'
    trigger:
      - platform: template
        value_template: '{{ states.sensor.time.state == (states.input_datetime.retic_program2_start_time.attributes.timestamp | int | timestamp_custom("%H:%M", False)) }}'
    condition:
      condition: and
      conditions:
        - condition: state
          entity_id: input_boolean.retic_program2_enable
          state: 'on'
        - condition: state
          entity_id: sensor.retic_program2_watering_day
          state: 'true'
    action:
      - service: script.turn_on
        entity_id: script.retic_program2_run


script:
  retic_program1_run:             #run retic program 1 through each station for selected time
    alias: Program 1 Run
    sequence:
      - service: switch.turn_on
        data:
          entity_id: switch.retic_station_1_valve
      - delay: "00:{{ states('input_number.retic_program1_station1_run_time')|int }}:00"
      - service: switch.turn_off
        data:
          entity_id: switch.retic_station_1_valve
      - service: switch.turn_on
        data:
          entity_id: switch.retic_station_2_valve
      - delay: "00:{{ states('input_number.retic_program1_station2_run_time')|int }}:00"
      - service: switch.turn_off
        data:
          entity_id: switch.retic_station_1_valve
      
  retic_program2_run:             #run retic program 2 through each station for selected time
    alias: Program 2 Run
    sequence:
      - service: switch.turn_on
        data:
          entity_id: switch.retic_station_1_valve
      - delay: "00:{{ states('input_number.retic_program2_station1_run_time')|int }}:00"
      - service: switch.turn_off
        data:
          entity_id: switch.retic_station_1_valve
      - service: switch.turn_on
        data:
          entity_id: switch.retic_station_2_valve
      - delay: "00:{{ states('input_number.retic_program2_station2_run_time')|int }}:00"
      - service: switch.turn_off
        data:
          entity_id: switch.retic_station_1_valve

and this in in groups.yaml for the frontend tab

retic:
  name: Retic
  view: yes
  icon: mdi:barley
  entities:
    - group.retic_program1
    - group.retic_program2
    - group.retic_manual

which should all then give you this interface:

and if you click the watering days section:

and clicking the run times section gives:

You can disable a program if you want (without having to turn off all the watering days). The program run will allow you to force the program to run now, or shows when it is currently running plus allows you to stop it. Manual station control is pretty obvious on the right hand side if you want to force a station for sprinkler tester etc.

It is currently set up for 2 programs and 2 stations but the code can be replicated to as many as you want very easily.

I hope you like it!

11 Likes

Like the way you did the grouping

I have mine pretty much ready now too but I was wondering if there is a limit to the length of code posts?

Mine ended up using a ton of inputs and sensors so is quite long but the whole thing is pretty much a portable package irrespective of how many zones you have so might be useful to others. I also have some rainfall and temperature adjustment calculations that someone might be able to improve on.

try to post it, then we’ll now. nothing to loose :slight_smile:

OK here goes…

My package conists of 3 files:

garden_globals.yaml ---- is all the groups, inputs, sensors, switches etc.,
garden_weather_calculations.yaml ---- does just what it says,
garden_irrigation ---- contains the logic.

I’m pretty new to all this so I won’t vouch for how well it has been implemented but it seems to work, albeit without actually having watered anything yet!

The switches are sonoffs that control four valves (or zones) but I have tried to write it in such a way that there is as little code replication as possible which means that if you want a different number of zones then you more or less have to just add more inputs and the logic largely takes care of it. There are a couple of automations that will also need to be replicated but I think (hope) it should be self explanatory :slight_smile:

The weather calculations are a bit of an experiment and to be honest with hindsight I think that whole idea might be a bit of overkill and I don’t know if we will ever actually use them! We’re not watering a prize winning garden we just don’t want it to die when we are on holiday!

I’d love feedback especially if you think I could have done something better. Like I said, I am still learning this.

Thanks of course go to those who have helped me on this forum and to the posters in this thread, some of whose ideas I have of course used.

It looks like this:

image

And yes, there is a limit to post size so I will have to post the three files in separate posts.

11 Likes

great. can’t wait to see all the files of the package.
including packages is so much more convenient than adding all yaml stuff seperately. it also makes it much easier to try things out and keep all in one place.

Sorry for the delay…

garden_globals.yaml

#===========
#=== Groups
#===========
group:

  irrigation_information:
    control: hidden
    name: Irrigation Information
    entities:
      - group.irrigation_weather_information
      - input_number.temperature_baseline
      - input_number.rainfall_threshold
      - sensor.last_irrigated_time
      - sensor.zone1_time_today
      - sensor.zone2_time_today
      - sensor.zone3_time_today
      - sensor.zone4_time_today

  irrigation_cycle1:
    control: hidden
    name: 'Morning Cycle'
    entities:
      - input_boolean.cycle1_enable
      - sensor.cycle1_next_run_time
      - input_boolean.cycle1_manual_run
      - input_select.cycle1_schedule_time
      - input_select.cycle1_watering_days
      - input_number.cycle1_zone1_duration
      - input_number.cycle1_zone2_duration
      - input_number.cycle1_zone3_duration
      - input_number.cycle1_zone4_duration

  irrigation_cycle1_status:
    control: hidden
    name: 'Morning Cycle Status'
    entities:
      - sensor.cycle1_running
      - sensor.cycle1_zone_being_watered
      - timer.cycle1_zone_duration
      - group.cycle1_weather_settings

  irrigation_cycle2:
    control: hidden
    name: 'Afternoon Cycle'
    entities:
      - input_boolean.cycle2_enable
      - sensor.cycle2_next_run_time
      - input_boolean.cycle2_manual_run
      - input_select.cycle2_schedule_time
      - input_select.cycle2_watering_days
      - input_number.cycle2_zone1_duration
      - input_number.cycle2_zone2_duration
      - input_number.cycle2_zone3_duration
      - input_number.cycle2_zone4_duration
           
  irrigation_cycle2_status:
    control: hidden
    name: 'Afternoon Cycle Status'
    entities:
      - sensor.cycle2_running
      - sensor.cycle2_zone_being_watered
      - timer.cycle2_zone_duration
      - group.cycle2_weather_settings

  irrigation_valves:
    name: 'Valves'
    entities:
      - switch.zone1_valve
      - switch.zone2_valve
      - switch.zone3_valve
      - switch.zone4_valve

  cycle1_weather_settings:
    control: hidden
    name: Morning cycle weather settings
    icon: mdi:weather-partlycloudy
    entities:
    - input_boolean.cycle1_use_weather_adjustment
    - sensor.cycle1_zone1_duration_adjusted
    - sensor.cycle1_zone2_duration_adjusted
    - sensor.cycle1_zone3_duration_adjusted
    - sensor.cycle1_zone4_duration_adjusted

  cycle2_weather_settings:
    control: hidden
    name: Afternoon cycle weather settings
    icon: mdi:weather-partlycloudy
    entities:
    - input_boolean.cycle2_use_weather_adjustment
    - sensor.cycle2_zone1_duration_adjusted
    - sensor.cycle2_zone2_duration_adjusted
    - sensor.cycle2_zone3_duration_adjusted
    - sensor.cycle2_zone4_duration_adjusted

  irrigation_weather_information:
  # Uses the Weather Underground weather sensor
    name: Weather Information History
    icon: mdi:weather-partlycloudy
#    icon: mdi:settings
    entities:
    - sensor.temp_high_2days
    - sensor.temp_high_5days

    - sensor.temp_minus0
    - sensor.temp_minus1
    - sensor.temp_minus2
    - sensor.temp_minus3
    - sensor.temp_minus4

    - sensor.rain_3days_ratio

    - sensor.rain_minus0
    - sensor.rain_minus1
    - sensor.rain_minus2
    - sensor.rain_minus3
    
#================
#=== Input_Texts
#================
input_text:
  cycle1_current_zone:
    name: Cycle 1 current zone

  cycle2_current_zone:
    name: Cycle 2 current zone


#==================
#=== Input_Selects
#==================
input_select:

  cycle1_watering_days:
    name: Morning cycle watering days
    options:
      - 'Daily'
      - 'Alternate'
    icon: mdi:calendar

  cycle2_watering_days:
    name: Afternoon cycle watering days
    options:
      - 'Daily'
      - 'Alternate'
    icon: mdi:calendar

  cycle1_schedule_time:
    name: Morning cycle schedule start time
    options:
      - '08:00'
      - '09:00'
      - '10:00'
      - '11:00'
      - '12:00'
    icon: mdi:alarm

  cycle2_schedule_time:
    name: Afternoon cycle schedule start time
    options:
      - '16:00'
      - '17:00'
      - '18:00'
      - '19:00'
      - '20:00'
    icon: mdi:alarm


#==================
#=== Input_Numbers
#==================
input_number:

  # CYCLE 1
  cycle1_zone1_duration:
    name: Zone 1 duration
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer

  cycle1_zone2_duration:
    name: Zone 2 duration
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer

  cycle1_zone3_duration:
    name: Zone 3 duration
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer

  cycle1_zone4_duration:
    name: Zone 4 duration
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer

  # CYCLE 2
  cycle2_zone1_duration:
    name: Zone 1 duration
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer

  cycle2_zone2_duration:
    name: Zone 2 duration
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer  

  cycle2_zone3_duration:
    name: Zone 3 duration
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer

  cycle2_zone4_duration:
    name: Zone 4 duration
    min: 0
    max: 30
    step: 1
    icon: mdi:camera-timer  

  # The temperature above or below which the duration is adjusted
  temperature_baseline:
    name: Temperature baseline
    min: 15
    max: 25
    icon: mdi:thermometer
    unit_of_measurement: '°C'

  # How much rain is needed before the duration is reduced
  rainfall_threshold:
    name: Rainfall threshold
    min: 5
    max: 30
    icon: mdi:weather-rainy
    unit_of_measurement: 'mm'

  # Adjusted duration times can be extreme during calculations
  # depending on weather conditions so min and max are set high
  adjusted_cycle1_zone1_duration:
    name: Adjusted cycle1 zone1 duration
    min: -60
    max: 60

  adjusted_cycle1_zone2_duration:
    name: Adjusted cycle1 zone2 duration
    min: -60
    max: 60

  adjusted_cycle1_zone3_duration:
    name: Adjusted cycle1 zone3 duration
    min: -60
    max: 60

  adjusted_cycle1_zone4_duration:
    name: Adjusted cycle1 zone4 duration
    min: -60
    max: 60

  adjusted_cycle2_zone1_duration:
    name: Adjusted cycle1 zone1 duration
    min: -60
    max: 60

  adjusted_cycle2_zone2_duration:
    name: Adjusted cycle1 zone2 duration
    min: -60
    max: 60

  adjusted_cycle2_zone3_duration:
    name: Adjusted cycle1 zone3 duration
    min: -60
    max: 60

  adjusted_cycle2_zone4_duration:
    name: Adjusted cycle1 zone4 duration
    min: -60
    max: 60

 
  # Temperature
  temp_minus0:
    name: Temp today
    min: 0
    max: 40
    icon: mdi:thermometer

  temp_minus1:
    name: Temp yesterday
    min: 0
    max: 40
    icon: mdi:thermometer

  temp_minus2:
    name: Temp 2 days ago
    min: 0
    max: 40
    icon: mdi:thermometer

  temp_minus3:
    name: Temp 3 days ago
    min: 0
    max: 40
    icon: mdi:thermometer

  temp_minus4:
    name: Temp 4 days ago
    min: 0
    max: 40
    icon: mdi:thermometer

  temp_high_2days:
    name: Avg 2 day temp
    min: 0
    max: 40
    mode: box
    icon: mdi:thermometer

  temp_high_5days:
    name: Avg 5 day temp
    min: 0
    max: 40
    mode: box
    icon: mdi:thermometer


  # Rainfall
  rain_minus0:
    name: Rain today
    min: 0
    max: 100
    icon: mdi:weather-rainy

  rain_minus1:
    name: Rain yesterday
    min: 0
    max: 100
    icon: mdi:weather-rainy

  rain_minus2:
    name: Rain 2 days ago
    min: 0
    max: 100
    icon: mdi:weather-rainy

  rain_minus3:
    name: Rain 3 days ago
    min: 0
    max: 100
    icon: mdi:weather-rainy

  rain_3days_ratio:
    name: Last 3 days rain adj
    min: 0
    max: 500
    step: 0.01
    mode: box
    icon: mdi:weather-rainy


#===================
#=== Input_Booleans
#===================
input_boolean:

  cycle1_enable:
    name: Enable schedule
    icon: mdi:pipe
    
  cycle2_enable:
    name: Enable schedule
    icon: mdi:pipe

  cycle1_use_weather_adjustment:
    name: Use weather adjustment
    icon: mdi:weather-partlycloudy

  cycle2_use_weather_adjustment:
    name: Use weather adjustment
    icon: mdi:weather-partlycloudy

  cycle1_manual_run:
    name: Run morning cycle now
    icon: mdi:arrow-right-drop-circle

  cycle2_manual_run:
    name: Run afternoon cycle now
    icon: mdi:arrow-right-drop-circle

  cycle1_running:
    name: Cycle 1 running

  cycle2_running:
    name: Cycle 2 running


#====================
#=== Input_Datetimes
#====================
input_datetime:
  cycle1_next_run_time:
    has_date: true
    has_time: true

  cycle2_next_run_time:
    has_date: true
    has_time: true

  last_irrigated_time:
    has_date: true
    has_time: true


#===========
#=== timers
#===========
timer:
  cycle1_zone_duration:
    name: Time remaining

  cycle2_zone_duration:
    name: Time remaining


#============
#=== Sensors
#============
sensor:
  - platform: template
    sensors:

      cycle1_next_run_time:
        friendly_name: "Next scheduled run time"
        value_template: >
          {% if is_state('input_boolean.cycle1_enable', 'on') %}
            {{ (as_timestamp(states.input_datetime.cycle1_next_run_time.state)) | timestamp_custom("%a %d %h at %H:%M") }}
          {% else %}
            None
          {% endif %}
        icon_template: mdi:clock-start

      cycle2_next_run_time:
        friendly_name: "Next scheduled run time"
        value_template: >
          {% if is_state('input_boolean.cycle2_enable', 'on') %}
            {{ (as_timestamp(states.input_datetime.cycle2_next_run_time.state)) | timestamp_custom("%a %d %h at %H:%M") }}
          {% else %}
            None
          {% endif %}
        icon_template: mdi:clock-start

      cycle1_running:
        friendly_name: "Morning cycle"
        value_template: >
          {% if is_state('input_boolean.cycle1_running', 'on') %}
            Running
          {% else %}
            Not running
          {% endif %}     
        icon_template: >
          {% if is_state('input_boolean.cycle1_running', 'on') %}
            mdi:run
          {% else %}
            mdi:human-handsdown
          {% endif %}     
          
      cycle2_running:
        friendly_name: "Afternoon cycle"
        value_template: >
          {% if is_state('input_boolean.cycle2_running', 'on') %}
            Running
          {% else %}
            Not running
          {% endif %}     
        icon_template: >
          {% if is_state('input_boolean.cycle2_running', 'on') %}
            mdi:run
          {% else %}
            mdi:human-handsdown
          {% endif %}     

      cycle1_zone_being_watered:
        friendly_name: "Zone being watered"
        value_template: "{{ states('input_text.cycle1_current_zone') }}"     
        icon_template: mdi:square-inc
        
      cycle2_zone_being_watered:
        friendly_name: "Zone being watered"
        value_template: "{{ states('input_text.cycle2_current_zone') }}"     
        icon_template: mdi:square-inc

      last_irrigated_time:
        friendly_name: "Last irrigation cycle ended"
        value_template: >
          {{ (as_timestamp(states.input_datetime.last_irrigated_time.state)) | timestamp_custom("%a %d %h at %H:%M") }}
        icon_template: mdi:update
        
      # Zone Times - templated on history_stats sensors
      zone1_time_today:
        friendly_name: Zone 1 total watering time today
        value_template: >
          {% set duration = states('sensor.zone1_time') %}
          {{ (float(duration) * 60) | round }}
        unit_of_measurement: minutes
        icon_template: mdi:water

      zone2_time_today:
        friendly_name: Zone 2 total watering time today
        value_template: >
          {% set duration = states('sensor.zone2_time') %}
          {{ (float(duration) * 60) | round }}
        unit_of_measurement: minutes
        icon_template: mdi:water
        
      zone3_time_today:
        friendly_name: Zone 3 total watering time today
        value_template: >
          {% set duration = states('sensor.zone3_time') %}
          {{ (float(duration) * 60) | round }}
        unit_of_measurement: minutes
        icon_template: mdi:water
        
      zone4_time_today:
        friendly_name: Zone 4 total watering time today
        value_template: >
          {% set duration = states('sensor.zone4_time') %}
          {{ (float(duration) * 60) | round }}
        unit_of_measurement: minutes
        icon_template: mdi:water
        

      cycle1_zone1_duration_adjusted:
        friendly_name: "Zone 1 adjusted duration"
        value_template: "{{ states('input_number.adjusted_cycle1_zone1_duration') | int }}"     
        icon_template: mdi:camera-timer

      cycle1_zone2_duration_adjusted:
        friendly_name: "Zone 2 adjusted duration"
        value_template: "{{ states('input_number.adjusted_cycle1_zone2_duration') | int }}"     
        icon_template: mdi:camera-timer

      cycle1_zone3_duration_adjusted:
        friendly_name: "Zone 3 adjusted duration"
        value_template: "{{ states('input_number.adjusted_cycle1_zone3_duration') | int }}"     
        icon_template: mdi:camera-timer

      cycle1_zone4_duration_adjusted:
        friendly_name: "Zone 4 adjusted duration"
        value_template: "{{ states('input_number.adjusted_cycle1_zone4_duration') | int }}"     
        icon_template: mdi:camera-timer

      cycle2_zone1_duration_adjusted:
        friendly_name: "Zone 1 adjusted duration"
        value_template: "{{ states('input_number.adjusted_cycle2_zone1_duration') | int }}"     
        icon_template: mdi:camera-timer

      cycle2_zone2_duration_adjusted:
        friendly_name: "Zone 2 adjusted duration"
        value_template: "{{ states('input_number.adjusted_cycle2_zone2_duration') | int }}"     
        icon_template: mdi:camera-timer

      cycle2_zone3_duration_adjusted:
        friendly_name: "Zone 3 adjusted duration"
        value_template: "{{ states('input_number.adjusted_cycle2_zone3_duration') | int }}"     
        icon_template: mdi:camera-timer

      cycle2_zone4_duration_adjusted:
        friendly_name: "Zone 4 adjusted duration"
        value_template: "{{ states('input_number.adjusted_cycle2_zone4_duration') | int }}"     
        icon_template: mdi:camera-timer

      # Weather information sensors
      # Temperature
      temp_minus0:
        friendly_name: Temp today
        value_template: "{{ states('input_number.temp_minus0') | int }}"     
        icon_template: mdi:thermometer

      temp_minus1:
        friendly_name: Temp yesterday
        value_template: "{{ states('input_number.temp_minus1') | int }}"     
        icon_template: mdi:thermometer

      temp_minus2:
        friendly_name: Temp 2 days ago
        value_template: "{{ states('input_number.temp_minus2') | int }}"     
        icon_template: mdi:thermometer

      temp_minus3:
        friendly_name: Temp 3 days ago
        value_template: "{{ states('input_number.temp_minus3') | int }}"     
        icon_template: mdi:thermometer

      temp_minus4:
        friendly_name: Temp 4 days ago
        value_template: "{{ states('input_number.temp_minus4') | int }}"     
        icon_template: mdi:thermometer

      temp_high_2days:
        friendly_name: Avg 2 day temp
        value_template: "{{ states('input_number.temp_high_2days') | int }}"     
        icon_template: mdi:thermometer

      temp_high_5days:
        friendly_name: Avg 5 day temp
        value_template: "{{ states('input_number.temp_high_5days') | int }}"     
        icon_template: mdi:thermometer


      # Rainfall
      rain_minus0:
        friendly_name: Rain today
        value_template: "{{ states('input_number.rain_minus0') | int }}"     
        icon_template: mdi:weather-rainy

      rain_minus1:
        friendly_name: Rain yesterday
        value_template: "{{ states('input_number.rain_minus1') | int }}"     
        icon_template: mdi:weather-rainy

      rain_minus2:
        friendly_name: Rain 2 days ago
        value_template: "{{ states('input_number.rain_minus2') | int }}"     
        icon_template: mdi:weather-rainy

      rain_minus3:
        friendly_name: Rain 3 days ago
        value_template: "{{ states('input_number.rain_minus3') | int }}"     
        icon_template: mdi:weather-rainy

      rain_3days_ratio:
        friendly_name: Last 3 days rain adj. multiplyer
        value_template: "{{ states('input_number.rain_3days_ratio') | int }}"     
        icon_template: mdi:weather-rainy


  # History sensors
  # Zone Times
  - platform: history_stats
    name: zone1_time
    entity_id: switch.zone1_valve
    state: 'on'
    type: time
    start: '{{ now().replace(hour=0).replace(minute=0).replace(second=0) }}'
    end: '{{ now() }}'

  - platform: history_stats
    name: zone2_time
    entity_id: switch.zone2_valve
    state: 'on'
    type: time
    start: '{{ now().replace(hour=0).replace(minute=0).replace(second=0) }}'
    end: '{{ now() }}'

  - platform: history_stats
    name: zone3_time
    entity_id: switch.zone3_valve
    state: 'on'
    type: time
    start: '{{ now().replace(hour=0).replace(minute=0).replace(second=0) }}'
    end: '{{ now() }}'

  - platform: history_stats
    name: zone4_time
    entity_id: switch.zone4_valve
    state: 'on'
    type: time
    start: '{{ now().replace(hour=0).replace(minute=0).replace(second=0) }}'
    end: '{{ now() }}'


#=============
#=== Switches
#=============
switch:

  # sonoff (4ch)
  # Zone 1
  - platform: mqtt
    name: "Zone1 valve"
    state_topic: "stat/sonoff4ch01/POWER1"
    command_topic: "cmnd/sonoff4ch01/power1"
    availability_topic: "tele/sonoff4ch01/LWT"
    qos: 1
    payload_on: "ON"
    payload_off: "OFF"
    payload_available: "Online"
    payload_not_available: "Offline"
    retain: true

  # Zone 2
  - platform: mqtt
    name: "Zone2 valve"
    state_topic: "stat/sonoff4ch01/POWER2"
    command_topic: "cmnd/sonoff4ch01/power2"
    availability_topic: "tele/sonoff4ch01/LWT"
    qos: 1
    payload_on: "ON"
    payload_off: "OFF"
    payload_available: "Online"
    payload_not_available: "Offline"
    retain: true

  # Zone 3
  - platform: mqtt
    name: "Zone3 valve"
    state_topic: "stat/sonoff4ch01/POWER3"
    command_topic: "cmnd/sonoff4ch01/power3"
    availability_topic: "tele/sonoff4ch01/LWT"
    qos: 1
    payload_on: "ON"
    payload_off: "OFF"
    payload_available: "Online"
    payload_not_available: "Offline"
    retain: true

  # Zone 4
  - platform: mqtt
    name: "Zone4 valve"
    state_topic: "stat/sonoff4ch01/POWER4"
    command_topic: "cmnd/sonoff4ch01/power4"
    availability_topic: "tele/sonoff4ch01/LWT"
    qos: 1
    payload_on: "ON"
    payload_off: "OFF"
    payload_available: "Online"
    payload_not_available: "Offline"
    retain: true
10 Likes

garden_weather_calculations.yaml

#=================
# === Automations
#=================
automation:

  #=====================================================================
  #=== Adjust weather durations if:
  #===   the baseline duration is changed
  #===   the temperature threshold or rainfall baseline are changed
  #===   use weather adjustment is turn on
  #=====================================================================
  - alias: Duration changed in cycle1
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id:
          - input_number.cycle1_zone1_duration
          - input_number.cycle1_zone2_duration
          - input_number.cycle1_zone3_duration
          - input_number.cycle1_zone4_duration
    action:
      - service: script.adjust_cycle1_durations

  - alias: Duration changed in cycle2
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id:
          - input_number.cycle2_zone1_duration
          - input_number.cycle2_zone2_duration
          - input_number.cycle2_zone3_duration
          - input_number.cycle2_zone4_duration
    action:
      - service: script.adjust_cycle2_durations

  - alias: Change in rain baseline or temp baseline
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id:
          - input_number.temperature_baseline
          - input_number.rainfall_threshold
    action:
      - service: script.adjust_cycle1_durations
      - service: script.adjust_cycle2_durations
      
  - alias: Use weather adjustment turned on
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id:
          - input_boolean.cycle1_use_weather_adjustment
          - input_boolean.cycle2_use_weather_adjustment
        to: 'on'
    action:
      - service: script.adjust_cycle1_durations
      - service: script.adjust_cycle2_durations
      
  #===============================================
  #=== Cycle historic weather data in the history
  #===============================================
  - alias: Collect weather data
    initial_state: 'on'
    trigger:
      - platform: time
        at: '23:55:00'

    action:
      # Cycle the temperature figures
      - service: input_number.set_value
        data_template:
          entity_id: input_number.temp_minus5
          value: >
            {{ states('input_number.temp_minus4') }}
      - service: input_number.set_value
        data_template:
          entity_id: input_number.temp_minus4
          value: >
            {{ states('input_number.temp_minus3') }}
      - service: input_number.set_value
        data_template:
          entity_id: input_number.temp_minus3
          value: >
            {{ states('input_number.temp_minus2') }}
      - service: input_number.set_value
        data_template:
          entity_id: input_number.temp_minus2
          value: >
            {{ states('input_number.temp_minus1') }}
      - service: input_number.set_value
        data_template:
          entity_id: input_number.temp_minus1
          value: >
            {{ states('input_number.temp_minus0') }}
      
      # Reset todays temp to low - the average weather calculations happen every
      # 30 mins and will only update todays temperature if it goes up.
      # This is because the DarkSky max temperature appears to change during
      # the day to reflect max temperature for the remainder of the day.
      - service: input_number.set_value
        data_template:
          entity_id: input_number.temp_minus0
          value: '0'

      # Cycle the rainfall figures
      - service: input_number.set_value
        data_template:
          entity_id: input_number.rain_minus3
          value: >
            {{ states('input_number.rain_minus2') }}
      - service: input_number.set_value
        data_template:
          entity_id: input_number.rain_minus2
          value: >
            {{ states('input_number.rain_minus1') }}
      - service: input_number.set_value
        data_template:
          entity_id: input_number.rain_minus1
          value: >
            {{ states('input_number.rain_minus0') }}
      
      # Set today's forecast rain
      # DarkSky rain is measured in mm/h so multiply by 24 then multiply by the probibility of rain.
      # This is done here so that it is only done once a day after midnight.
      # The forcast figues go up and down all day so the the best we can do for history
      # is take the whole day forecast once only.
      # We do NOT want to do it every time a re-calculation of the averages is performed.

      - wait_template: "{{ states.sensor.time.state == '00:10' }}"

      - service: input_number.set_value
        data_template:
          entity_id: input_number.rain_minus0
          value: >
            {{ float(states.sensor.dark_sky_precip_intensity.state) * 24 *
               (float(states.sensor.dark_sky_precip_probability.state) / 100) }}

      # Calculate temperature and rainfall averages
      - service: script.calculate_average_weather_conditions
      - wait_template: "{{ is_state('script.calculate_average_weather_conditions', 'off') }}"

      # Adjust Cycle 1 durations
      - service: script.adjust_cycle1_durations
      - wait_template: "{{ is_state('script.adjust_cycle1_durations', 'off') }}"

      # Adjust Cycle 2 durations
      - service: script.adjust_cycle2_durations
      - wait_template: "{{ is_state('script.adjust_cycle2_durations', 'off') }}"
      
#=================================================
#=== Recalculate average weather conditions every
#=== 30 minutes to account for todays weather
#=================================================
  - alias: Update average weather every 30 mins
    initial_state: 'on'
    trigger:
      - platform: time
        minutes: '/30'
        seconds: 00
    action:
      - service: script.calculate_average_weather_conditions


#============
#=== Scripts
#============
script:

  #=========================================
  #=== Calculate average weather conditions
  #=========================================
  calculate_average_weather_conditions:
    sequence:

      # Set today's forecast high temperature
      # but only if it has gone up
      # temp_minus0 is set to low evry night at 23:50 when the weather
      # information is cycled
      - service: input_number.set_value
        data_template:
          entity_id: input_number.temp_minus0  
          value: >
            {% if states.input_number.temp_minus0.state | float < states.sensor.dark_sky_daily_high_temperature.state | float %}
              {{ states('sensor.dark_sky_daily_high_temperature') }}
            {% else %}
              {{ states('input_number.temp_minus0') }}
            {% endif %}

      # Calculate average high temp for the today and yesterday
      - service: input_number.set_value
        data_template:
          entity_id: input_number.temp_high_2days
          value: >
            {{ ((float(states.input_number.temp_minus0.state) + 
                 float(states.input_number.temp_minus1.state)) / 2 ) | round  }}
    
      # Calculate average high temp for the today and yesterday and previous 3 days
      - service: input_number.set_value
        data_template:
          entity_id: input_number.temp_high_5days
          value: >
            {{ ((float(states.input_number.temp_minus0.state) +
                 float(states.input_number.temp_minus1.state) +
                 float(states.input_number.temp_minus2.state) + 
                 float(states.input_number.temp_minus3.state) + 
                 float(states.input_number.temp_minus4.state)) / 5) | round }}

      # Adjust the total amount of rain depending on how many days ago it was.
      # If 3 days ago take 20% of the rain.
      # If 2 days ago take 50% of the rain.
      # If yesterday take 80% of the rain.
      - service: input_number.set_value
        data_template:
          entity_id: input_number.rain_3days_ratio
          value: >
            {{ (float(states.input_number.rain_minus1.state) * 0.8) +
               (float(states.input_number.rain_minus2.state) * 0.5) +
               (float(states.input_number.rain_minus3.state) * 0.2) }}

      # Add in todays forecast rain
      - service: input_number.set_value
        data_template:
          entity_id: input_number.rain_3days_ratio
          value: >
            {{ float(states.input_number.rain_3days_ratio.state) + 
               (float(states.input_number.rain_minus0.state)) }}  

      # Convert into a ratio (maximum 1.00) used to reduce irrigation time or stop it altogether:
      #   Rain needed in last 3 days to stop grass irrigation is set in RainfallThreshold
      #   Rain levels less than this amount will be used to reduce irrigation run times proportionally
      - service: input_number.set_value
        data_template:
          entity_id: input_number.rain_3days_ratio
          value: >
            {{ states.input_number.rain_3days_ratio.state | float / states.input_number.rainfall_threshold.state | float }}
      - service: input_number.set_value
        data_template:
          entity_id: input_number.rain_3days_ratio
          value: >
            {% if states.input_number.rain_3days_ratio.state | float > 1 %}
              1
            {% else %}
              {{ states.input_number.rain_3days_ratio.state }}
            {% endif %}


  #=========================================
  #=== Adjust duration for zones in cycle 1
  #=========================================
  adjust_cycle1_durations:
    sequence:
      - service: script.calculate_adjusted_zone_durations
        data:
          cycle: '1'
          zone: '1'
      - wait_template: "{{ is_state('script.calculate_adjusted_zone_durations', 'off') }}"
      
      - service: script.calculate_adjusted_zone_durations
        data:
          cycle: '1'
          zone: '2'
      - wait_template: "{{ is_state('script.calculate_adjusted_zone_durations', 'off') }}"
      
      - service: script.calculate_adjusted_zone_durations
        data:
          cycle: '1'
          zone: '3'
      - wait_template: "{{ is_state('script.calculate_adjusted_zone_durations', 'off') }}"
      
      - service: script.calculate_adjusted_zone_durations
        data:
          cycle: '1'
          zone: '4'
      - wait_template: "{{ is_state('script.calculate_adjusted_zone_durations', 'off') }}"
      

  #=========================================
  #=== Adjust duration for zones in cycle 2
  #=========================================
  adjust_cycle2_durations:
    sequence:
      - service: script.calculate_adjusted_zone_durations
        data:
          cycle: '2'
          zone: '1'
      - wait_template: "{{ is_state('script.calculate_adjusted_zone_durations', 'off') }}"

      - service: script.calculate_adjusted_zone_durations
        data:
          cycle: '2'
          zone: '2'
      - wait_template: "{{ is_state('script.calculate_adjusted_zone_durations', 'off') }}"

      - service: script.calculate_adjusted_zone_durations
        data:
          cycle: '2'
          zone: '3'
      - wait_template: "{{ is_state('script.calculate_adjusted_zone_durations', 'off') }}"

      - service: script.calculate_adjusted_zone_durations
        data:
          cycle: '2'
          zone: '4'
      - wait_template: "{{ is_state('script.calculate_adjusted_zone_durations', 'off') }}"



  #========================================================
  #=== Adjust zone duration for average weather conditions
  #=== is passed:
  #===   {{ cycle }}
  #===   {{ zone }}
  #========================================================
  calculate_adjusted_zone_durations:
    sequence:
      #   Adjust for average daily temperature

      #   Calculate any change in run time based on daily average temperature.
      #   The default run time will occur at TemperatureBaseline degrees.
      #   As the daily average temperature increases above this the run time will
      #   also increase.
      #   As the average temp drops below TemperatureBaseline the run time decreases.
      #   The forumla used ‐ based on a 20 degree TemperatureBaseline would be:
      #   (((TwoDayHighAverageTemp ‐ 20) / 20) +1) * BaseLineDuration
      #   So: the percentage change in duration is the same as the percentage
      #       difference between the actual temperature and the baseline temperature
      #       i.e. a 10% increase in duration when the baseline temperataure 
      #            is 20 degrees and the actual temperature is 22 degrees 
      - service: input_number.set_value
        data_template:
          entity_id: input_number.adjusted_cycle{{ cycle }}_zone{{ zone }}_duration
          value: > 
            {% set duration = states('input_number.cycle' + cycle + '_zone' + zone + '_duration') %}
              {{ (((float(states.input_number.temp_high_2days.state) - float(states.input_number.temperature_baseline.state)) / float(states.input_number.temperature_baseline.state)) + 1) * float(duration) }}
      
      # Adjust for average daily rainfall
      - service: input_number.set_value
        data_template:
          entity_id: input_number.adjusted_cycle{{ cycle }}_zone{{ zone }}_duration
          value: >
            {% set duration = states('input_number.cycle' + cycle + '_zone' + zone + '_duration') %}
            {% set adj_duration = states('input_number.adjusted_cycle' + cycle + '_zone' + zone + '_duration') %}
              {{ float(adj_duration) - (float(duration) * float(states.input_number.rain_3days_ratio.state)) }}

      # Make sure new duration is not less than zero
      - service: input_number.set_value
        data_template:
          entity_id: input_number.adjusted_cycle{{ cycle }}_zone{{ zone }}_duration
          value: >
            {% if states('input_number.adjusted_cycle' + cycle + '_zone' + zone + '_duration') | float < 0 %}
              0
            {% elif states('input_number.adjusted_cycle' + cycle + '_zone' + zone + '_duration') | float > 30 %}
              30
            {% else %}
              {{ states('input_number.adjusted_cycle' + cycle + '_zone' + zone + '_duration') }}
            {% endif %}
8 Likes

garden_irrigation.yaml

#================
#=== Automations
#================
automation:

#============================================================
#=== Set next run time when schedule enabled or time changed
#============================================================
  - alias: Set next run time
    trigger:
      - platform: state
        entity_id:
          - input_boolean.cycle1_enable
          - input_boolean.cycle2_enable
        to: 'on'
      - platform: state
        entity_id:
          - input_select.cycle1_schedule_time
          - input_select.cycle2_schedule_time
    action:
      - service: input_datetime.set_datetime
        data_template:
          entity_id: >
            {% if trigger.entity_id == 'input_boolean.cycle1_enable' or
                  trigger.entity_id == 'input_select.cycle1_schedule_time' %}
              input_datetime.cycle1_next_run_time
            {% else %}
              input_datetime.cycle2_next_run_time
            {% endif %}
          date: >
            {% if trigger.entity_id == 'input_boolean.cycle1_enable' or
                  trigger.entity_id == 'input_select.cycle1_schedule_time' %}
              {% set time = states('input_select.cycle1_schedule_time') %}
            {% else %}
              {% set time = states('input_select.cycle2_schedule_time') %}
            {% endif %}
            {% if now().strftime('%H:%M') < time %}
              {{ as_timestamp(now()) | timestamp_custom("%Y-%m-%d") }}
            {% else %}
              {{ (as_timestamp(now()) + 24 * 3600 ) | timestamp_custom("%Y-%m-%d") }}
            {% endif %}
          time: >
            {% if trigger.entity_id == 'input_boolean.cycle1_enable' or
                  trigger.entity_id == 'input_select.cycle1_schedule_time' %}
              {{ states('input_select.cycle1_schedule_time') }}
            {% else %}
              {{ states('input_select.cycle2_schedule_time') }}
            {% endif %}

    
#==================================================================
#=== Run irrigation cycles at the right time and on the right days
#==================================================================

  #=== Cycle 1
  - alias: Run Irrigation Cycle 1
    initial_state: 'on'
    trigger:
      - platform: time
        minutes: 00
        seconds: 00

    condition:
      - condition: template
        value_template: >
          {{ now().strftime('%Y-%m-%d %H:%M:00') == states('input_datetime.cycle1_next_run_time') }}
      - condition: state
        entity_id: input_boolean.cycle1_enable
        state: 'on'

    action:
      - service: script.run_a_cycle
        data_template:
          cycle: 1

  #=== Cycle 2
  - alias: Run Irrigation Cycle 2
    initial_state: 'on'
    trigger:
      - platform: time
        minutes: 00
        seconds: 00

    condition:
      - condition: template
        value_template: >
          {{ now().strftime('%Y-%m-%d %H:%M:00') == states('input_datetime.cycle2_next_run_time') }}
      - condition: state
        entity_id: input_boolean.cycle2_enable
        state: 'on'

    action:
      - service: script.run_a_cycle
        data_template:
          cycle: 2


#======================
# Run A Cycle Manually
#======================
  - alias: Run Cycle a Maually
    trigger:
    - platform: state
      entity_id: 
      - input_boolean.cycle1_manual_run
      - input_boolean.cycle2_manual_run
      from: 'off'
      to: 'on'

    # Don't run if the other cycle is already running
    condition:
    - condition: template
      value_template: >
        {% if trigger_entity_id == 'input_boolean.cycle1_manual_run' %}
          {{ states('sensor.cycle2_running') == 'Not running' }}
        {% else %}
          {{ states('sensor.cycle1_running') == 'Not running' }}
        {% endif %}

    action:
    # Update progress in ui
    - service: input_text.set_value
      data_template:
        entity_id: >
          {% if trigger.entity_id == 'input_boolean.cycle1_manual_run' %}
            input_text.cycle1_current_zone
          {% else %}
            input_text.cycle2_current_zone
          {% endif %}
        value: 'Initialising...'

    # Make sure nothing in the system is running
    # turn off valves
    - service: homeassistant.turn_off  
      data_template:
        entity_id: group.irrigation_valves

    # turn off scripts
    - service: homeassistant.turn_off
      entity_id: script.run_a_cycle

    - service: homeassistant.turn_off
      entity_id: script.irrigate_a_zone

    # disable both programmes
    - service: homeassistant.turn_off
      entity_id:
      - input_boolean.cycle1_enable
      - input_boolean.cycle2_enable

    # run a cycle
    - service: script.run_a_cycle
      data_template:
        cycle: >
          {% if trigger.entity_id == 'input_boolean.cycle1_manual_run' %}
            1
          {% else %}
            2
          {% endif %}

    # wait until script is finished
    - wait_template: "{{ is_state('script.run_a_cycle', 'off') }}"

    # turn off both manual run booleans (in case one was turned on
    # while the other was already running)
    - service: homeassistant.turn_off  
      entity_id:
      - input_boolean.cycle1_manual_run
      - input_boolean.cycle2_manual_run


  #=======================
  # Cancel A Manual Cycle
  #=======================
  - alias: Cancel A Manual Cycle
    trigger:
    - platform: state
      entity_id: 
      - input_boolean.cycle1_manual_run
      - input_boolean.cycle2_manual_run
      from: 'on'
      to: 'off'

    action:
    # turn off valves
    - service: homeassistant.turn_off  
      data_template:
        entity_id: group.irrigation_valves

    # turn off scripts
    - service: homeassistant.turn_off
      entity_id: script.run_a_cycle

    - service: homeassistant.turn_off
      entity_id: script.irrigate_a_zone

    # Cancel the timer
    - service: timer.cancel
      data_template:
        entity_id: >
          {% if trigger.entity_id == 'input_boolean.cycle1_manual_run' %}
            timer.cycle1_zone_duration
          {% else %}
            timer.cycle2_zone_duration
          {% endif %}
      
    # Update progress in ui
    - service: homeassistant.turn_off
      data_template:
        entity_id: >
          {% if trigger.entity_id == 'input_boolean.cycle1_manual_run' %}
            input_boolean.cycle1_running
          {% else %}
            input_boolean.cycle2_running
          {% endif %}

    - service: input_text.set_value
      data_template:
        entity_id: >
          {% if trigger.entity_id == 'input_boolean.cycle1_manual_run' %}
            input_text.cycle1_current_zone
          {% else %}
            input_text.cycle2_current_zone
          {% endif %}
        value: 'None'


  #===================================================
  #=== Update the last time an irrigation cycle ended
  #===================================================
  - alias: Update last run time
    trigger:
    - platform: state
      entity_id: group.irrigation_valves
      to: 'off'
      for: '00:00:05'
    action:
    - service: input_datetime.set_datetime
      data_template:
        entity_id: input_datetime.last_irrigated_time
        date: >
          {{ as_timestamp(now()) | timestamp_custom("%Y-%m-%d ") }}
        time: >
          {{ as_timestamp(now()) | timestamp_custom("%H:%M") }}


#============
#=== Scripts
#============
script:

  #==============================
  #=== Script to run a cycle
  #=== is passed:
  #===   {{ cycle }}
  #==============================
  run_a_cycle:
    sequence:

      # Update next runtime
      - service: input_datetime.set_datetime
        data_template:
          entity_id: input_datetime.cycle{{ cycle }}_next_run_time
          date: >
            {% if states('input_select.cycle' + cycle + '_watering_days') == 'Daily' %}
              {{ (as_timestamp(now() ) + 24 * 3600 ) | timestamp_custom("%Y-%m-%d ") }}
            {% else %}  
              {{ (as_timestamp(now() ) + 48 * 3600 ) | timestamp_custom("%Y-%m-%d ") }}
            {% endif %}
          time: >
              {{ states('input_select.cycle' + cycle + '_schedule_time') }}

      # Only continue if no cycle is running
      - condition: state
        entity_id: 'sensor.cycle1_running'
        state: 'Not running'
      - condition: state
        entity_id: 'sensor.cycle2_running'
        state: 'Not running'

      # Update ui to show cycle is running
      - service: homeassistant.turn_on
        data_template:  
          entity_id: input_boolean.cycle{{ cycle }}_running

      # Call the script to irrigate zone 1
      - service: script.irrigate_a_zone
        data_template:
          cycle: '{{ cycle }}'
          zone: 1

      # Wait for irrigate script to end
      - wait_template: "{{ is_state('script.irrigate_a_zone', 'off') }}"

      # Call the script to irrigate zone 2
      - service: script.irrigate_a_zone
        data_template:
          cycle: '{{ cycle }}'
          zone: 2

      # Wait for script.irrigate_a_zone to end
      - wait_template: "{{ is_state('script.irrigate_a_zone', 'off') }}"
      
      # Call the script to irrigate zone 3
      - service: script.irrigate_a_zone
        data_template:
          cycle: '{{ cycle }}'
          zone: 3

      # Wait for script.irrigate_a_zone to end
      - wait_template: "{{ is_state('script.irrigate_a_zone', 'off') }}"
      
      # Call the script to irrigate zone 4
      - service: script.irrigate_a_zone
        data_template:
          cycle: '{{ cycle }}'
          zone: 4

      # Wait for script.irrigate_a_zone to end
      - wait_template: "{{ is_state('script.irrigate_a_zone', 'off') }}"

      # Update progress in ui
      - service: input_text.set_value
        data_template:
          entity_id: input_text.cycle{{ cycle }}_current_zone
          value: 'None'

      - service: homeassistant.turn_off  
        data_template:
          entity_id: input_boolean.cycle{{ cycle }}_running


  #==============================
  #=== Script to irrigate a zone
  #=== is passed:
  #===   {{ cycle }}
  #===   {{ zone }}
  #==============================
  irrigate_a_zone:
    sequence:

      # Don't continue if duration is zero
      - condition: template
        value_template: >
          {% set n = states('input_number.cycle' + cycle + '_zone' + zone + '_duration')  | int %}
            {{ n != 0 }}
          
      # wait 3 seconds for good measure to enable previous zone to completly finish
      - delay: "00:00:03"

      # Update progress in ui
      - service: input_text.set_value
        data_template:
          entity_id: 'input_text.cycle{{ cycle }}_current_zone'
          value: '{{ zone }}'

      - service: switch.turn_on
        data_template:
          entity_id: switch.zone{{ zone }}_valve
         
      # Start timer for zone duration
          
########################################
# CHANGE DURATION BACK TO MINUTES AFTER TESTING
#      00:{{ '%02i' | format(adj_duration) }}:00
#      00:{{ '%02i' | format(duration) }}:00
#
#
# Use seconds for testing
#      00:00:{{ '%02i' | format(adj_duration) }}
#      00:00:{{ '%02i' | format(duration) }}
########################################
#      - service: timer.start
#        data_template:
#          entity_id: timer.cycle{{ cycle }}_zone_duration
#          duration: >
#            {% set duration = states('input_number.cycle' + cycle + '_zone' + zone + '_duration')  | int %}
#            {% set adj_duration = states('input_number.adjusted_cycle' + cycle + '_zone' + zone + '_duration')  | int %}
#            {% if is_state('input_boolean.cycle' + cycle + '_use_weather_adjustment', 'on') %}
#              00:00:{{ '%02i' | format(adj_duration) }}
#            {% else %}
#              00:00:{{ '%02i' | format(duration) }}
#            {% endif %}
#      - wait_template: >
#          {% set entity = 'timer.cycle' + cycle + '_zone_duration' %}
#          {{ is_state(entity , 'idle') }}
##########################################

      - service: timer.start
        data_template:
          entity_id: timer.cycle{{ cycle }}_zone_duration
          duration: >
            {% set duration = states('input_number.cycle' + cycle + '_zone' + zone + '_duration')  | int %}
            {% set adj_duration = states('input_number.adjusted_cycle' + cycle + '_zone' + zone + '_duration')  | int %}
            {% if is_state('input_boolean.cycle' + cycle + '_use_weather_adjustment', 'on') %}
              00:{{ '%02i' | format(adj_duration) }}:00
            {% else %}
              00:{{ '%02i' | format(duration) }}:00
            {% endif %}
      - wait_template: >
          {% set entity = 'timer.cycle' + cycle + '_zone_duration' %}
          {{ is_state(entity , 'idle') }}

      - service: switch.turn_off
        data_template:
          entity_id: switch.zone{{ zone }}_valve

      # Update progress in ui
      - service: input_text.set_value
        data_template:
          entity_id: input_text.cycle{{ cycle }}_current_zone
          value: 'Changing zone...'
7 Likes

thanks i’ll try that at the weekend!!

Hello, sorry I tried your configuration that I find it very interesting, unfortunately gives me errors, I created the three yaml files that you have published but gives me the following error “Component not found: garden” in the file configuration.yaml how they should be referred to ex. I have indicated
"garden global: !include garden_globals.yaml
garden weather: !include garden_weather_calculations.yaml
garden irrigation: !include garden_irrigation.yaml "
is correct?
thanks

@gojonny I use packages.

In my configuration.yaml I have

homeassistant:
  packages: !include_dir_named packages/   

create the folder packages

Put the three files in that folder.

2 Likes

@klogg Really liked your irrigation package for HA.
I implemented it to my setup with 5 zones.
I am getting the below listed error for all 5 sensors.

homeassistant.exceptions.InvalidStateError: Invalid state encountered for entity id: sensor.zone1_time_today. State max length is 255 characters.

This isn’t going to be very helpful but I remember getting that message too when was writing it.
I am pretty sure (but really can’t remember) that it turned out to be a simple syntax error that got through the config check.

Have you looked at the full Home Assistant Log? Sometimes that gives more information
Sorry that I can’t be more helpful. At the bottom of the ‘Info’ page in developers tools see ‘Press the button to load the full Home Assistant log’

See below

2018-05-19 19:45:45 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
File “/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py”, line 287, in async_update_ha_state
self.entity_id, state, attr, self.force_update)
File “/usr/lib/python3.6/site-packages/homeassistant/core.py”, line 738, in async_set
state = State(entity_id, new_state, attributes, last_changed)
File “/usr/lib/python3.6/site-packages/homeassistant/core.py”, line 533, in init
“State max length is 255 characters.”).format(entity_id))

Also, do i need any monitored conditions for the weather history to update?

Yes I have DarkSky set up as follows but only the temperature and precipitation conditions are needed for weather history.

  - platform: darksky
    api_key: !secret darksky_api_key
    units: auto
    update_interval:
      minutes: 15
    monitored_conditions:
      - summary
      - hourly_summary
      - temperature
      - temperature_max
      - temperature_min
      - precip_probability
      - precip_intensity
      - precip_type
      - precip_intensity_max
      - cloud_cover
      - dew_point
      - wind_speed
      - humidity
1 Like

Hello, in thanking you for the work done I wanted to ask you … I managed to configure darksky and is seen by HA but does not integrate with irrigation, you would be kind enough to indicate where you placed darksky in the configuration and how did you communicate with irrigation? thank you

Weather information history is pulled from your database, so let it run for couple of hours and it will start populating today’s temp and rain data. if you don’t want to wait you will have to use input sliders to update the data manually for the first time and then it will continue to do it by self.

1 Like

Nothing, I followed the advice but do not integrate with the data of Darksky, I checked the console of Darksky and the requests are periodically, if I verify in the entities of HA the sensors of DarkSky (precipitation, temperature etc) are detected, but with irrigation nothing … other tips?

hi klogg. this is really great work! i dl’d your yaml files and they seem to work fine.
maybe you could give a bit more information on how you are supposed to use your scripts?
especially the logic.
i may sum up:

you have two cycles. one in the morning and one in the evening. why? because you plan to irrigate some zones more than once per day? i personally do the irrigation every 2nd day and then longer, but different parts of the world may need other schemes i guess. so i think if you switch from “daily” to “alternate” it does what i need, correct?

then you can set the duration for each of the 4 zones. that’s quite obvious.

you can also do an immediate start of the whole cylce by checking “run morning/afternoon cycle now”

then you have a nice status screen which shows which zone is running and how long it will run.

how are the weather settings taken into the calculation?

and how is the avg temp and rain from the last days taken into calculation?

will you include any kind of physical soil humiditiy sensors? this makes most sense imho. of course it still raises the question what percentage of humidity to set as trigger point for start/stop of the irrigation.