Template Sensor for between times of day using hours and minutes

Hey All.
I’m trying to create template sensors between times of day.
Example: If it’s between 07:00:00 and 07:30:00, then show such n such value.

Basically my use case is that I’ve made my own circadian lighting automations that change brightness and color_temp. Currently I have a different automation for each time of day and each device. While it works, it’s not very sexy and is LOTS of code (19 automations per device and 4 devices). I created these when I wasn’t as skilled with HA, so I’m trying to go back and clean it up.

To head off the suggestions at the pass pointing me to other options:
I’ve tried the flux component and the circadian lighting custom component. Both are fine, but I’m not crazy about the actual lighting (Flux seemed to be too pink most of the time and CL didn’t give me enough control. I also like my nightlights to be red rather than ‘starry sky’ white).

This post shows an example for the hours only like this

{% set current_hour = strptime(states('sensor.time'), "%H:%M").hour %}
          {% if current_hour < 12 %}
            Morning
          {% elif 12 <= current_hour < 18 %}
            Afternoon
          {% else %}
            Evening
          {% endif %}

That’s fine, except I would like the times to also include minutes (or at least half hours) so I can change the sensor value on the half hour as well.

I’ve also tried this from another post

{% if as_timestamp(now()) > as_timestamp(states.sensor.date.state ~ ' 05:00:00') %}
  25
{% elif as_timestamp(now()) > as_timestamp(states.sensor.date.state ~ ' 05:30:00') %}
  50
#........continued for rest of day............
{% else %}
  1
{% endif %}

When typing this into the template editor, it only shows the first value of “25” and doesn’t change at 5:30 to the next value. Makes sense. Overnight it shows the “else” value until 5a again.

So any suggestions? Or am I going about this the wrong way?

Thanks in advance!

Ugh!

I literally just posted this question and then found this.

I’m going to give it a shot.

:wink:
an advice - use variables in comparisons, i.e

{% set state = states('sensor.time') %}
{% if '08:01' < state  < '12:00' %}
3 Likes

Oh cool. When I get home I’ll give that a go and report back… If anything, it’s easier to read.
Thanks so much!

Looks like it’s working!!!

Thanks a bunch!

Hmmm !
That won’t wrap around midnight (say if you want a period between 22:00 and 02:00)
So, I’d do it like this : -

          {% set time = states('sensor.time') %}
          {% set slt1start = states('input_datetime.id_switch_pifi01_tmeslt1_on') [0:5] %}
          {% set slt1stop = states('input_datetime.id_switch_pifi01_tmeslt1_off') [0:5] %}
          {{ (slt1start <= time < slt1stop) if (slt1start < slt1stop) else (slt1start <= time or time < slt1stop) }}

This also uses input_datetime’s so you can set it from the frontend

oh that’s interesting, but I have my {% else %} as my wrap around midnight…

here’s my full template:

{% set state = states('sensor.time') %}
          {% if '05:00' <= state  <= '06:00' %}
            25
          {% elif '06:01' <= state <= '06:30' %}
            65
          {% elif '06:31' <= state <= '07:00' %}
            75
          {% elif '07:01' <= state <= '07:30' %}
            125
          {% elif '07:31' <= state <= '08:00' %}
            190
          {% elif '08:01' <= state <= '09:00' %}
            210
          {% elif '09:01' <= state <= '16:30' %}
            255
          {% elif '16:31' <= state <= '17:00' %}
            255
          {% elif '17:01' <= state <= '017:30' %}
            240
          {% elif '17:31' <= state <= '18:00' %}
            235
          {% elif '18:01' <= state <= '18:30' %}
            225
          {% elif '18:31' <= state <= '19:00' %}
            200
          {% elif '19:01' <= state <= '19:30' %}
            175
          {% elif '19:31' <= state <= '20:00' %}
            125
          {% elif '20:01' <= state <= '20:30' %}
            115
          {% elif '20:31' <= state <= '21:00' %}
            110
          {% elif '21:01' <= state <= '22:00' %}
            100
          {% elif '22:01' <= state <= '22:30' %}
            85
          {% elif '22:31' <= state <= '23:00' %}
            25
          {% else %}
            1
          {% endif %}

so from 11p to 5a it should just be the “else” brightness of ‘1’

This should work I think unless I’m missing something. Which, let’s be honest, is usually the case with me and templates…haha. #not_a_coder

I’ve never seen this kind of template. I’m not sure I understand. I get the input_datetime being addressable from the frontend, although I’m not sure I really want 19 separate settings on my frontend to make those adjustments. Also I don’t even know how I would name them all…

