Light Automation - variable delay based on sunset time

Hopefully a simple question.
I have night time light automation using the common trigger on sunset followed by delay then light off.
However I would like to set the delay as {time(23:30 - sunset)} so that the light stays on from sunset to 23:30.
Have can I configure the delay to be related to time of sunset?
Here is what I have now:

alias: Light - Evening
trigger:
  - platform: sun
    event: sunset
    offset: '+00:30:00'
condition: []
action:
  - service: light.turn_on
    data: {}
    entity_id: light.0x00999
  - delay:
      hours: 3      # <<--- Want to vary this time to be 23:30-SUNSET eg; Sunset = 19:00 Delay = 04:30
      minutes: 0  
      seconds: 0 
      milliseconds: 0
  - service: light.turn_off
    data: {}
    entity_id: light.0x00999
mode: single

Hi, This is my automation lights come on at sunset and then another automation to turn offt my lights randomly after 22:00.

- alias: "lights on at sunset"
  trigger:
    platform: numeric_state
    entity_id: sun.sun
    value_template: "{{ state.attributes.elevation }}"
    below: 3.1

  action:
    - service: homeassistant.turn_on
      entity_id:
        - switch.nadia_light


- alias: "Random lights off"
  trigger:
    platform: time
    at: "22:00:00"
  action:
    - delay: "{{ (range(0, 1)|random|int) }}:{{ (range(1, 59)|random|int) }}:00"
    - service: homeassistant.turn_off
      entity_id: switch.nadia_light

Try this:

  - delay:
      seconds: >
        {% set time = now().replace(hour=22).replace(minute=30) %}
        {% set sunset = strptime(state_attr('sun.sun', 'next_setting'), '%Y-%m-%dT%H:%M:%S%z') %}
        {{ (time - sunset).seconds }}

Thanks both.
@123 That looks like what I wanted to do - I will try it out

This works and does what I asked for, but it needs two automations per light doesn’t it?
I have several lights that I want to apply this to, with different times, so I would need 2 x automations.
But having said that, this is easy to understand and easy to implement!

If I can not get the delay approach to work then I will do for this.
Thanks again

I have tried this and looked in more detail using standard python, and I don’t think this is correct?
Or have I misunderstood?
You suggested:

  - delay:
      seconds: >
        {% set time = now().replace(hour=22).replace(minute=30) %}
        {% set sunset = strptime(state_attr('sun.sun', 'next_setting'), '%Y-%m-%dT%H:%M:%S%z') %}
        {{ (time - sunset).seconds }}

The problem is that sunset is in the future because is it 'sun.sun', 'next_setting' which is always tomorrow as my trigger uses a positive offset to current sunset. eg; it is triggered after today’s sunset and so the next one is tomorrow.
Using your logic I would need to use sunset today so that the calculation is (22:30 - sunset.today).seconds

Below is my example in python (I made up today’s sunset)

>>> sunset=datetime.now().replace(hour=18).replace(minute=00)
>>> print( sunset )
2021-02-24 18:00:34.388178. # Today's sunset
>>> time=datetime.now().replace(hour=22).replace(minute=30)
>>> print( time )
2021-02-24 22:30:34.362889 # Time I want to turn lights off
>>> print( time - sunset )
4:29:59.974711
>>> print( time - sunset ).seconds
16199 # delay I need to use to turn off at 22:30

That all works and gives me my variable delay of 16199 seconds in my example.
I would need to take account of my original trigger offset but that is just maths :slight_smile:
However, how do I get today’s sunset datetime for this to work?

You’re correct; I overlooked the fact that the automation’s Sun Trigger uses a positive offset for sunset.

The Sun integration has no means of indicating today’s sunset time after the sun has already set. As you mentioned, next_setting is today’s sunset time only if the current time is prior to sunset. After sunset, it’s tomorrow’s sunset time.

There’s a custom integration called Sun2 which, among many other things, provides an attribute reporting today’s sunset time (even after sunset). Alternatively, we could create a separate automation that stores today’s next_setting in an input_datetime at sunrise. Then your automation can refer to that input_datetime’s value when computing its delay.

However, neither of the two aforementioned strategies is needed if you are willing to make a small compromise. For most latitudes, the difference between today and tomorrow’s sunset time is minimal. Therefore all we need to do is force next_setting to have today’s date.

  - delay:
      seconds: >
        {% set time = now().replace(hour=22).replace(minute=30) %}
        {% set sunset = strptime(state_attr('sun.sun', 'next_setting'), '%Y-%m-%dT%H:%M:%S%z').replace(month=now().month).replace(day=now().day) %}
        {{ (time - sunset).seconds }}

I’d go with the separate on/off automations out of choice. If you restart HA during the delay (intentionally or not), the automations will (I assume) be reset but the lights will remain on

