Variables in times of day (tod) binary sensors - is it possible?

I am trying to use a converted input_number as offset for the binary_sensor times of day as follows, but HA doesn’t approve:

  sunrise_offset:
    entity_id: sensor.time
    value_template: >
      {% if states.input_number.sunrise_offset.state | int < 0 %}
      -0{{states.input_number.sunrise_offset.state | int | abs}}{% if states.input_number.sunrise_offset.state | float % 1 == 0 %}:00{% else %}:30{% endif %}
      {% else %}
      0{{states.input_number.sunrise_offset.state | int | abs}}{% if states.input_number.sunrise_offset.state | float % 1 == 0 %}:00{% else %}:30{% endif %}
      {% endif %}

      sunset_offset:
        entity_id: sensor.time
        value_template: >
          {% if states.input_number.sunset_offset.state | int < 0 %}
          -0{{states.input_number.sunset_offset.state | int | abs}}{% if states.input_number.sunset_offset.state | float % 1 == 0 %}:00{% else %}:30{% endif %}
          {% else %}
          0{{states.input_number.sunset_offset.state | int | abs}}{% if states.input_number.sunset_offset.state | float % 1 == 0 %}:00{% else %}:30{% endif %}
          {% endif %}

    binary_sensor:
    ##################################################
    ## Times of Day (tod) sensors
    ##################################################
      - platform: tod
        name: Dagsljus
        after: sunrise
        # after_offset: '00:00'
        after_offset: '{{states.sensor.sunrise_offset.state}}'
        before: sunset
        # before_offset: '-01:30'
        before_offset: '{{states.sensor.sunset_offset.state}}'

The template sensors give me output in the form of e.g. 01:00 or -01:30.
Could somebody help me out?

Thanks!

I have tried this instead, but the times of day binary_sensor won’t accept that either:

binary_sensor:
##################################################
## Times of Day (tod) sensors
##################################################
  - platform: tod
    name: Dagsljus
    after: >
      '{{ (as_timestamp(states.sun.sun.attributes.next_rising) + states.input_number.sunrise_offset.state | float * 3600) | timestamp_custom("%I:%M") }}
    before: >
      '{{ (as_timestamp(states.sun.sun.attributes.next_setting) + states.input_number.sunset_offset.state | float * 3600) | timestamp_custom("%I:%M") }}'

Any ideas?

Is your configuration passing Config Check?

When I try to do what you’re attempting, it fails Config Check. The stated error message always indicates it’s expecting the option’s data to be in “HH:MM” format (not a template).

If you mean config check as in this, yes:

But no, if you mean this:
Invalid config for [binary_sensor.tod]: Invalid time specified: '{{ (as_timestamp(states.sun.sun.attributes.next_rising) + states.input_number.sunrise_offset.state | float * 3600) | timestamp_custom("%H:%M") }}' for dictionary value @ data['after']. Got '\'{{ (as_timestamp(states.sun.sun.attributes.next_rising) + states.input_number.sunrise_offset.state | float * 3600) | timestamp_custom("%H:%M") }}\'\n' Invalid time specified: '{{ (as_timestamp(states.sun.sun.attributes.next_setting) + states.input_number.sunset_offset.state | float * 3600) | timestamp_custom("%H:%M") }}' for dictionary value @ data['before']. Got '\'{{ (as_timestamp(states.sun.sun.attributes.next_setting) + states.input_number.sunset_offset.state | float * 3600) | timestamp_custom("%H:%M") }}\'\n'. (See ?, line ?). Please check the docs at https://home-assistant.io/components/binary_sensor.tod/
But the docs says:
Configuration Variables

name

(string)(Required)Name of the sensor.

before

(string or time)(Required)The absolute local time value or sun event for beginning of the time range.

before_offset

(time)(Optional)The time offset of the beginning time range.

after

(string or time)(Required)The absolute local time value or sun event for ending of the time range.

after_offset

(time)(Optional)The time offset of the beginning time range.

In other words, before and after could be either string or time. My template returns a string.

By supplying the option with a template, you’re assuming the option is able to recognize it’s a template, evaluate the template, and then use the result of the evaluation.

Templates are not supported by all options. If you examine the source code for the Time of Day binary_sensor, you’ll see that it simply reads its options directly from the configuration file. I see nothing in the code that assumes it might receive a template and be required to evaluate it first.

Here’s the line of code where the after option is read directly from the configuration. You’ll notice it expects it to be a valid time.

Ok, I understand. Thanks for the info. I will have to find another way to have a daylight sensor that I can adjust dynamically from the UI. A simple time offset from sunrise and sunset won’t cut it as it changes through the seasons. I find it awkward to edit a yaml-file every time I want to adjust it.

I’m intrigued by what you are trying to accomplish.

The Time of Day Sensor can be used to specify a day-period which can automatically adjust itself according sunrise/sunset. You want to influence its calculation using an input_number. Effectively, you want to manually shorten/lengthen the day-period, on demand.

What is your intended application for a day-period that can be manually adjusted?

Let me explain.
Daylight is a relative thing when you consider luminance. I would like to have a sensor that gives me a good and reliable daylight reading to turn on/off lights in the house. I know, there are luminance sensors, but I haven’t got any of those to work reliably as their readings often vary significantly over short periods of time, thereby triggering lights due to a dark cloud passing by.

My idea now is to create a sensor based on sunrise/sunset, but adjusted for the seasons, e.g. in the summer there is daylight maybe 1-1½ hours before sunrise, while as during the winter it could still be dark an hour after sunrise.

To complicate it a bit further, I would also like to adjust the timings dependent on if the weather is sunny or cloudy. It makes a lot of difference.

I might add that I live in Stockholm, Sweden.

Here’s what I’ve been using for almost ten years (I use another home automation software):

  • I have one light sensor outdoors and another indoors.
  • Both have thresholds indicating when it is dark.
  • The thresholds were determined by experimentation and have been set to my preference.
  • Both sensors must agree it is dark before any automations are triggered.

The combination of the two sensors is used to turn on lights in the morning and in the evening.

For late afternoon/evening, there are two automations controlling the interior lights :

  1. A scheduled automation runs 2.5 hours before sunset. If the indoor and outdoor light sensors indicate it is dark, the interior lights are turned on.

  2. An automation is triggered when the indoor and outdoor light sensors indicate it is dark. If the current time is less than 2.5 hours from sunset, the interior lights are turned on.

This offset prevents the lights from being turned on during the middle of the day in the event of a passing storm. For your purposes, the 2.5 hour offset could be set by an input_number.

For early morning, there is only one automation controlling the interior lights:

  1. A scheduled automation runs at 6:45 AM. If the indoor and outdoor light sensors indicate it is dark, the interior lights are turned on

Albeit I live on the front end of Sweden, I can sympathise with your dillemma. I am trying the same thing, but want to throw in the workday sensor as well, so I can have time of day for weekdays and weekends differentiated easily.

Edit: The workday binary sensor is not a valid source for a template within the tod sensor

You can very easily filter out short dark periods by using a numeric_state trigger with a for period specified. E.g.:

trigger:
  platform: numeric_state
  entity_id: sensor.luminance
  below: 50
  for:
    minutes: 15

I have an Aeotec MultiSensor 6 I use for luminance that controls my lights on the front of the house. I use a slightly different technique: The lights will only change if they haven’t changed for 30 minutes. This way even a manual turn on or off of the lights won’t get immediately overridden by the automation.

- alias: House Front
  trigger:
    - platform: state
      entity_id: sensor.house_front_luminance
    - platform: state
      entity_id: switch.house_front_lights
      to: 'on'
      for: '00:30:00'
    - platform: state
      entity_id: switch.house_front_lights
      to: 'off'
      for: '00:30:00'
    - platform: event
      event_type: DELAYED_HOMEASSISTANT_START
  condition:
    - condition: template
      value_template: >
        {% set turn_on = states('sensor.house_front_luminance')|int < 50 %}
        {% set is_on = is_state('switch.house_front_lights', 'on') %}
        {{ (turn_on and not is_on or not turn_on and is_on) and
           (trigger.platform == 'event' or
            states('sensor.uptime')|int < 30 or
            (now() - states.switch.house_front_lights.last_changed)
              .total_seconds() >= 30*60) }}
  action:
    - service: switch.toggle
      entity_id: switch.house_front_lights

Thomas,
I appreciate that people can understand time before sunset (or after) as it’s easier to get your head around.
However, you are looking at luminance and (assuming a volcano has not erupted or you get particularly bad weather) that’s related to sun position in the sky relative to your global position and elevation.
Why not use regular binary sensors to detect a particular sun elevation derived from an angular sun.sun.elevation offset passed by an input_number ? eg civilian dusk (valid HA option, dusk) is 6° below horizon (but pick any number, careful if the sun never reaches that declination (closer to the poles)). Condition this with if the sun is rising or falling and you will be able to hit it with a bit of trial and error.
I’ve just spent an hour trying to get TOD to work with a template (and even to accept a name to use as an entity_id)
So vanilla binary sensors for me tomorrow : - S
Cheers

Guys,
I’ve had a play with this and have something that seems to work. I’m hoping that as this would seem a resonable function, some more people can test it, see if there are any faults/issues and polish this up a bit.
What I use it for is to turn on my floodlights, they consume (according to my metering) 0.7W and 0.6W each (2 off them) in the standby state and 48W each when they fire up having detected movement.
I have a double switch (consumes 0.96W on it’s own so I can’t claim I’m saving much energy (or any at all)) But it’s about interupting the flood if I step out side (patio or porch light goes on) and choosing the enable period for the porch light to come on when I get home or if I just open the relevant door.
This is written as a package so you may need to adjust for your installation.
Ended up I didn’t need binary_sensors, just used templates

input_boolean:
  ib_light_floodr1_timeslot_enable:
    name: Time Slot Enable
    #initial: on
    icon: mdi:clock-outline
  ib_light_floods2_timeslot_enable:
    name: Time Slot Enable
    #initial: on
    icon: mdi:clock-outline

  ib_global_darkslot: # usually lives elsewhere (globals) as I can use this for other things
    name: Time Slot - Dark
    #initial: off
    icon: mdi:weather-night

