Template Sensor for between times of day using hours and minutes

I offer you the following alternative solution. I tested it and it works.

  - platform: template
    sensors:
      tod_light_level:
        friendly_name: 'TOD Light Level'
        value_template: >
          {% set hours = [[0,500,1],[501,600,25],[601,630,65],[631,700,75],[701,730,125],[731,800,190],
                          [801,900,210],[901,1630,255],[1631,1700,255],[1701,1730,240],[1731,1800,235],
                          [1801,1830,225],[1831,1900,200],[1901,1930,175],[1931,2000,125],[2001,2030,115],
                          [2031,2100,110],[2101,2200,100],[2201,2230,85],[2231,2300,25],[2301,2359,1]] %}
          {% set t = states('sensor.time').replace(':','')|int %}
          {% for h in hours if h[0] <= t <= h[1] %}
              {{h[2]}}
          {% endfor %}

Screenshot from 2020-02-23 21-10-05

Here’s how it works:

  • hours is a list. Each item in the list is also a list which represents a period of the day and the desired light level in this format: [start time, end time, light level]. For example, a light level of 240 between 17:01 and 17:30 is represented like this: [1701,1730,240]
  • The hours list covers all hours of the day from 00:00 to 23:59 (represented as 0 and 2359).
  • The current time comes from sensor.time. It’s converted to an integer value (08:15 becomes 815) and assigned to the variable t.
  • A for-loop is used to iterate through all items in hours. However, it is constrained to iterate only for the condition where t is between the ‘start’ and ‘end’ time. That’s performed by if h[0] <= t <= h[1] in the for-loop’s declaration.
  • The light level is reported from the matching item.
4 Likes

Mind=blown

So that is pretty slick. I like the idea of using it for the simple fact that it’ll be there as a reference to draw upon for future templating.

Would there be any other reasoning why a person would use one vs. the other?
Certainly less lines of code…

2 Likes

They both do the same thing so pick whichever you prefer.

The example I posted is:

  • compact
  • easy to add/modify time periods (they’re all in one list)
  • probably marginally faster because it compares integer values as opposed to string values

Cool… I’ll probably use the second option as it seems really slick and again just to have yet another example of the templating possibilities in my configuration.

Thanks again so much u/123 and u/Mutt

1 Like

Taras, I know this problem has been solved six ways from sunday

But

Could your code not be simplified (the array part at least) from a 3 * 17 array to a 2 * 17 array ?
It would then require either a reversed array with an exit for
Or use of namespace and a print statement outside the loop

Sometimes I can’t resist trying to gild that lilly :crazy_face:

Show me this proposed ‘gilded lily’. :slight_smile:

What I was going for was something like : -

    sensors:
      tod_light_level:
        friendly_name: 'TOD Light Level'
        value_template: >
          {% set hrs = [[2300,1],[2230,25],[2200,85],[2100,100],[2030,110],
                        [2000,115],[1930,125],[1900,175],[1830,200],[1800,225],
                        [1730,235],[1700,240],[1630,255],[900,255],[800,210],
                        [730,190],[700,125],[630,75],[600,65],[500,25],[0,1]] %}
          {% set t = states('sensor.time').replace(':','') | int %}
          {% set ns = namespace(lvl = 0) %}
          {% for hr in hrs if t >= hr[0] %}
            {% set ns.lvl = hr[1] %}
          {% endfor %}
          {{ ns.lvl }}

But I’ve screwed up the syntax somehow (the above was cobbled from two of your pervious. (So a Frankenstein :japanese_ogre: (best monster I could find :crazy_face: )))

If you could do an “exit for” you could have an ascending list, but I couldn’t find a working example. :frowning_face:

Jinja2 doesn’t support {% break %} to exit a for-loop. The [for-loop’s documentation (Template Designer Documentation — Jinja Documentation (2.11.x)) states the following:

Unlike in Python, it’s not possible to break or continue in a loop. You can, however, filter the sequence during iteration, which allows you to skip items. The following example skips all the users which are hidden:

{% for user in users if not user.hidden %}
    <li>{{ user.username|e }}</li>
{% endfor %}

Given that requirement, it poses a challenge to create an appropriate filter when you only have the period’s ‘start time’.

Understood :+1:

But the above loop on the descending list just needs to keep writing a value to a variable until the time is no longer greater that the stored value. Then you walk away with that.

It would make maintenance / adjustment a lot more transparent.
But clearly there is something wrong with it that is currently beyond my ken. :man_shrugging:

You’ve proposed a change to my example that you can’t get to work and you want my help to fix it? Is that what’s happening here? :thinking:


Copy this into the Template Editor and watch what the for-loop is actually doing:

{% set hrs = [[2300,1],[2230,25],[2200,85],[2100,100],[2030,110],
              [2000,115],[1930,125],[1900,175],[1830,200],[1800,225],
              [1730,235],[1700,240],[1630,255],[900,255],[800,210],
              [730,190],[700,125],[630,75],[600,65],[500,25],[0,1]] %}
{% set t = 1815 %}
{% set ns = namespace(lvl = 0) %}
{%- for hr in hrs if t >= hr[0] %}
{{hr[1]}}
{%- set ns.lvl = hr[1] -%}
{% endfor -%}

Not quite what you expected, right? Now remove the if t >= hr[0] filter from the for-loop. Here’s what you get:

Hmmm, this is interesting : -

{% set t = states('sensor.time').replace(':','') %}
{#% set t = 1815 %#}
{% set hrs = [[0,1],[500,20],[600,65],[630,140],[700,160],[730,190],
              [800,200],[900,220],[1630,230],[1700,240],[1730,255],
              [1800,241],[1830,231],[1900,221],[1930,201],[2000,191],
              [2030,161],[2100,141],[2200,66],[2230,21],[2300,2]] %}
{% set ns = namespace(lvl = 0) %}
{%- for hr in hrs if t >= hr[0] %}
  {%- set ns.lvl = hr[1] -%}
{% endfor -%}
{{ ns.lvl }}

If you use the {% set t = 1815 %} it works

BUT AND

If you use the {% set t = states(‘sensor.time’).replace(‘:’,‘’) %} it DOES’NT now does (very weird :man_shrugging: )

This seemsed to be like you can’t use : -
value_template: “{{ states(‘input_datetime.id1’) <= states(‘sensor.time’) < states(‘input_datetime.id2’) }}”
But if you assign sensor.time to a variable, You can use the variable

Yeah, I try to pick the best man for the job :rofl: ('cept I’m not kidding !)

EDIT: Double weird it now works, dunno what changed but …

Kill Bill 2, Estaban Vihaio: I must warn you, … I am susceptible to flattery.

Here’s the working version of your proposal (using the OP’s light levels).

  - platform: template
    sensors:
      tod_light_level:
        friendly_name: 'TOD Light Level'
        value_template: >
          {% set hours = [[0,1],[500,25],[600,65],[630,75],[700,125],[730,190],
                          [800,210],[900,255],[1630,255],[1700,240],[1730,235],
                          [1800,225],[1830,200],[1900,175],[1930,125],[2000,115],
                          [2030,110],[2100,100],[2200,85],[2230,25],[2300,1]] %}
          {% set t = states('sensor.time').replace(':','')|int %}
          {% set ns = namespace(level = 0) %}
          {% for h in hours if t >= h[0] %}
            {% set ns.level = h[1] %}
          {% endfor %}
          {{ ns.level }}

Compared to the original, it has two more lines of code (to support namespace) and the for-loop performs more iterations (because the filter is less restrictive) but the list is more compact and easier to manage.

Cast the converted time to integer. In the example you posted, it leaves it as a string.

I must point out that I am neither young nor a lady :crazy_face:

And it’s not flattery, I consider you Petro and pnbruckner to be the best templaters (though arsaboo is pretty good) I wish to learn from the masters … Then I’ll creep up behind you and stab you with a really sharp template … so I can take over the WORLD !!! Muah Ha Haar !!!

Yeah, looking back at what I was playing with I did try casting to int so it must have been that, that I got working.

I did timing tests and the modified one runs a ‘bit’ slower (about 1/2 a millisecond, see below).
But for the readability and my cpu still running at about 1% - who cares ???
Dunno - I’ll never use either but I have both in my arsenal of ‘pointy templates’ :rofl:

This is really fun. Like reading a coding tennis match. haha.

I guess I’m just the ball boy. :rofl:

Tried something similar but getting error

sensor:
  - platform: time_date
    display_options:
      - time
      - date
      - date_time
  - platform: template
    sensors:
      greeting:
        value_template:
          {% set state = states('sensor.time') %}
          {% if '00:00' < state  < '06:00' %}
            Goeie Nacht !
          {% elif '06:00' < state  < '12:00' %}
            Goeie Morgen !
          {% elif '12:00' < state  < '18:00' %}
            Goeie Middag !
          {% else %}
            Goeie Avond !
          {% endif %}   

Error loading /config/configuration.yaml: while scanning for the next token
found character ‘%’ that cannot start any token
in “/config/packages/sensors.yaml”, line 11

Any idea?
Running template in dev works

value_template: >

The cause of the error is what AhmadK explained, you need to add the line-continuation character (>) as shown in his reply.

Your template’s logic doesn’t handle three hours of the day, when it’s 00:00, 06:00, or 12:00. Change it to this (note the use of <= for testing the hour):

  - platform: template
    sensors:
      greeting:
        value_template: >
          {% set state = states('sensor.time') %}
          {% if '00:00' <= state < '06:00' %}
            Goeie Nacht !
          {% elif '06:00' <= state < '12:00' %}
            Goeie Morgen !
          {% elif '12:00' <= state < '18:00' %}
            Goeie Middag !
          {% else %}
            Goeie Avond !
          {% endif %} 
1 Like

Cool thx guys
Gonna test it

Allright ! this works !!!

I’m posting this here because I didn’t see anyone else mentioning this. Home Assistant has Time of Day binary sensors.
With this in mind, it becomes incredibly easy to make templates in automations. My example shows the use of a data_template for a specific scene to be called but you could, of course, do the same with a call for light.turn_on instead.

I hope this helps someone out there ;D

Config Example

# configuration.yaml

binary_sensor:
  - platform: tod
    name: Daytime
    after: "06:00"
    before: "21:00"

  - platform: tod
    name: Nighttime
    after: "21:00"
    before: "06:00"

automation:
  - id: 'example_automation_with_data_template'
    alias: This automation turns on specific scene depending on time of day
    trigger:
      - platform: state
        entity_id: switch.example_switch
        from: 'off'
        to: 'on'
    action:
      - service: scene.turn_on
        data_template:
          entity_id: >
            {% if is_state('binary_sensor.daytime', 'on') %}
              scene.daytime_bedroom_lights
            {% elif is_state('binary_sensor.nighttime', 'on') %}
              scene.nighttime_bedroom_lights
            {% endif %}
3 Likes