That’s true. A restart will cancel all of the following features that involve elapsed time:

  • delay
  • for
  • timer

However, all three are valuable features and avoiding their use may require making compromises.

The alternative is to use two automations (as suggested by Troon and DKJNA) or even a single automation with two triggers, one to turn on the light and the other to turn it off.

alias: Light - Evening
trigger:
  - platform: sun
    event: sunset
    offset: '+00:30:00'
  - platform: time
    at: '23:30:00'
condition: []
action:
  - service: "light.turn_{{ 'on' if trigger.platform == 'sun' else 'off' }}"
    data: {}
    entity_id: light.0x00999
mode: single
1 Like

This is what I use for all my lights off automation

- alias: "Random lights off"
  trigger:
    platform: time
    at: "22:00:00"
  action:
    - delay: "{{ (range(0, 1)|random|int) }}:{{ (range(1, 59)|random|int) }}:00"
    - service: homeassistant.turn_off
      entity_id: switch.1_light
    - delay: "{{ (range(0, 1)|random|int) }}:{{ (range(1, 59)|random|int) }}:00"
    - service: homeassistant.turn_off
      entity_id: switch.2_light
    - delay: "{{ (range(0, 1)|random|int) }}:{{ (range(1, 30)|random|int) }}:00"
    - service: homeassistant.turn_off
      entity_id: switch.3

I like this approach! Its pretty neat and my next adaption will be to introduce a little “randomness” and that will be easy with this method.

Thanks @DKJNA . I like this as well and will be using that random piece later
So many ways to do this!!
Gotta love HA!

That doesn’t work the way you may believe it does.

This does not produce a random number that’s either 0 or 1 hours:

{{ (range(0, 1)|random|int) }}

It produces only 0 because the range function stops before the specified upper value.

If you want range() to generate a sequence containing 0, 1 then you can do it like this:

range(0,2)

or like this (because it starts from 0 by default):

range(2)

In addition, the result of random will be a value from the sequence of integers. Therefore the inclusion of the int filter is redundant. The template is reduced to:

{{ range(2)|random }}

Similarly, this will vary from 1 to 58 as opposed to 59 minutes:

{{ (range(1, 59)|random|int) }}

whereas this will vary from 1 to 59 minutes:

{{ range(1, 60)|random }}

FWIW, it’s not mandatory to present the delay period in HH:MM:SS format. For example, if you want a delay that varies from 1 to 119 minutes (1 minute to 1 hour and 59 minutes) you can specify it like this:

    - delay: 
        minutes: "{{ range(1, 120)|random }}"

I only randomise the minutes in this particular automation so the range below as you corrlectly stated it will only prodoucd 0 Hours.

{{ (range(0, 1)|random|int) }}

I have other automations that i wish to randomise the Hours and minutes and this seems to work.

   - delay: "{{ (range(0, 5)|random|int) }}:{{ (range(1, 59)|random|int) }}:00"

Just double checked in the template editor and I am getting the expected results! I have read so many of your posts and reply’s to know you are indeed an expert and I welcome your advice.

Yes, that one uses range(0, 5) so it will produce this sequence:

0, 1, 2, 3, 4

therefore random will report an integer value from 0 to 4. As mentioned, the int filter isn’t needed.

Again, this will never report 59 minutes because of how range works:

{{ range(1, 59)|random|int }}

If you should ever want it to report 59 minutes you must change it to: range(1, 60)

Here’s a version that generates a random value ranging from 1 minute to 299 minutes (4 hours and 59 minutes):

   - delay:
       minutes: "{{ (range(1, 300)|random }}"

Thanks for the continued discussion guys. This is all very pythonic so I am happy with methods.
I haven’t tried it yet but I presume I can use a negative number in the range command?
Suppose I want to say plus or minus 20 minutes the I can use:

"{{ (range(-20,21)|random }}

For example in python:
>>> range(-5,6)
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]

You can answer your own question by pasting your template into the Template Editor and see the result.

Developer Tools > Template


Spoiler: Yes

1 Like

Another tip I have learnt! Thanks again
Its even better when I close the () :slight_smile:

In your example, there’s no need for the outer parentheses. This is sufficient:

{{ range(-20,21)|random }}

Just finishing up with this automation and I going to add some randomness by using sun elevation but I read that the offset doesn’t support template. https://community.home-assistant.io/t/help-with-templating-using-a-variable-sunset-offset/140700

For this reason, is it easiest/best to add a random delay in the action before the call to the on/off service? That way I get random on and off? Just like @DKJNA suggested earlier:

action:
    - delay: "00:{{ range(1, 31)|random }}:00"
    - service: 'light.turn_{{ ''on'' if trigger.platform == ''sun'' else ''off'' }}'

This will give me a 1-30 minute delay with the ON or the OFF?