That aside, thank you for your suggestion. Can you explain when the slt1start and slt1stop are? Are those just made up variables that you named or is that template language?

Can you laymen’s terms explain what that template does?

Well yeah you can call a variable anything as long as it starts with a letter, excludes ‘some’ special characters and is not a reserved word.
So sltstart could just as easily be elephants
And sltend zebras
It’s just something that gives you a clue what it is as a place holder

time is just a name, to which we are assigning a text string which represents the current time.

Gotcha. That’s what I figured, but I wasn’t sure.

Your template may not behave the way you expect. Imagine the time is 06:30. What do you expect the result will be? It will be 1.

Why? Because the expression checks if 06:30 is greater than 06:01 and less than 06:30. It’s not so it moves on to the next one which checks if 06:30 is greater than 06:31 and less than 07:00. It’s not so it continues like this until the final else and reports 1.

Unless you want it to work like that, the expression should use <= to test the time.

2 Likes

Ah I see thanks… That makes sense, thank you…
123Taras helping me out again. You’re a rock star!

I’m adding this edit here as well…

So like this, correct?

{% set state = states('sensor.time') %}
          {% if '05:00' <= state  <= '06:00' %}
            25
          {% elif '06:01' <= state <= '06:30' %}
            65
          {% elif '06:31' <= state <= '07:00' %}
            75
          {% elif '07:01' <= state <= '07:30' %}
            125
          {% elif '07:31' <= state <= '08:00' %}
            190
          {% elif '08:01' <= state <= '09:00' %}
            210
          {% elif '09:01' <= state <= '16:30' %}
            255
          {% elif '16:31' <= state <= '17:00' %}
            255
          {% elif '17:01' <= state <= '017:30' %}
            240
          {% elif '17:31' <= state <= '18:00' %}
            235
          {% elif '18:01' <= state <= '18:30' %}
            225
          {% elif '18:31' <= state <= '19:00' %}
            200
          {% elif '19:01' <= state <= '19:30' %}
            175
          {% elif '19:31' <= state <= '20:00' %}
            125
          {% elif '20:01' <= state <= '20:30' %}
            115
          {% elif '20:31' <= state <= '21:00' %}
            110
          {% elif '21:01' <= state <= '22:00' %}
            100
          {% elif '22:01' <= state <= '22:30' %}
            85
          {% elif '22:31' <= state <= '23:00' %}
            25
          {% else %}
            1
          {% endif %}

No, you don’t use it both sides generally you want something at 9 pm so 21:00 <= time and off at 10 pm (so on upto 10 pm) time < 22:00 otherwise what happens in the minute you’ve not specified ?
It shouldn’t matter much in your usage case and as you’ve specified ‘odd’ times but it’s not very clean and it’s bad programming practice.

1 Like

Okay… Yea the odd times were me trying to…do something. Not really sure what. Make it more complicated I guess, haha!

{% set state = states('sensor.time') %}
          {% if '05:00' <= state  < '06:00' %}
            25
          {% elif '06:00' <= state < '06:30' %}
            65
          {% elif '06:30' <= state < '07:00' %}
            75
          {% elif '07:00' <= state < '07:30' %}
            125
          {% elif '07:30' <= state < '08:00' %}
            190
          {% elif '08:00' <= state < '09:00' %}
            210
          {% elif '09:00' <= state < '16:30' %}
            255
          {% elif '16:30' <= state < '17:00' %}
            255
          {% elif '17:00' <= state < '017:30' %}
            240
          {% elif '17:30' <= state < '18:00' %}
            235
          {% elif '18:00' <= state < '18:30' %}
            225
          {% elif '18:30' <= state < '19:00' %}
            200
          {% elif '19:00' <= state < '19:30' %}
            175
          {% elif '19:30' <= state < '20:00' %}
            125
          {% elif '20:00' <= state < '20:30' %}
            115
          {% elif '20:30' <= state < '21:00' %}
            110
          {% elif '21:00' <= state < '22:00' %}
            100
          {% elif '22:00' <= state < '22:30' %}
            85
          {% elif '22:30' <= state < '23:00' %}
            25
          {% else %}
            1
          {% endif %}

Appreciate this a bunch. I’m really enjoying learning these things, but since I have no coding/programming background, some of these fundamental rules of thumb and good practices are outside of my knowledge base. I’m certainly straight into the deepend here, but it’s fun learning things like this along the way. Helps me go back and clean up my yaml from when I first got HA rolling.

So the above is looking better, yea?
Definitely getting some hand holding now :stuck_out_tongue:

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: