Sleep Mode - Binary Sensor Based off Input_Datetimes for Time Range

I’d like to have a simple binary sensor to indicate “Sleep Mode” or call it what you will. I realise I can define a Times of the Day sensor, but this involves sort of hard-coded start and end times for this mode. I’d instead like to set the time period using “Sleep Start” and “Sleep End” input_datetimes.

   name: Sleep Start
   icon: mdi:bed-clock
   has_date: false
   has_time: true
   initial: "20:30"
   name: Sleep End
   icon: mdi:bed-clock
   has_date: false
   has_time: true

I can’t work out how best to then arrive at Sleep Mode.

I have toyed with a templated binary sensor. The logic isn’t as simple as I would expect:

Something like...
    state: >-
      {% set timenow = now().strftime("%H:%M:%S") %}
      {% set start = states('input_datetime.sleep_start') %}
      {% set end = states('input_datetime.sleep_end') %}
      {{ (start < end and (start < now().time() < end)) 
          or (start > end and ( (start < now and now < "23:59:59") or now < end )) }}

And it also won’t trigger unless the input_datetimes change of course… when I really want it to trigger based on time.

Surely there is an easier way to achieve what I’m trying to do?

 state: >-
      {% set tnow = as_timestamp(now()) %}
      {% set start = today_at(states('input_datetime.sleep_start')) | as_timestamp %}
      {% set end = today_at(states('input_datetime.sleep_end')) | as_timestamp %}
      {% set midnite = today_at('23:59:59') | as_timestamp %}
      {{ (start < end and (start < tnow < end)) or
          (start > end and ((start < tnow and tnow < midnite ) or tnow < end )) }}

I believe this will trigger once a minute as you are using now(). I think the conversion to a unix timestamp will make this easier.

Thank you. Timestamps is the approach I was hoping for but I wasn’t aware of anything like at_today. Is there something I need to do to employ this? When I input this to Developer Tools > Template Editor I get an error: UndefinedError: 'at_today' is undefined

I think you are correct re trigger on now(). Templating docs:

Using now() will cause templates to be refreshed at the start of every new minute.

I typed at_today instead of today_at. I fixed in my original post.

Here is from developer (I changed the variables to get it to work)

Hope this fixes it for you.

Thank you, I think I’ve got it all working off the back of that. I’ve changed it around a little to simplify / make it more readable:

{% set tnow = as_timestamp(now()) %}
{% set tstart = today_at(states('input_datetime.sleep_start')) | as_timestamp %}
{% set tend = today_at(states('input_datetime.sleep_end')) | as_timestamp %}

{% if tend < tstart %}
   {% set tend = (tend + 24*60*60) %}
{% endif %} 

{{ tstart, tnow, tend}}
{{ tstart < tnow < tend }}

Final minor query: is there any way to tidy up the if statement there or must it be done in three separate blocks like this? I tried an inline expression but couldn’t get it working.

Another option is to use timedelta to add/subtract a day from today_at.

I didn’t have any datetimes to use to I just hardcoded the values but it can be replaced with states(…).

Current time is about 7 in the morning.

{% set start = today_at("20:30:00") - timedelta(days=  1 if (now().hour < 12) else 0 )  %}
{% set end = today_at("08:30:00") + timedelta(days=  1 if (now().hour >= 12) else 0 ) %}

{{ start }}
{{end }}
{{ start <= now() <= end}}

so if now.hour is less than 12 (morning) then the start will be yesterday (because timedelta removes one day) 20:30, else today at 20:30.
If the now.hour is more than 12 then the end will be set to tomorrow because timedelta adds a day on end.

This won’t work on daylight savings

Thank you both, will update accordingly.