input_number:
  in_light_flood_on_elev_offset:
    name: Switch On ( ° offset from horizon)
    #initial: 0
    min: -12
    max: 12
    step: 0.1
    mode: box
    icon: mdi:clock-start
  in_light_flood_off_elev_offset:
    name: Switch Off ( ° offset from horizon)
    #initial: 0
    min: -12
    max: 12
    step: 0.1
    mode: box
    icon: mdi:clock-end

# ''{{ (states.sun.sun.attributes.elevation <= states.input_number.in_light_flood_on_elev_offset.state | float) and not (states.sun.sun.attributes.rising) }}'
# '{{ (states.sun.sun.attributes.elevation <= states.input_number.in_light_flood_off_elev_offset.state | float) and (states.sun.sun.attributes.rising) }}'

automation:
    #name: Light Flood Dark
  - alias: au_switch_flood_dark
    trigger:
      - platform: template
        value_template: '{{ (states.sun.sun.attributes.elevation <= states.input_number.in_light_flood_on_elev_offset.state | float) and not (states.sun.sun.attributes.rising) }}'
    action:
      - service: input_boolean.turn_on
        entity_id: input_boolean.ib_global_darkslot
    #name: Light Flood Light
  - alias: au_switch_flood_light
    trigger:
      - platform: template
        value_template: '{{ (states.sun.sun.attributes.elevation <= states.input_number.in_light_flood_off_elev_offset.state | float) and (states.sun.sun.attributes.rising) }}'
    action:
      - service: input_boolean.turn_off
        entity_id: input_boolean.ib_global_darkslot
    #name: Light Flood Rear Control On
  - alias: au_switch_floodr1_on
    trigger:
      - platform: state
        entity_id: input_boolean.ib_global_darkslot
        to: 'on'
    condition:
      - condition: state
        entity_id: input_boolean.ib_light_floodr1_timeslot_enable
        state: 'on'
    action:
      - service: switch.turn_on
        entity_id: switch.fib_fgs223dblrly_fld1_sw1
    #name: Light Flood Rear Control Off
  - alias: au_switch_floodr1_off
    trigger:
      - platform: state
        entity_id: input_boolean.ib_global_darkslot
        to: 'off'
    condition:
      - condition: state
        entity_id: input_boolean.ib_light_floodr1_timeslot_enable
        state: 'on'
    action:
      - service: switch.turn_off
        entity_id: switch.fib_fgs223dblrly_fld1_sw1
    #name: Light Flood Side Control On
  - alias: au_switch_floods2_on
    trigger:
      - platform: state
        entity_id: input_boolean.ib_global_darkslot
        to: 'on'
    condition:
      - condition: state
        entity_id: input_boolean.ib_light_floods2_timeslot_enable
        state: 'on'
    action:
      - service: switch.turn_on
        entity_id: switch.fib_fgs223dblrly_fld2_sw2
    #name: Light Flood Side Control Off
  - alias: au_switch_floods2_off
    trigger:
      - platform: state
        entity_id: input_boolean.ib_global_darkslot
        to: 'off'
    condition:
      - condition: state
        entity_id: input_boolean.ib_light_floods2_timeslot_enable
        state: 'on'
    action:
      - service: switch.turn_off
        entity_id: switch.fib_fgs223dblrly_fld2_sw2
    #name: Light Flood Rear Control Off Overide
  - alias: au_switch_floodr1_off_bcpatio
    trigger:
      - platform: state
        entity_id: light.fib_fgd212dim2_prch_level
        to: 'on'
    condition:
      - condition: state
        entity_id: input_boolean.ib_light_floodr1_timeslot_enable
        state: 'on'
      - condition: state
        entity_id: input_boolean.ib_global_darkslot
        state: 'on'
    action:
      - service: switch.turn_off
        entity_id: switch.fib_fgs223dblrly_fld1_sw1
    #name: Light Flood Rear Control On Overide
  - alias: au_switch_floodr1_on_bcpatio
    trigger:
      - platform: state
        entity_id: light.fib_fgd212dim2_prch_level
        to: 'off'
    condition:
      - condition: state
        entity_id: input_boolean.ib_light_floodr1_timeslot_enable
        state: 'on'
      - condition: state
        entity_id: input_boolean.ib_global_darkslot
        state: 'on'
    action:
      - service: switch.turn_on
        entity_id: switch.fib_fgs223dblrly_fld1_sw1

Basically it assumes that if the sun is a given number of degrees below the horizon, it then reaches the threshold for you to want external lights ‘on’ (as a rule of thumb), the limits have been set +/- 12° from the horizon, adjust as you require (12° is military dusk/dawn, 6° is civilian dusk/dawn).
When the light reaches your desired level, note the sun elevation and off you go.
It allows you to set a different limit on mornings to evenings as it checks the limit against whether the sun is rising or falling.
You can then use the input_number’s, as variables, to adjust the offset via the interface.
Any feedback welcome.