Time range condition template

{% if 'Cool' not in states.input_select.lounge_ac_mode.state %}

or

{% if (states.input_select.lounge_ac_mode.state not in ['Normal Cool','Silent Cool','Powerful Cool'] %}
1 Like

As my input_datetime values have no date I went with doing the check in seconds as per the suggestion from @petro.

  - condition: or # And only if current time is within *active* AM or PM automation time ranges
    conditions:
      - condition: template
        value_template: >
          {% set no_date_timestamp = now().second + (now().minute * 60) + (now().hour * 3600) %}
          '{{ (states.input_datetime.lounge_ac_am_on_time.attributes.timestamp <= no_date_timestamp < states.input_datetime.lounge_ac_am_off_time.attributes.timestamp) and states.input_boolean.lounge_ac_am_automation.state == 'on'}}'

      - condition: template
        value_template: >
          {% set no_date_timestamp = now().second + (now().minute * 60) + (now().hour * 3600) %}
          '{{ (states.input_datetime.lounge_ac_pm_on_time.attributes.timestamp <= no_date_timestamp < states.input_datetime.lounge_ac_pm_off_time.attributes.timestamp) and states.input_boolean.lounge_ac_pm_automation.state == 'on'}}'

And itā€™s still not working even though it all evaluates fine in the template editor.

I ended up making binary template sensors and using those in the conditions:

- platform: template
  sensors:
    lounge_ac_am_automation_time_active:
      friendly_name: "Lounge AC AM Automation Time Active"
      value_template: >-
        {{ (now().second + (now().minute * 60) + (now().hour * 3600)) >= states.input_datetime.lounge_ac_am_on_time.attributes.timestamp
           and (now().second + (now().minute * 60) + (now().hour * 3600)) <= states.input_datetime.lounge_ac_am_off_time.attributes.timestamp
           and is_state('input_boolean.lounge_ac_am_automation', 'on')}}
    lounge_ac_pm_automation_time_active:
      friendly_name: "Lounge AC PM Automation Time Active"
      value_template: >-
        {{ (now().second + (now().minute * 60) + (now().hour * 3600)) >= states.input_datetime.lounge_ac_pm_on_time.attributes.timestamp
           and (now().second + (now().minute * 60) + (now().hour * 3600)) <= states.input_datetime.lounge_ac_pm_off_time.attributes.timestamp
           and is_state('input_boolean.lounge_ac_pm_automation', 'on')}}

This seems to work. I suspect the reason why it wasnā€™t working in the condition_template has something to do with the quotes I had around the template. Iā€™m going to keep it this way rather than fiddling further as the binary template sensors simplify the code as I use them as conditions and triggers in more than one place.

EDIT 3: nope. god damn now() doesnā€™t update templates. Not only that but changing the other sensors in the template are hit and miss too.

Iā€™ve spent over a day working on this and am no closer to finding a solution. It souldnā€™t be this hard to tell if a time falls within a range.

EDIT 4: After a day and a half I gave up. Iā€™ve worked around it with four automations and two input_booleans.

- id: lounge_ac_set_am_automation_time_active # stupid workaround for lack of time only timestamp
  alias: 'Lounge AC Set AM Automation Time Active'
  hide_entity: true
  trigger:
    platform: template
    value_template: "{{ states('sensor.time') == (states.input_datetime.lounge_ac_am_on_time.state[0:5]) }}"
  condition:
    condition: state
    entity_id: input_boolean.lounge_ac_am_automation # Only if automation is required
    state: 'on'
  action:
    service: input_boolean.turn_on
    data:
      entity_id: input_boolean.lounge_ac_am_automation_time_active

- id: lounge_ac_reset_am_automation_time_active
  alias: 'Lounge AC Reset AM Automation Time Active'
  hide_entity: true
  trigger:
    platform: template
    value_template: "{{ states('sensor.time') == (states.input_datetime.lounge_ac_am_off_time.state[0:5]) }}"
  condition:
    condition: state
    entity_id: input_boolean.lounge_ac_am_automation # Only if automation is required
    state: 'on'
  action:
    service: input_boolean.turn_off
    data:
      entity_id: input_boolean.lounge_ac_am_automation_time_active

- id: lounge_ac_set_pm_automation_time_active
  alias: 'Lounge AC Set PM Automation Time Active'
  hide_entity: true
  trigger:
    platform: template
    value_template: "{{ states('sensor.time') == (states.input_datetime.lounge_ac_pm_on_time.state[0:5]) }}"
  condition:
    condition: state
    entity_id: input_boolean.lounge_ac_pm_automation # Only if automation is required
    state: 'on'
  action:
    service: input_boolean.turn_on
    data:
      entity_id: input_boolean.lounge_ac_pm_automation_time_active

- id: lounge_ac_reset_pm_automation_time_active
  alias: 'Lounge AC Reset PM Automation Time Active'
  hide_entity: true
  trigger:
    platform: template
    value_template: "{{ states('sensor.time') == (states.input_datetime.lounge_ac_pm_off_time.state[0:5]) }}"
  condition:
    condition: state
    entity_id: input_boolean.lounge_ac_pm_automation # Only if automation is required
    state: 'on'
  action:
    service: input_boolean.turn_off
    data:
      entity_id: input_boolean.lounge_ac_pm_automation_time_active

Sorry, been out of town. Iā€™ll take a look in the morning.

I havenā€™t read the whole topic, but to this one point, if you want to use now() in a template binary_sensor/sensor, you can just explicitly list all the entities the template references (use the entity_id config parameter), and add sensor.time to the list (assuming youā€™ve enabled that sensor.) That will force the template to update once a minute. Again, I didnā€™t read the whole topic, so there may be a more efficient way to do what you want. @petro has been helping you with this, so in general youā€™re in good hands. :slight_smile:

Thanks @petro. No hurry, I have a working (if somewhat inelegant) workaround with the automations and input_booleans.

Thatā€™s definitely worth investigating. Thanks.

So is there any reason you didnā€™t try this template?

condition:
  - condition: template
    value_template: >
      {% set current_date_string = now().strftime("%Y-%m-%d ") %}
      {% set current_time = now().timestamp() %}
      {% set lounge_am_start = strptime(date+state('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M').timestamp() %}
      {% set lounge_am_end = strptime(date+state('input_datetime.lounge_ac_am_off_time'), '%Y-%m-%d %H:%M').timestamp() %}
      {% set lounge_pm_start = strptime(date+state('input_datetime.lounge_ac_pm_on_time'), '%Y-%m-%d %H:%M').timestamp() %}
      {% set lounge_pm_end = strptime(date+state('input_datetime.lounge_ac_pm_off_time'), '%Y-%m-%d %H:%M').timestamp() %}
      {{ lounge_am_start <= current_time <= lounge_am_end or lounge_pm_start <= current_time <= lounge_pm_end }}

Also, pretty much the reason most of your posts arenā€™t working is because you are treating everything as a string instead of a number.

These attributes:

states.input_datetime.lounge_ac_am_on_time.attributes.timestamp

always return strings. Pretty much everything coming back from home assistant is a string. In some cases, it will properly convert it for you but that is very unlikely.

Second, I recommend avoiding grabbing from the objects directly unless you have to. Instead of using:

states.input_datetime.lounge_ac_am_on_time.attributes.timestamp

use:

state_attr('input_datetime.lounge_ac_am_on_time','timestamp') 

Also, I believe the timestamps on your input_datetimes actually indlude todayā€™s date. Otherwise the timestamps would be pointless. Which brings us back to using the following condition:

condition:
  - condition: template
    value_template: >
      {% set current_time = now().timestamp() %}
      {% set am_start = states('input_datetime.lounge_ac_am_on_time', 'timestamp') | int %}
      {% set am_end = states('input_datetime.lounge_ac_am_on_time', 'timestamp') | int %}
      {% set pm_start = states('input_datetime.lounge_ac_am_on_time', 'timestamp') | int %}
      {% set pm_end = states('input_datetime.lounge_ac_am_on_time', 'timestamp') | int %}
      {{ am_start <= current_time <= am_end or pm_start <= current_time <= pm_end }}

But, it looks like youā€™re having trouble finding a trigger? So I need to ask, what is your overall goal with this automation? Are you trying to turn on an AC mode at a certain time each day?

Iā€™m thinking you need 2 template sensors, one for am and one for pm. Then your automation will trigger off that turning on and off. If thatā€™s the case, then you just need to set that up:

sensor:
    # This platform turns on the datetime sensor.  It has the name sensor.date__time because
    # whoever set this up decided to convert 'date & time' to the name which produces
    # 2 underscores... annoying.
  - platform: time_date
    display_options:
      - 'date_time'
binary_sensor:
  - platform: template
    sensors:
      lounge_ac_am:
        value_template: >
          {% set current = strptime(states('sensor.date__time'), '%Y-%m-%d %H:%M').timestamp() %}
          {% set start = states('input_datetime.lounge_ac_am_on_time', 'timestamp') | int %}
          {% set end = states('input_datetime.lounge_ac_am_off_time', 'timestamp') | int %}
          {{ is_state('input_boolean.lounge_ac_am_automation','on') and start <= current <= end }}
      lounge_ac_pm:
        value_template: >
          {% set current = strptime(states('sensor.date__time'), '%Y-%m-%d %H:%M').timestamp() %}
          {% set start = states('input_datetime.lounge_ac_pm_on_time', 'timestamp') | int %}
          {% set end = states('input_datetime.lounge_ac_pm_off_time', 'timestamp') | int %}
          {{ is_state('input_boolean.lounge_ac_pm_automation','on') and start <= current <= end }}

Now all you need to do is build 4 automations:

trigger:
  - platform: state
    entity_id: binary_sensor.lounge_ac_am
    to: 'on'
    from: 'off'
trigger:
  - platform: state
    entity_id: binary_sensor.lounge_ac_am
    to: 'off'
    from: 'on'
trigger:
  - platform: state
    entity_id: binary_sensor.lounge_ac_pm
    to: 'on'
    from: 'off'
trigger:
  - platform: state
    entity_id: binary_sensor.lounge_ac_pm
    to: 'off'
    from: 'on'

Iā€™m sure that can be shortened depending on your end goal as well.

I did actually try casting the timestamps as integers. It made no difference.

No they really only have a time. I have defined them this way so that they can be applied to any day, e.g.

lounge_ac_pm_on_time:
  name: PM on time
  has_date: false
  has_time: true

e.g. {{ state_attr(ā€˜input_datetime.lounge_ac_am_on_timeā€™,ā€˜timestampā€™) }} returns 21600 when set to 06:00 (6x60x60 seconds).

During a time range (e.g. AM_on time to AM_off time) if the temperature of the room exceeds a trip temperature, cool the room, or if the temperature falls below another tirip piont heat the room. I originally had it set up to just check the temperature at the on time and turn the AC off at the off time. But often the temperature was falling (or rising) at the trigger time and the heating or cooling needed to come on a short time later. I have a bit of logic to stop oscillation between heat/cool or on/off too. Hereā€™s whatā€™s working (using the automations and input booleans I talked about in my last post:

- id: lounge_aircon_auto_on
  alias: 'Lounge Room Aircon Scheduled On'
  hide_entity: false
  trigger:
    - platform: state # AM on time trigger
      entity_id: input_boolean.lounge_ac_am_automation_time_active
      to: 'on'
    - platform: state # PM on time trigger
      entity_id: input_boolean.lounge_ac_pm_automation_time_active
      to: 'on'
    - platform: state # also trigger if heating is required during on time (condition below)
      entity_id: binary_sensor.lounge_ac_heat_required
      to: 'on'
    - platform: state # also trigger if cooling is required during on time (condition below)
      entity_id: binary_sensor.lounge_ac_cool_required
      to: 'on'
  condition:
    condition: and
    conditions:
      - condition: state # Only if AC is off (prevents oscillation between heat and cool mode if temperature over/undershoots set point)
        entity_id: input_select.lounge_ac_mode
        state: 'Off'
      - condition: or # And only if room heating or cooling is required (for the case of a time trigger)
        conditions:
          - condition: state
            entity_id: binary_sensor.lounge_ac_heat_required
            state: 'on'
          - condition: state
            entity_id: binary_sensor.lounge_ac_cool_required
            state: 'on'
      - condition: or # And only if current time is within *active* AM or PM automation time ranges (for the case of a temperature trigger)
        conditions:
          - condition: state
            entity_id: input_boolean.lounge_ac_am_automation_time_active
            state: 'on'
          - condition: state
            entity_id: input_boolean.lounge_ac_pm_automation_time_active
            state: 'on'
      - condition: or # And only if workday not tested or if is a workday
        conditions:
          - condition: state
            entity_id: input_boolean.lounge_ac_workday
            state: 'off'
          - condition: state
            entity_id: binary_sensor.workday_sensor
            state: 'on'
  action:
    - service: input_select.select_option
      data_template:
        entity_id: input_select.lounge_ac_mode
        option: >
          {% if states.sensor.aeotec_lounge_temperature.state < states.input_number.lounge_ac_heat_temp_set.state  %}
            Normal Heat
          {% elif states.sensor.aeotec_lounge_temperature.state > states.input_number.lounge_ac_cool_temp_set.state  %}
            Normal Cool
          {% endif %}

Iā€™ll have a look at the rest of your post and suggestions later. Too worn out to think tonight.

Thank you for taking the time to help with this.

Ok, so if this is true:

Then you have to use this method with all your datetimes:

{% set cdate = now().strftime("%Y-%m-%d ") %}
{% set ctime = now().timestamp() %}
{% set am_start = strptime(cdate + states('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M').timestamp() %}
{% set am_end = strptime(cdate + states('input_datetime.lounge_ac_am_off_time'), '%Y-%m-%d %H:%M').timestamp() %}
{% set pm_start = strptime(cdate + states('input_datetime.lounge_ac_pm_on_time'), '%Y-%m-%d %H:%M').timestamp() %}
{% set pm_end = strptime(cdate + states('input_datetime.lounge_ac_pm_off_time'), '%Y-%m-%d %H:%M').timestamp() %}

Which leads to this automation. I optimized and removed unnecessary conditions that you had and simplified your triggers. Also added safety in your action:

- id: lounge_aircon_auto_on
  alias: 'Lounge Room Aircon Scheduled On'
  hide_entity: false
  trigger:
    - platform: state # AM on time trigger
      entity_id: 
        - binary_sensor.lounge_ac_heat_required
        - binary_sensor.lounge_ac_cool_required
      to: 'on'
  condition:
    - condition: state # Only if AC is off (prevents oscillation between heat and cool mode if temperature over/undershoots set point)
      entity_id: input_select.lounge_ac_mode
      state: 'off'
    - condition: template
      value_template: >
        {% set d = now().strftime("%Y-%m-%d ") %}
        {% set t = now().timestamp() %}
        {% set am_start = strptime(d + states('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M').timestamp() %}
        {% set am_end = strptime(d + states('input_datetime.lounge_ac_am_off_time'), '%Y-%m-%d %H:%M').timestamp() %}
        {% set pm_start = strptime(d + states('input_datetime.lounge_ac_pm_on_time'), '%Y-%m-%d %H:%M').timestamp() %}
        {% set pm_end = strptime(d + states('input_datetime.lounge_ac_pm_off_time'), '%Y-%m-%d %H:%M').timestamp() %}
        {{ am_start <= t <= am_end or pm_start <= t <= pm_end }}
    - condition: or # And only if workday not tested or if is a workday
      conditions:
        - condition: state
          entity_id: input_boolean.lounge_ac_workday
          state: 'off'
        - condition: state
          entity_id: binary_sensor.workday_sensor
          state: 'on'
  action:
    - condition: template
      value_template: >
        # verify that the temperature is actually different so that the option in the next
        # action is always populated.
        {{ states.sensor.aeotec_lounge_temperature.state != states.input_number.lounge_ac_heat_temp_set.state }}
    - service: input_select.select_option
      data_template:
        entity_id: input_select.lounge_ac_mode
        option: >
          {% if states.sensor.aeotec_lounge_temperature.state < states.input_number.lounge_ac_heat_temp_set.state  %}
            Normal Heat
          {% elif states.sensor.aeotec_lounge_temperature.state > states.input_number.lounge_ac_cool_temp_set.state  %}
            Normal Cool
          {% endif %}

Sorry to keep butting in, but I just wanted to let you know this is not true. State strings are definitely always strings, no matter what they might look like. But attributes can be other types, such as int, float, boolean, datetime, dictionaries, lists, tuples, etc. E.g., the timestamp attribute of an input_datetime is definitely not a string:

And theyā€™re not getting converted by anything. They are just their raw Python type. When states are set into the state machine, the ā€œstateā€ is definitely converted to a string, but the attributes are left whatever they were.

2 Likes

Ah. Now I remember why I didnā€™t use this:

In the template editor this:

{% state('input_datetime.lounge_ac_am_on_time') %}

Returns:

Error rendering template: TemplateSyntaxError: Encountered unknown tag 'state'.

I follow your logic and it is amazing except for this one problem.

Goodnight for real this time. :sleeping:

Because state is a typo, it should be states()

anyways, use this:

- id: lounge_aircon_auto_on
  alias: 'Lounge Room Aircon Scheduled On'
  hide_entity: false
  trigger:
    - platform: state # AM on time trigger
      entity_id: 
        - binary_sensor.lounge_ac_heat_required
        - binary_sensor.lounge_ac_cool_required
      to: 'on'
  condition:
    - condition: state # Only if AC is off (prevents oscillation between heat and cool mode if temperature over/undershoots set point)
      entity_id: input_select.lounge_ac_mode
      state: 'off'
    - condition: template
      value_template: >
        {% set d = now().strftime("%Y-%m-%d ") %}
        {% set t = now().timestamp() %}
        {% set am_start = strptime(d + states('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M').timestamp() %}
        {% set am_end = strptime(d + states('input_datetime.lounge_ac_am_off_time'), '%Y-%m-%d %H:%M').timestamp() %}
        {% set pm_start = strptime(d + states('input_datetime.lounge_ac_pm_on_time'), '%Y-%m-%d %H:%M').timestamp() %}
        {% set pm_end = strptime(d + states('input_datetime.lounge_ac_pm_off_time'), '%Y-%m-%d %H:%M').timestamp() %}
        {{ am_start <= t <= am_end or pm_start <= t <= pm_end }}
    - condition: or # And only if workday not tested or if is a workday
      conditions:
        - condition: state
          entity_id: input_boolean.lounge_ac_workday
          state: 'off'
        - condition: state
          entity_id: binary_sensor.workday_sensor
          state: 'on'
  action:
    - condition: template
      value_template: >
        # verify that the temperature is actually different so that the option in the next
        # action is always populated.
        {{ states.sensor.aeotec_lounge_temperature.state != states.input_number.lounge_ac_heat_temp_set.state }}
    - service: input_select.select_option
      data_template:
        entity_id: input_select.lounge_ac_mode
        option: >
          {% if states.sensor.aeotec_lounge_temperature.state < states.input_number.lounge_ac_heat_temp_set.state  %}
            Normal Heat
          {% elif states.sensor.aeotec_lounge_temperature.state > states.input_number.lounge_ac_cool_temp_set.state  %}
            Normal Cool
          {% endif %}

Hi petro,

In the template editor this:

        {% set d = now().strftime("%Y-%m-%d ") %}
        {% set am_start = strptime(d + states('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M').timestamp() %}

returns the following error:

Error rendering template: UndefinedError: ā€˜str objectā€™ has no attribute ā€˜timestampā€™

What does this return?

{{ states('input_datetime.lounge_ac_am_on_time') }}

Seems like there is a typo somewhere because when I replace that state in my template, i get an answer. Are you sure you have the correct entity_id? Are you sure that state returns ā€œHH:MMā€ time format?

{{ states('input_datetime.lounge_ac_am_on_time') }}

returns: 06:00:00

{{ strptime( states('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M') }}

Also returns: 06:00:00

Keep in mind that the strptime function implementation in jinja returns the first string, instead of a datetime object, if the first string doesnā€™t match the second (format) string.

Ok so

 {% set d = now().strftime("%Y-%m-%d ") %}
        {% set am_start = strptime(d + states('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M') %}

{{ am_start }}

Returns: 2018-09-18 06:00:00

But what does:

 {% set d = now().strftime("%Y-%m-%d ") %}
        {% set am_start = strptime(d + states('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M') %}

{{ am_start }}
{{ am_start is string }}

return?

The issue seems to be your input_datetime is returning HH:MM:SS instead of HH:MM, which is weird, because I always thought they only returned HH:MM.

EDIT: Ok, my bad! input_datetimeā€™s always return HH:MM:SS for the time part. I was thinking of sensor.time which only returns HH:MM.

So the problem is your format string. It needs to be '%Y-%m-%d %H:%M:%S'.

2018-09-18 06:00:00
True

I definitely only supply HH:MM in the frontend, but itā€™s states are:

<template state input_datetime.lounge_ac_am_on_time=06:00:00; has_date=False, has_time=True, hour=6, minute=0, second=0, timestamp=21600, friendly_name=AM on time @ 2018-09-17T23:05:10.040873+10:00>

So they add secondsā€¦ this should be used then:

{% set d = now().strftime("%Y-%m-%d ") %}
{% set am_start = strptime(d + states('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}

which leads to this:

- id: lounge_aircon_auto_on
  alias: 'Lounge Room Aircon Scheduled On'
  hide_entity: false
  trigger:
    - platform: state # AM on time trigger
      entity_id: 
        - binary_sensor.lounge_ac_heat_required
        - binary_sensor.lounge_ac_cool_required
      to: 'on'
  condition:
    - condition: state # Only if AC is off (prevents oscillation between heat and cool mode if temperature over/undershoots set point)
      entity_id: input_select.lounge_ac_mode
      state: 'off'
    - condition: template
      value_template: >
        {% set d = now().strftime("%Y-%m-%d ") %}
        {% set t = now().timestamp() %}
        {% set am_start = strptime(d + states('input_datetime.lounge_ac_am_on_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {% set am_end = strptime(d + states('input_datetime.lounge_ac_am_off_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {% set pm_start = strptime(d + states('input_datetime.lounge_ac_pm_on_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {% set pm_end = strptime(d + states('input_datetime.lounge_ac_pm_off_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {{ am_start <= t <= am_end or pm_start <= t <= pm_end }}
    - condition: or # And only if workday not tested or if is a workday
      conditions:
        - condition: state
          entity_id: input_boolean.lounge_ac_workday
          state: 'off'
        - condition: state
          entity_id: binary_sensor.workday_sensor
          state: 'on'
  action:
    - condition: template
      value_template: >
        # verify that the temperature is actually different so that the option in the next
        # action is always populated.
        {{ states.sensor.aeotec_lounge_temperature.state != states.input_number.lounge_ac_heat_temp_set.state }}
    - service: input_select.select_option
      data_template:
        entity_id: input_select.lounge_ac_mode
        option: >
          {% if states.sensor.aeotec_lounge_temperature.state < states.input_number.lounge_ac_heat_temp_set.state  %}
            Normal Heat
          {% elif states.sensor.aeotec_lounge_temperature.state > states.input_number.lounge_ac_cool_temp_set.state  %}
            Normal Cool
          {% endif %}