Calculating and formatting a time calculation

Hi very bright peoples,
I am trying to set up a template calculation that starts my pool the correct number of hours before I want to go swimming.
So I have set up the variables:
In input_numbers.yaml

pool_desired_temp:
  name: Desired pool temp
  icon: mdi:thermometer-lines
  initial: 29
  min: 25
  max: 32
  step: 0.1
  mode: box

In input_boolean.yaml

pool_temp_calc:
  name: Calculate heating duration
  initial: off
  icon: mdi:calculator

In input_datetime.yaml

 pool_end_time:
   name: Pool end time
   has_date: false
   has_time: true

And in automations.yaml

- alias: Turn pool on at calculated time
  trigger:
    platform: template
    value_template: '{{ (as_timestamp(states.sensor.time.state)) == (as_timestamp(states.input_datetime.pool_end_time.state)) - (((states.input_number.pool_desired_temp ā€“ sensor.wirelesstag_pool_temp_temperature)/0.5)*3600) }}
  condition:
    condition: state
    entity_id: input_boolean.pool_temp_calc
    state: 'on'
  action:
    - service: switch.turn_on
      data:
        entity_id:
          - switch.fibaro_system_fgs223_double_relay_switch
          - switch.fibaro_system_fgs223_double_relay_switch_2

The 0.5 is used because the heater heats up the pool at 0.5 deg/hour.

I am turning my head into mush trying to work out how to calculate the trigger.
So far I think I have understood that:
ā€œstates.sensor.time.stateā€ is better to use than ā€œnow()ā€ because the latter is dependent on being triggered
ā€œas_timestamp()ā€ is needed to convert standard time formats into seconds so that times can be used in calculations.
However I just canā€™t work out how to get the calculation to work. I havenā€™t even been able to get ā€œas_timestamp(states.sensor.time.state)ā€ to work in the Template Editor. The result returned states ā€œNoneā€.

Thank you for your advice.

Did you configure sensor.time? Itā€™s based on the Time and Date integration which requires you to configure the sensor before it can become available for use.

Hello again @123 thank you for your help. I have sensor.time set up in sensors.yaml:

- platform: time_date
  display_options:
    - 'time'
    - 'date'
    - 'date_time'
    - 'time_date'
    - 'time_utc'
    - 'beat

It is working in another automation I use:

- alias: Turn pool on at set time
  trigger:
    platform: template
    value_template: '{{ states.sensor.time.state + ":00" == states.input_datetime.pool_start_time.state }}'
  condition:
    condition: state
    entity_id: input_boolean.start_pool_set_times
    state: 'on'
  action:
    - service: switch.turn_on
      data:
        entity_id:
          - switch.fibaro_system_fgs223_double_relay_switch
          - switch.fibaro_system_fgs223_double_relay_switch_2

And putting {{ states.sensor.time.state }} into the Template Editor gives a readable time in the format hh:mm.

Thanks again

In your second automation you are comparing strings. In the first you are trying to compare timestamps butā€¦

Put this in the developer tools template editor:

{{ as_timestamp(states.sensor.time.state) }}

You will see it returns None. Without the date you can not return a timestamp. You will have the same problem with the input_datetime.

I use the binary sensor definition below to trigger these sorts of automations. It adds todayā€™s date to the input_datetime and compares it to now(). The function now() wonā€™t trigger updates of the sensor template so the entity sensor.time is used for this. It updates the template every minute.

