Lots of new errors in log since 0.80.3 (Help please!)

I have just come back from being away and upgraded to 0.80.3 (from 0.79.x I think).

I am now getting the error shown below many tens of times after a re-start which I wasn’t getting before I went away. I don’t know if this is related to the upgrade but I have not made any changes to the relevant bits of my config for many months.

The errors all relate to sensors of the form sensor.zoneX_time_today which are all templated on sensor.zoneX_time. (X is a number from one to five)

They appear precisely once each in my config as shown.

Any help greatly appreciated.

sensor:
  - platform: template
    sensors:
      zone5_time_today:
        friendly_name: Zone 5 total watering time today
        value_template: >
          {% set duration = states('sensor.zone5_time') %}
          {{ (float(duration) * 60) | round }}
        unit_of_measurement: minutes
        icon_template: mdi:water

and

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

Log Details (ERROR)
Wed Oct 24 2018 11:58:45 GMT+0100 (British Summer Time)

Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 310, in async_update_ha_state
    self.entity_id, state, attr, self.force_update, self._context)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/core.py", line 903, in async_set
    context)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/core.py", line 673, in __init__
    "State max length is 255 characters.").format(entity_id))
homeassistant.exceptions.InvalidStateError: Invalid state encountered for entity id: sensor.zone5_time_today. State max length is 255 characters.

Apparently the float() function, if given a string that does not represent a valid number, will simply return the input string. And when you “multiply” a string by a number, the result is the string replicated that many times. And the round filter will also just pass through an invalid number (such as a string.) The end result is, if the state of sensor.zone5_time is ‘unknown’, the template will output a string which contains ‘unknown’ sixty times, which is longer than the maximum allowed state string length of 255.

I don’t know why the state of sensor.zone5_time is different than it was, but I would at least recommend changing the second part of your template sensor’s value_template to:

{{ duration|float|multiply(60)|round }}

The float filter (as opposed to the float function) will output 0.0 if the input cannot be properly converted to a float.

1 Like

Thank you so much. I am just about to try this,
Can you explain why my syntax:

{{ (float(duration) * 60) | round }}

is not the same as

{{ duration|float|multiply(60)|round }}


I’ll report back shortly after I’ve made the changes and my system has restarted.

Just for the record I am sure something must have changed in HA between 0.79 and 0.80.3 because I have not touched this code since well before June…

Well, that worked! Again thank you for taking the time to reply!

Um, I thought I did. :wink:

EDIT:

The real difference is using float(x) vs x|float. As I mentioned, if x cannot be properly converted to float, the former will return x, whereas the latter will return 0.0.

I changed from using the operator * to the filter multiply() only for stylistic reasons. This should also work:

{{ (duration | float * 60) | round }}
1 Like

@pnbruckner is this still the case in 0.98.5?

I tried {{ (duration | float * 60) | round }} however, still seem to be getting the same error as described by @klogg

Any help with this will be appreciated.

I just tried with 0.98.5 and it seems to work as expected:

@pnbruckner thank you very much. I found the reasons behind my issues and why i’m not seeing any history stats.

One other question though and i’d really appreciate some help on this. I’m trying to change the input select from using “Daily” to selecting days of the week instead. Klogg wrote something really nice and appreciate the great share.

Below are just some information on his other post with the rest of the code:

All i’m asking is for some pointers or advise.

input_select:
cycle1_watering_days:
name: Morning 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

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

cycle1_enable:
name: Enable schedule
icon: mdi:pipe

input_datetime:
cycle1_next_run_time:
has_date: true
has_time: true

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

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

automation:

  • 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 %}

I think it is asking rather a lot of @pnbruckner to look through all that code!
(But feel free, I am sure you could improve it for me!)

I have answered this question where you asked it in the Garden Irrigation thread.

@klogg thats fine i only asked.

No problem, I didn’t mean you to take it the wrong way…

Please format the code correctly. I can’t provide feedback if I can’t read it properly. Also, are you just looking for general comments, or are you having some specific issue with the code, and if so, what?

I’m happy to provide feedback if I can. :slight_smile:

@pnbruckner Thank you very much. I’m just looking for some advice to change the “Daily” to days of the week. I really appreciate you willing to take a look. This is quite a bit of code so i’ll have to post it in 3 seperate posts. All three files have been saved in packages and has been included in my config.yaml file. I haven’t had time to take out some of the code that relate to the weather and irrigation information. I’m only interested in Cycle1 and to be able to select a day of the week.
image
image

GardenGlobals.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

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 %}

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...'

So I browsed through the code, and although I could probably fairly easily modify it to allow for selecting which days of the week to run the cycles, it’s clearly a bit involved. A bit more than I’d like to take on, especially since I’m not using it myself. :smile: And even suggesting what to do is a bit involved (and would require even a deeper understanding on my part.) So, sorry, I’m not going to be able to help with this one. Good luck!

@pnbruckner That’s fine man, thank you anyways.