Spot the error... nested template

Ok this template should work for your next day at all times.

{%- 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 %}
  {% set today_alarm = getalarm() | float %}
  {% set next_alarm = getalarm(offset) | float %}
  {% if today_alarm > 0 %}
    {{ today_alarm | timestamp_custom('%H:%M', False) }}
  {% elif offset == 1 %}
    {{ next_alarm | timestamp_custom('%H:%M', False) }}
  {% else %}
    {{ (next_alarm // 86400) | int }}:{{ next_alarm | timestamp_custom('%H:%M', False) }}
  {% endif %}
{% else %}
  Not set
{% endif %}

This template should calculate the days until your next alarm.

{%- 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 %}
  {% set today_alarm = getalarm() | float %}
  {% set next_alarm = getalarm(offset) | float %}
  {% if today_alarm > 0 %}
    0
  {% else %}
    {{ offset }}
  {% endif %}
{% else %}
  Not set
{% endif %}

HI Petro!
Thanks, feared I had scared you away… sorry if I did…

Ive implemented your 2 templates (called Macro in the screenshot), but they are still incorrect for the weekend days, when current day is a weekday. It has to do with the input_boolean not being taken into account:

have a look, while current time is 18:10 ish

just for reference, with my own sensor (using the external days counter) it is done like this:

        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 week_on = is_state('input_boolean.alarmclock_wd_enabled','on') %}
          {% set end_on = is_state('input_boolean.alarmclock_we_enabled','on') %}
          {% set in_days = states('sensor.number_of_days_next_alarm_alt')|int %}

          {% set alarm = getalarm(in_days) | float %}

          {% if week_on or end_on %}
              {% if (alarm / 3600 ) < 1 %}
                {{ alarm | timestamp_custom('%-M', False) }}
              {% elif (alarm / 86400 ) < 1 %}
                {{ alarm | timestamp_custom('%-H:%-M', False) }}
              {% else %}
                {{ (alarm // 86400 )|int }}:{{ alarm | timestamp_custom('%-H:%-M', False) }}
              {% endif %}

          {% else %} Not set, relax
          {% endif %}

will have a thorough look why that is, because your all in one templates are much more responsive than my templates, that need each other.

Not sure why yours isn’t working. I’m using that exact code and everything is a-ok.

Are you sure you restarted and used the code I just linked?

why yes, the exact code.

Its quite peculiar. If I turnoff the weekday boolean, the time is incorrectly calculated, when the current time is earlier than alarm time:


and see the same error in the day counter:

Ok found the issue with the current day calculation. These should work then.

      next_alarm:
        entity_id:
          - input_number.alarmclock_wd_hour
          - input_number.alarmclock_we_hour
          - input_number.alarmclock_wd_minute
          - input_number.alarmclock_we_minute
          - input_boolean.alarmclock_wd_enabled
          - input_boolean.alarmclock_we_enabled
        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 %}
            {% if today_alarm > 0 %}
              {{ today_alarm | timestamp_custom('%H:%M', False) }}
            {% elif offset == 1 %}
              {{ next_alarm | timestamp_custom('%H:%M', False) }}
            {% else %}
              {{ (next_alarm // 86400) | int }}:{{ next_alarm | timestamp_custom('%H:%M', False) }}
            {% endif %}
          {% else %}
            Not set
          {% endif %}
      days_to_next_alarm:
        entity_id:
          - input_number.alarmclock_wd_hour
          - input_number.alarmclock_we_hour
          - input_number.alarmclock_wd_minute
          - input_number.alarmclock_we_minute
          - input_boolean.alarmclock_wd_enabled
          - input_boolean.alarmclock_we_enabled
        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 %}
            {% if today_alarm > 0 %}
              0
            {% else %}
              {{ offset }}
            {% endif %}
          {% else %}
            Not set
          {% endif %}

sorry, was posting before I read your last reply…

they test promising in the dev-template, will update now first and report back. Thanks a million for your stamina…
now where’s the difference quickly?

difference is in the today_alarm calculation. Had to check to see if today was a weekday and if weekday was on, same for weekend.

EDIT: This is probably not the most optimized. I’m sure there’s other ways to do it but it would be just as much code. Just organized differently.

yep, that has been the issue from starters I fear, hence my separate templates, to debug per issue. If these proof to be alright (as I fervently hope) I can do away with the individual template sensors, and use these all in one’s. They are far more responsive!

as a side question: what would be necessary for the entity_id’s to set? For my own sensors I added most entities used in the template, but according to the docs, that shouldn’t be necessary. I have found the templates wouldn’t update otherwise though, only when sensor.time changes state which is one a minute…

Ive even created an automation to update all sensors when one of the entities changes state. because of the rather complex set of related and intermediary sensors, this became a real issue…

still happening also with your macro templates: i added the entities to the time until next alarm, and that changes swiftly, while Ive left them out on the day counter, which changes only after a while.

can confirm this to be blazingly fast now! wow, and very nice. Now have to see what happens around the weekend :wink:

btw, I am using these sensors:

sensor:
  - platform: template
    sensors:
      alarmclock_wd_time:
        friendly_name: 'Alarmtime weekday'
        value_template: >
          {% if is_state('input_boolean.alarmclock_wd_enabled','on') %}
          {{ '%0.02d:%0.02d' | format(states('input_number.alarmclock_wd_hour') | int, 
                                      states('input_number.alarmclock_wd_minute') | int) }}
          {% else %} Not set
          {% endif %}
        icon_template: >
          {% if is_state('input_boolean.alarmclock_wd_enabled','on') %} mdi:alarm
          {% else %} mdi:alarm-off
          {% endif %}

      alarmclock_we_time:
        friendly_name: 'Alarmtime weekend'
        value_template: >
          {% if is_state('input_boolean.alarmclock_we_enabled','on') %}
          {{ '%0.02d:%0.02d' | format(states('input_number.alarmclock_we_hour') | int,
                                      states('input_number.alarmclock_we_minute') | int) }}
          {% else %} Not set
          {% endif %}
        icon_template: >
          {% if is_state('input_boolean.alarmclock_we_enabled','on') %} mdi:alarm
          {% else %} mdi:alarm-off
          {% endif %}

to show the state of the weekday and weekend alarms. Using these as entity_id’s for the sensors at hand works just fine too Ive just established. cuts a few lines again.

If you find a way to do it even more optimized, or better for that matter, don’t hold back. If only for learning experience here, this has been a ride up to now.

still a small anomaly:

today is friday, weekday alarm turned on, but with alarmtime < now and weekend turned on, and the all-in-1 loses the day (because offset == 1?):

my own template uses this to prevent that:

          {% set week_on = is_state('input_boolean.alarmclock_wd_enabled','on') %}
          {% set end_on = is_state('input_boolean.alarmclock_we_enabled','on') %}
          {% set in_days = states('sensor.number_of_days_next_alarm_alt')|int %}

          {% set alarm = getalarm(in_days) | float %}

          {% if week_on or end_on %}
              {% if (alarm / 3600 ) < 1 %}
                {{ alarm | timestamp_custom('%-M', False) }}
              {% elif (alarm / 86400 ) < 1 %}
                {{ alarm | timestamp_custom('%-H:%-M', False) }}
              {% else %}
                {{ (alarm // 86400 )|int }}:{{ alarm | timestamp_custom('%-H:%-M', False) }}
              {% endif %}

          {% else %} Not set

could you change the allin1 to use some of that, like this maybe?

          {% 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 %}
            {% if today_alarm > 0 %}
              {{ today_alarm | timestamp_custom('%-H:%-M', False) }}
            {% elif offset == 1 %}
              {% if next_alarm/86400 <1 %}
              {{ next_alarm | timestamp_custom('%-H:%-M', False) }}
              {% else %} {{(next_alarm // 86400) | int }}:{{ next_alarm | timestamp_custom('%-H:%-M', False) }}
              {% endif %}
            {% else %} {{(next_alarm // 86400) | int }}:{{ next_alarm | timestamp_custom('%-H:%-M', False) }}
            {% endif %}
          {% else %}
            Not set, relax
          {% endif %}

this does show the correct timing including the day now, but I am afraid it is a hack on your offset template, so not sure if this is the place it should be corrected, or the set offset part should be checked?
Also, note the double use of the {% else %} {{(next_alarm // 86400) | int }}:{{ next_alarm | timestamp_custom('%-H:%-M', False) }}

update

had another swirl, to try to make it a bit simpler (which wasn’t easy since your day counter technique is rather complex I must admit…) and add a frivolity in the same move, to only show minutes if alarm is within the hour. I had that in my template, and I like that, especially when TTS ir reading it aloud :wink:

this is what I came up with:

          {% 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 %}
            {% if today_alarm > 0 %}
              {% if today_alarm/3600 < 1 %} {{ today_alarm | timestamp_custom('%-M', False) }}
              {% else %}{{ today_alarm | timestamp_custom('%-H:%-M', False) }}
              {% endif %}
            {% elif offset == 1 %}
              {% if next_alarm/3600  < 1 %} {{ next_alarm | timestamp_custom('%-M', False) }}
              {% elif next_alarm/86400 <1 %} {{ next_alarm | timestamp_custom('%-H:%-M', False) }}
              {% else %} {{(next_alarm // 86400) | int }}:{{ next_alarm | timestamp_custom('%-H:%-M', False) }}
              {% endif %}
            {% else %} {{(next_alarm // 86400) | int }}:{{ next_alarm | timestamp_custom('%-H:%-M', False) }}
            {% endif %}
          {% else %}
            Not set, relax
          {% endif %}

which is still a lot more difficult to read then:

          {% set alarm = getalarm(in_days) | float %}

          {% if week_on or end_on %}
              {% if (alarm / 3600 ) < 1 %}
                {{ alarm | timestamp_custom('%-M', False) }}
              {% elif (alarm / 86400 ) < 1 %}
                {{ alarm | timestamp_custom('%-H:%-M', False) }}
              {% else %}
                {{ (alarm // 86400 )|int }}:{{ alarm | timestamp_custom('%-H:%-M', False) }}
              {% endif %}

          {% else %} Not set, relax
          {% endif %}

which only uses alarm, and doesn’t distinguish between today_alarm and next_alarm.

HI @petro

have to get back on this:

   {% if is_state('input_boolean.alarmclock_wd_enabled','off') and
                is_state('input_boolean.alarmclock_we_enabled','off') %} {% set daytype = 'Not set' %}
          {% elif day_delta == '0' %} {% set daytype = 'Today,'%}
          {% elif day_delta == '1' %} {% set daytype = 'Tomorrow,' %}
          {% elif day_delta == '2' %} {% set daytype = 'The day after tomorrow,' %}
          {% elif day_delta > '2' %} {% set daytype = 'Next' %}
          {% endif %}

          {% if day_delta == 'Not set' %} Not set
          {% elif day + day_delta|int > month_days %}
             {{daytype}} {{ as_timestamp(now().replace(month= month + month_delta).replace(day=(day + 
                                                day_delta|int - month_days))) | timestamp_custom('%A, %-d %B')}}
          {% else %}
             {{daytype}} {{ as_timestamp(now().replace(day=(day + day_delta|int )))| timestamp_custom('%A, %-d %B') }}
          {% endif %}

Gives me Not set, while it should say Tomorrow… day_delta is 1.

today is 31th next day is calculated but I get an out of range error. Please help me spot the error, and a way to solve it.

Thing is it worked until yesterday, and the template counted correctly to saturday the 1st…

pretty sure I fixed this for you using %J, that’s really your only option without making a map that tracks each months days. If I recall correctly, you would need to upgrade your HA from 84 to something newer.

but that wasn’t another template I think, I must find it…sorry.

Why won’t the addition for the month now work? It did so for all the other days last week.

should be like this:
I test if the addition is within the month days, if not, add +1 to the month (5+1=6), and subtract the days of the current month (31+1-31=1) making that the 1st day of the 6th month…

which is what I am using:

      days_current_month:
        friendly_name: Days current month
        entity_id: sensor.date
        value_template: >
          {% set month = now().strftime('%-m') %}
          {% if month in ['1','3','5','7','8','10','12'] %} 31
          {% elif month in ['4','6','9','11'] %} 30
          {% else %} {{'29' if (now().strftime('%-y'))|int//4 == 0 else '28'}}
          {% endif %}

really don’t get why this wouldn’t work for 31+1-31

after some more testing, Ive moved the |int to the {% set %} part in the template, which seems to rid me of the day is out of range error…
in the test setup that is, now wait 2 months for real life testing :wink:

        value_template: >
          {% set day_delta = states('sensor.number_of_days_next_alarm_macro')|int %}
          {% set day = now().day %}
          {% set month = now().month %}
          {% set month_days = states('sensor.days_current_month')|int %}
          {% set month_delta = 1 if month < 12  else 1-12 %}

          {% if is_state('input_boolean.alarmclock_wd_enabled','off') and
                is_state('input_boolean.alarmclock_we_enabled','off') %} {% set daytype = 'Not set' %}
          {% elif day_delta == 0 %} {% set daytype = 'Today,'%}
          {% elif day_delta == 1 %} {% set daytype = 'Tomorrow,' %}
          {% elif day_delta == 2 %} {% set daytype = 'The day after tomorrow,' %}
          {% elif day_delta > 2 %} {% set daytype = 'Next' %}
          {% endif %}

          {% if day_delta == 'Not set' %} Not set
          {% elif day + day_delta > month_days %}
             {{daytype}} {{ as_timestamp(now().replace(month= month + month_delta).replace(day=(day + 
                                                day_delta - month_days))) | timestamp_custom('%A, %-d %B')}}
          {% else %}
             {{daytype}} {{ as_timestamp(now().replace(day=(day + day_delta )))| timestamp_custom('%A, %-d %B') }}
          {% endif %}

yes, but it was the 31st, added +1, so would be 32. so than the if kicks in and subtracts the days of current month = 31, resulting in day=1.
Or so it should have done. Hope the new template does that better.

because 31 isn’t greater than 31

{% elif day + day_delta|int > month_days %}

new template doesn’t care about days. It goes off days of the year and works year round regardless of the month

really sorry, but I don’t understand what you are saying here…

The template that uses %j uses the yearly day number. For example, the day number today is 153. You can simply get that number, add 1 to it, then convert it back in to a date to get the date number without caring what month it is or whatever. You just need to watch out for day 365+1. It didn’t work in 0.84 but it does work in the latest.

if you mean this:

46

there seems to be no issue in 84.3…

the problem was with strptime not understanding ‘%j’, not strftime

ok, thanks.
for reference, this is the thread we discussed that earlier.

Still annoyed by the fact I don’t understand why my 84.3 template failed doing 31+1-31 and didn’t doing 30+2-31, 29+3-31, etc etc.

Hope time will tell if my current template (with the amended |int) is doing better now. In 2 months… :wink:

there wouldn’t be a way to use %j in the current template? given the fact it calculates the day number perfectly. It might be easier than adding a delta, subtracting the days of the current month and substituting the current month with the next…

–edit–
just remembered I can’t use the |int in the {% set%} section, because it makes ‘Not set’ into 0… which kills the template if not set…

will have to make a catch for that in a newer iteration .