- platform: template
  sensors:
    pool_start_time_active:
      friendly_name: "Pool Start Time Active"
      entity_id:
      - sensor.time # required to update now()
      - input_datetime.pool_start_time
      value_template: >-
        {% set d = now().strftime("%Y-%m-%d ") %}
        {% set t = now().timestamp() %}
        {% set pool_time_active = strptime(d + states('input_datetime.pool_start_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {{ pool_time_active < t < ( pool_time_active + 300) }}

This binary sensor will be on for five minutes beginning at your input_datetime setting.

Be careful trying to use {{ pool_time_active = t }} as there may be a fraction of a second difference between the calculation of t and the calculation of the input_datetime timestamp. And timestamps include fractions of seconds.

Your automation trigger becomes:

- alias: Turn pool on at calculated time
  trigger:
    platform: state
    entity_id: binary_sensor.pool_start_time_active
    to: 'on'

FYI you can also use this template to check if the time is between a range set by two input_datetimes. e.g.

- platform: template
  sensors:
    dining_heating_am_automation_time_active:
      friendly_name: "Dining AM Automation Time Active"
      entity_id:
      - sensor.time # required to update now()
      - input_datetime.dining_am_on_time
      - input_datetime.dining_am_off_time
      value_template: >-
        {% set d = now().strftime("%Y-%m-%d ") %}
        {% set t = now().timestamp() %}
        {% set am_start = strptime(d + states('input_datetime.dining_am_on_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {% set am_end = strptime(d + states('input_datetime.dining_am_off_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {{ am_start <= t <= am_end }}
1 Like

The biggest thing to realize is that all states are strings.

date_time objects require that the strings be in a specific format before you can use them in a timestamp conversion.

the sensor.time isnā€™t a valid date_time string. neither is sensor.date_time.

However sensor.date_time_iso is a valid date_time string and can be converted using the as_timestamp() method.

And you can convert the sensor.time & sensor.date_time into valid date_time strings using strptime().

this might be a bit of help:

Then look at the real world example given by @tom_l above and you hopefully should be able to work something out.

Thanks very much and apologies for the delay, life has got in the way. I will get back to you guys when I am looking at HA again. This looks very helpful and I appreciate your input.

Thank you all for your input with helping me understand the time formatting.
I have now got the set up to work. There were a few bumps along the way - please bear in mind I am new to this and not a programmer.

Based on @tom_lā€™s advice I set up the sensor template as instructed. However the automation trigger would not work until I realised the entity was sensor.pool_start_time_active and not binary_sensor.pool_start_time_active. And then that the state was ā€˜trueā€™ rather than ā€˜onā€™. And after much head scratching (in fact the answer became apparent to me as I started this contribution which was going to be a request for more help) that the state has to be capitalised ie ā€˜Trueā€™.

Thank you @tom_l for your advice, and thank you @finity for pointing me to the comprehensive resource on formatting.

Here is the template sensor (with an extra 30 mins added on to the duration for ā€œsafetyā€:


    calc_pool_pump_active:
      friendly_name: "Calculated pool pump active"
      entity_id:
      - sensor.time # required to update now()
      - input_datetime.routine_pool_end_time
      - sensor.wirelesstag_pool_temp_temperature
      - input_number.pool_desired_temp
      value_template: >-
        {% set d = now().strftime("%Y-%m-%d ") %}
        {% set t = now().timestamp() %}
        {% set dur = ((((((states.input_number.pool_desired_temp.state | float) - (states.sensor.wirelesstag_pool_temp_temperature.state | float ))/0.5)) * 3600) | int ) + (30*60) %}
        {% set pool_start = strptime(d + states('input_datetime.routine_pool_end_time'), '%Y-%m-%d %H:%M:%S').timestamp() - dur %}
        {% set pool_end = strptime(d + states('input_datetime.routine_pool_end_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {{ pool_start <= t <= pool_end }}

Here is the automation:

#Pool pump turns on at routine_pool_start_time
- alias: "Pool pump on"
  trigger:
    platform: state
    entity_id: sensor.calc_pool_pump_active
    to: 'True'
  action:
    - service: switch.turn_on
      data:
        entity_id: switch.fibaro_system_fgs223_double_relay_switch_2
    - service: switch.turn_on
      data:
        entity_id: switch.fibaro_system_fgs223_double_relay_switch
    - service: notify.paul_dev_email
      data_template:
        title: 'Pool Notification'
        message: "Pool pump turned on.  Pool temp is {{states('sensor.wirelesstag_pool_temp_temperature')}}"
    - service: notify.paul_pushbullet
      data:
        message: 'Pool pump turned on'

Thanks again wise people!

Hello again!
I woke up this morning thinking how I could build into the calculation of how long the pool pump has to be on to raise the temperature to the desired level a variable that reflected how the external temperature affected the speed of raising the temperature.
I estimate that when the external temperature is above 20deg, the rate of rise in the pool is 0.5deg/hour. Between 10 and 20deg it is 0.35deg/hour. And below that it is 0.2deg/hour.
I have tried the following:

    calc_pool_pump_active:
      friendly_name: "Calculated pool pump active"
      entity_id:
      - sensor.time # required to update now()
      - input_datetime.routine_pool_end_time
      - sensor.wirelesstag_pool_temp_temperature
      - input_number.pool_desired_temp
      - sensor.nibe_47186_40004 #outdoor temp
      value_template: >-
        {% set d = now().strftime("%Y-%m-%d ") %}
        {% set t = now().timestamp() %}
        {% set p = (if(sensor.nibe_47186_40004 | float) > 20) %}
          '0.5'
        {% elif(10 <= (sensor.nibe_47186_40004 | float) <= 20) %}
          '0.35'
        {% else %}
          '0.2'
        {% endif %}
        {% set dur = ((((((states.input_number.pool_desired_temp.state | float) - (states.sensor.wirelesstag_pool_temp_temperature.state | float ))/(p | float))) * 3600) | int ) + (30*60) %}
        {% set pool_start = strptime(d + states('input_datetime.routine_pool_end_time'), '%Y-%m-%d %H:%M:%S').timestamp() - dur %}
        {% set pool_end = strptime(d + states('input_datetime.routine_pool_end_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {{ pool_start <= t <= pool_end }}

But the added part does not work. I have tried to put it into the template tool, but I get an error Error rendering template: TemplateSyntaxError: Encountered unknown tag 'elif'.

        {% set p = (if(sensor.nibe_47186_40004 | float) > 20) %}
          '0.5'
        {% elif(10 <= (sensor.nibe_47186_40004 | float) <= 20) %}
          '0.35'
        {% else %}
          '0.2'
        {% endif %}

Thank you very much for your help.

I think this is what you want:

        {% if states('sensor.nibe_47186_40004')|float > 20 %}
          {% set p = 0.5 %}
        {% elif 10 <= states('sensor.nibe_47186_40004')|float <= 20 %}
          {% set p = 0.35 %}
        {% else %}
          {% set p = 0.2 %}
        {% endif %}

Streamlined version:

{% set n = states('sensor.nibe_47186_40004')|float %}
{% set p = 0.5 if n > 20 else 0.35 if 10 <= n <= 20 else 0.2 %}
1 Like

I have had a little time to look at HA and thanks to your help I have the following running successfully.
In sensors.yaml:

    calc_pool_pump_active:
      friendly_name: "Calculated pool pump active"
      entity_id:
      - sensor.time # required to update now()
      - input_datetime.routine_pool_end_time
      - sensor.wirelesstag_pool_temp_temperature
      - input_number.pool_desired_temp
      - sensor.outside_temp
      value_template: >-
        {% set d = now().strftime("%Y-%m-%d ") %}
        {% set t = now().timestamp() %}
        {% set n = states('sensor.outside_temp')|float %}
        {% set p = 0.5 if n > 20 else 0.25 if 10 <= n <= 20 else 0.2 %}
        {% set dur = ((((((states.input_number.pool_desired_temp.state | float) - (states.sensor.wirelesstag_pool_temp_temperature.state | float ))/(p | float))) * 3600) | int ) + (30*60) %}
        {% set pool_start = strptime(d + states('input_datetime.routine_pool_end_time'), '%Y-%m-%d %H:%M:%S').timestamp() - dur %}
        {% set pool_end = strptime(d + states('input_datetime.routine_pool_end_time'), '%Y-%m-%d %H:%M:%S').timestamp() %}
        {{ pool_start <= t <= pool_end }}

In automation.yaml:

#Pool pump turns on when calc_pool_pump_active
- alias: "Set pool for swimming today"
  initial_state: 'true'
  trigger:
    platform: state
    entity_id: sensor.calc_pool_pump_active
    to: 'True'
  condition:
    condition: and
    conditions:
      - condition: state
        entity_id: input_boolean.automated_pool_pump
        state: 'on'
      - condition: template
        value_template: >-
          {% set today = now().strftime('%a')|lower %}
          {{ is_state('input_select.pool_pump_'~today, 'swimming') }}
  action:
    - service: switch.turn_on
      data:
        entity_id: switch.fibaro_system_fgs223_double_relay_switch_2
    - service: switch.turn_on
      data:
        entity_id: switch.fibaro_system_fgs223_double_relay_switch
    - service: notify.paul_dev_email
      data_template:
        title: 'Pool Notification'
        message: "Pool pump turned on.  Pool temp is {{states('sensor.wirelesstag_pool_temp_temperature')}}"
    - service: notify.paul_pushbullet
      data_template:
        message: "Pool pump turned on.  Pool temp is {{states('sensor.wirelesstag_pool_temp_temperature')}}"

Thanks again for your help!

1 Like

I have tidied up the template sensor and added explanations of what is being done. Remove the explanations after value_template: >- : because all the comments were included in the result causing the sensor to show in HA as ā€œUnknownā€.

    calc_pool_pump_active:
      friendly_name: "Calculated pool pump active"
      entity_id:
      - sensor.time # required to update now()
      - input_datetime.routine_pool_end_time
      - sensor.wirelesstag_pool_temp_temperature
      - input_number.pool_desired_temp
      - sensor.outside_temp
      value_template: >-
        {% set d = now().strftime("%Y-%m-%d ") %} #set today's date
        {% set t = now().timestamp() %} #set now as timestamp (number of seconds since epoch i.e. January 1, 1970)
        {% set n = states('sensor.outside_temp')|float %} #set the outside temp as a number
        {% set p = 0.45 if n > 5 else 0.3 %} #set the rate of temp rise (with the heating running) in deg/hour depending on the outside temp
        {% set dur = ((states.input_number.pool_desired_temp.state | float - states.sensor.wirelesstag_pool_temp_temperature.state | float )/(p) * 3600) | int %} #set the number of seconds for pool to get to the desired temp
        {% set pool_start = strptime(d ~ states('input_datetime.routine_pool_end_time'), '%Y-%m-%d %H:%M:%S').timestamp() - dur %} #add today's date to the pool start time and express as a timestamp
        {% set pool_end = strptime(d ~ states('input_datetime.routine_pool_end_time'), '%Y-%m-%d %H:%M:%S').timestamp() %} #add today's date to the pool end time and express as a timestamp
        {{ pool_start <= t <= pool_end }} #compare now with the pool start and pool end times with the result either 'True' or 'False'

There are places in your template where you are concatenating strings and using the plus operator (+) to do it.

The plus operator is for performing numeric addition but if the values arenā€™t numeric it performs a string concatenation. The use of a plus operator in this manner can sometimes lead to an unexpected result . Thatā€™s why thereā€™s another operator, the tilde (~), used for the sole purpose of concatenating strings.

Instead of this:

d + states('input_datetime.routine_pool_end_time')

do this:

d ~ states('input_datetime.routine_pool_end_time')
1 Like

Thank you @123 for the explanation of + and ~. I have edited my previous reply to reflect this. That helps me understand what is going on in the d + states('input_datetime.routine_pool_end_time') part of the expression ie strings rather than numbers.

Good morning/day/evening to anyone reading this.

My plan is to have the heater turn on automatically two hours before my (or my gfs) alarm rings.
Iā€™m getting that value from the app.

What I tried so far:

{{ 
states.sensor.lenovo_tb_j616x_next_alarm
- 
states.input_datetime.2 # helper with 2 hours
}}

TypeError: unsupported operand type(s) for -: 'TemplateState' and 'TemplateState'

Doesnā€™t work and gives me the error I posted.
I tried a ton from here, canā€™t get it to work even with localtime, etc.

In the end my plan would be:

If person.hans is true and states.sensor.lenovo_tb_j616x_next_alarm < states.sensor.in2023_next_alarm
set helper to states.sensor.lenovo_tb_j616x_next_alarm - 2 hours
elseif person.gretel is true and states.sensor.in2023_next_alarm < states.sensor.lenovo_tb_j616x_next_alarm
set helper to states.sensor.in2023_next_alarm - 2 hours
else set helper to false

A couple of things to note:

  • Access the state of an entity with e.g. {{ states('sensor.lenovo_tb_j616x_next_alarm') }}
  • All states are strings and may need converting to datetime objects, numbers etc in order to process them

With that, and using the docs, have another go and come back with at least partially-working code if you get stuck. Weā€™ll also need to know what the states of the sensors youā€™re using look like, either from the template editor or with screenshots like this:

1 Like

That got me there, thanks!

{{ 
as_local(as_datetime(states.sensor.lenovo_tb_j616x_next_alarm.state)) - timedelta(hours = 2)
}}