Heads up! Upcoming breaking change in the Template integration

Worse case is that you move the sensors in the template. When I get on a pc I can rejigger it for ya.

1 Like

how can we test this locally?

are you saying the new system will also understand this:

      time_until_next_alarm:
        friendly_name: Time until next alarm
        entity_id:
          - sensor.time
          - sensor.next_alarm_timestamp
          - input_boolean.alarmclock_wd_enabled
          - input_boolean.alarmclock_we_enabled
          - input_number.alarmclock_wd_hour
          - input_number.alarmclock_wd_minute
          - input_number.alarmclock_we_hour
          - input_number.alarmclock_we_minute
        value_template: >
          {% set timestamp = states('sensor.next_alarm_timestamp')|int %}
          {% if timestamp %}
            {% set delta = timestamp-now().replace(second=0).replace(microsecond=0).timestamp() %}
            {% if delta/3600 < 1 %} {{delta|timestamp_custom('%-M',False)}}
            {% elif delta/86400 <1 %} {{delta|timestamp_custom('%-H:%-M',False)}}
            {% else %} {{(delta// 86400)|int}}:{{delta|timestamp_custom('%-H:%-M',False)}}
            {% endif %}
          {% else %}
            Not set, relax
          {% endif %}

where all of the above listed entity_id’s which influence sensor.next_alarm_timestamp are found without explicitly listing them anymore?

      irrigation_cycle1_duration_in_seconds:
        friendly_name: Cycle 1 Duration In Seconds
        value_template: >
          {% set zones = [
            states.sensor.irrigation_cycle1_zone1_actual_duration_in_seconds,
            states.sensor.irrigation_cycle1_zone2_actual_duration_in_seconds,
            states.sensor.irrigation_cycle1_zone3_actual_duration_in_seconds,
            states.sensor.irrigation_cycle1_zone4_actual_duration_in_seconds,
            states.sensor.irrigation_cycle1_zone5_actual_duration_in_seconds,
            states.sensor.irrigation_cycle1_zone6_actual_duration_in_seconds,
            states.sensor.irrigation_cycle1_zone7_actual_duration_in_seconds,
            states.sensor.irrigation_cycle1_zone8_actual_duration_in_seconds,
            ] %}
          {% set threshold = states('input_number.irrigation_number_of_zones') | int %}
          {{ zones | map(attribute='state') | map('int') | select('<', threshold) | list | sum }}

Might want to test it in the template tester… haven’t verified that it’s correct.

You shouldn’t have needed those for that because the template is only based on states(‘sensor.next_alarm_timestamp’). As long as states(‘sensor.next_alarm_timestamp’) updates properly, then you should be good.

yes, Thats what we thought at the time. However, time has taught updating of these sensors was rather peculiar, and had me add entity_id’s consecutively. Oddly enough, the sensor.next_alarm_timestamp itself already needs these same entity_id’s listed, even while these are in the template itself, except the sensor.time.

this has been the case since sometime ago, I could give it (taking out the entity_id’s) another try in the current HA.

of course, the timestamp sensor is quite the one:

  - platform: template
    sensors:
      next_alarm_timestamp:
        friendly_name: Next alarm timestamp
        entity_id:
          - sensor.time
          - input_boolean.alarmclock_wd_enabled
          - input_boolean.alarmclock_we_enabled
          - input_number.alarmclock_wd_hour
          - input_number.alarmclock_wd_minute
          - input_number.alarmclock_we_hour
          - input_number.alarmclock_we_minute
        value_template: >
          {% macro getalarm(offsetday=0) %}
          {% set day = (now().weekday() + offsetday) % 7 %}
          {% set offset = offsetday * 86400 %}
          {% set fmat = 'd' if day in range(5) else 'e' %}
          {% set hour = 'input_number.alarmclock_w{}_hour'.format(fmat) %}
          {% set minute = 'input_number.alarmclock_w{}_minute'.format(fmat) %}
          {% set alarm = states(hour)|float|multiply(3600) + states(minute)| float|multiply(60) %}
          {% set time = now().hour * 3600 + now().minute * 60 %}
          {{(offset - time) + alarm if offsetday else alarm - time}}
          {% endmacro %}

          {% set weekday = is_state('input_boolean.alarmclock_wd_enabled','on') %}
          {% set weekend = is_state('input_boolean.alarmclock_we_enabled','on') %}

          {% set weekdays = [0,1,2,3,4] %}
          {% set weekends = [5,6] %}
          {% set day = now().weekday() %}
          {% set nextday = day + 1 if day != 6 else 0 %}
          {% if nextday in weekdays %}
            {% if weekday %}
              {% set offsetday = nextday %}
            {% elif weekend %}
              {% set offsetday = weekends[0] %}
            {% else %}
              {% set offsetday = None %}
            {% endif %}
          {% elif nextday in weekends %}
            {% if weekend %}
              {% set offsetday = nextday %}
            {% elif weekday %}
              {% set offsetday = weekdays[0] %}
            {% else %}
              {% set offsetday = None %}
            {% endif %}
          {% else %}
            {% set offsetday = None %}
          {% endif %}

          {% if offsetday != None %}
            {% set offset = offsetday-day if offsetday > day else offsetday - day + 7 %}
            {% if (day in weekdays and weekday) or (day in weekends and weekend) %}
              {% set today_alarm = getalarm()|float %}
            {% else %}
              {% set today_alarm = -1 %}
            {% endif %}
            {% set next_alarm = getalarm(offset)|float %}
            {% set time = now().replace(second=0).replace(microsecond=0) %}
            {% if today_alarm > 0 %}
              {{as_timestamp(time) + today_alarm}}
            {% else %}
              {{as_timestamp(time) + next_alarm}}
            {% endif %}
          {% else %}
            0
          {% endif %}

:wink:

Yes, this will need to be re-tailored.

          {% macro getalarm(offsetday=0) %}
          {% set day = (now().weekday() + offsetday) % 7 %}
          {% set offset = offsetday * 86400 %}
          {% if day in range(5) %}
            {% set hour = states('input_number.alarmclock_wd_hour') %}
            {% set minute = states('input_number.alarmclock_wd_minute') %}
          {% else %}
            {% set hour = states('input_number.alarmclock_we_hour') %}
            {% set minute = states('input_number.alarmclock_we_minute') %}
          {% endif %}
          {% set alarm = hour|float|multiply(3600) + minute|float|multiply(60) %}
          {% set time = now().hour * 3600 + now().minute * 60 %}
          {{(offset - time) + alarm if offsetday else alarm - time}}
          {% endmacro %}

@bdraco Do you need to have the entity_id near states, state in templates? Or do we find valid entity_id’s that are concatenated in the code?

I.E. a formatted string that builds an entity_id, a concatenated string that builds an entity_id, etc…

jut did a quick test, and commented all entity_ids of all sensors, except for using

          - sensor.time
          - sensor.next_alarm_timestamp

on them, which works out fine for now.

the big one posted above now only has sensor.time, and seems to fare well at it… Big changes must have happened behind the screens for this to be possible now. Which is cool indeed.

As long as the state is accessed it doesn’t matter how the string is built now as it will still find it. We don’t do analysis on the template string anymore, the analysis is done by looking at which states we touch during rendering of the template.

2 Likes

Excellent, that’s good to know. @Mariusthvdb, @klogg shouldn’t need to use the reworked templates.

[…stops typing mid post to say ‘thank you’ your reworked template seemed to work with initial tests.]

So, just to be completely clear…
You are now saying my original templates will work untouched, after I remove the entity_id?
(albeit that I might prefer yours anyway :wink: )

EDIT: If that is so then it seems the whole discussion does ‘simply’ come down to one of when in time sensors are evaluated and how to control that?

No don’t use mine. I just went through the template and it doesn’t match your functionality. It’s a threshold for the state, not the zone number. Use your old template.

I understand how that might be perceived to be the general-purpose solution (‘shoehorn the entity_id into the template’) but I feel it only applies to a specific case: the template itself lacks appropriate entities.

The classic example is a template that uses now() to do some date arithmetic (for those who don’t know, now() is a function, not an entity). The usual workaround is to add entity_id: sensor.time to cause the template to be updated once a minute (i.e. when sensor.time changes state).

In an upcoming release, you will no longer be able to do that because the entity_id option is deprecated. The first few posts in this growing thread discussed a proposed workaround where you simply add the following to the template:

{% set x = states('sensor.time') %}

Although it may feel like a band-aid solution, it’s really no worse than the old workaround of adding entity_id: sensor.time. It’s the same principle, ensure sensor.time is identifiable.


Exploring new possibilities:

One way to make it feel less like a kludge is to use sensor.time in one’s date arithmetic as opposed to employing now(). bdraco pointed out that this:

states.sensor.time.last_updated

is a datetime object and so we can use that to get timestamp, year, month, day, hour, and minute. However, there are caveats because it’s UTC time and only provides resolution to the nearest minute, not second. Converting it to local time is currently a messy proposition so bdraco proposed a new filter called as_local to easily convert a datetime object from UTC to local.

To reiterate, all of this applies to the case where the template had no identifiable entities and sensor.time was employed to ensure the template is evaluated every minute.

Yes

the “clever code” (as tom puts it) will read your template, work out what states are affecting it, and monitor those.
The issue comes where you used (say) sensor.time (as an entity_id: ) to trigger an update in (say) a sensor that only had (say) now() in it.
now() is a function, it changes on a millisecond basis, no listener (or whatever they use) can be assigned to it, so we have to introduce something to which a listener can be assigned.
So rather than just {% embed %} sensor.time (or whatever) to make the template be re-evaluated (using now() ) we actually use sensor.time to generate a timestamp that we can use directly instead of now(). [This is to shorten and simplify the resulting template].
So far the alternatives look a little long winded in these particular usage cases but bdrago is woking on his as_local() to contract it producing local time rather than working out if dst is applied or not then doing this offset or that offset.
These are quite esoteric outliers and most people won’t be affected by them (they just will be prompted to remove the entity_id: portion AND maybe (This would help people in general) be prompted to look at the release notes on dealing with the loss of entity_id: IF it contained sensor.time)
[Not all would need tweeking though - I have schedules set up that just compare sensor.time to (say) states(‘input_datetime.id_time’) [0:5] ]

Edit: @123 SNAP !

Edit2: Also the template checker (not sure what it should be referred to as (clever code is as good as anything but … ) is just an update on the current (slightly less clever code) that has the ability to make dramatic improvements in performance.
The point is that the ‘old way’ was not without it’s problems. Say I had 5 entities that were in my old template and I didn’t have an entity_id: section, the template would work as intended UNLESS it had one of the outlier cases above. SO I introduced the entity_id: section and listed only 4 of the 5 used entities. This would tell the less_clever_bit_of_code to ignore the fifth element (see what I did there ???)
The New code is more bullet proof than that BUT you need to tell it EVERYTHING it needs to work out what it should be doing (no change there then, when writting code in general)

2 Likes

I can’t think of a quick way to convert a UTC datetime object to a local datetime object. For example, timestamp_local converts from UTC to local but produces a string. Yes, I can slice the string to get the parts I want but that’s less convenient than if the result remained a datetime object.

I think I would prefer : -
states.sensor.time.last_local
or somesuch, but the amount of work that would require and the crannies explored and the things it would thus affect … Yeah go with the filter/function thang !

4 Likes

Many Thanks Kind Sir
Do you think this will also make it into 0.115.0 ???

It is a very small so it seems likely, but there are lot of PRs waiting for review so there are no guarantees it will make 0.115.

With all the WTH PRs 0.115 is going to be loaded!