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
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
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:
And yes, there is a limit to post size so I will have to post the three files in separate posts.
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
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 %}
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...'
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.
@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
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.
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.
I’m glad you like it and I think you summed up pretty well how I planned the system! Yes if you choose alternate days I think it will do what you need.
For temperature it looks at the previous few days and then averages it. you can set the baseline temperature before it starts to adjust the time up or down.
For rainfall it looks at the previous few days and weights the effect depending on how many days ago the rain fell. Again you can set a baseline.
All the calculations are described in the comments in the code but as I said, I decided I might have been over thinking everything so in the end I kind of left things where they are now. It does do some reasonable looking adjustments (although we’ve had no rain here for a while) but you can probably come up with better algorithms. Mine are based on other ideas I have seen around in other forums when searching for ideas.
You are right, some actual physical sensors would probably be best but I have no intention right now of getting any. I wanted to write my system to be as modular and expandable as possible (within the very restrictive confines of HA and yaml config files!!!) so hopefully you will be able to add these features if you want them.
Good luck with using it, it is good to know that it might be useful for someone else.