Lights on at Dusk - is this the best/simplest way?

Hello,

I want my outside lights to turn on at dusk and off at 1am. I’ve thought about doing this with elevation, and I use elevation as triggers that have a physical input, e.g., if I tap a button, HA does a bunch of stuff including turning on the lights if elevation is below -2. However, I don’t want to use elevation for the outside lights because it will be triggered after a server reboot (I reboot once a week at 4am and the sun elevation is below -2 at 4am…).

My solution is to use next_dusk and compare it to the time:

- id: turn_outside_lights_on
  alias: Outside Lights On (Dusk)
  initial_state: 'on'
  trigger:
    - platform: template
      value_template: '{{ (( as_timestamp( states.sun.sun.attributes.next_dusk ) | int ) - 180) < ( as_timestamp( now() ) | int ) }}'
  action:
    - service: light.turn_on
      data:
        entity_id: light.outside_front_porch_light
        brightness_pct: 70
    - service: switch.turn_on
      data:
        entity_id:
          - switch.outside_side_door_light
          - switch.outside_patio_lights

I just wrote this and haven’t lived with it yet but is there any reason why I should not create a trigger using now()? I just want to make sure what I’m doing makes sense and won’t cause any issues down the road.

Thanks,
Will

As I think more about this, I could use a standard elevation trigger, like below:

  trigger:
    - platform: numeric_state
      entity_id: sun.sun
      value_template: '{{ state.attributes.elevation }}'
      below: -5

With a condition of the time having to be after noon so my lights don’t come on with the server reboot at 4am. I prefer the dusk time comparison because it feels simpler to me. What are everyone else’s thoughts?

Actually, I use the “sunset” event as described here with an offset that matches my local setting.

When I made my first automation a few weeks ago that is exactly what I did and it works but the downside is that the lights come on before dusk, at least certain times of the year.

I’ve been using this tool to help estimate sun position. With the site, you can actual move the time slider around and see the sun’s position move around your location - it is actually what started getting me interested in using elevation for the trigger.

I have my deck light doing the exact thing you are wanting to do (on at dusk - 15 min after sunset-, off at 1 am):

- id: deck_light_on_at_dusk
  alias: Deck Light On at Dusk
  trigger:
    platform: sun
    event: sunset
    offset: "+00:15:00"
  action:
    service: homeassistant.turn_on
    entity_id: light.sengled_e11g13_03070a4c_1

- id: deck_light_off_at_x_time
  alias: Deck Light Off at 1am
  trigger:
    platform: time
    hours: 01
    minutes: 00
    seconds: 00
  action:
    service: homeassistant.turn_off
    entity_id: light.sengled_e11g13_03070a4c_1

works like a charm.

I started just using offsets (I used 15 minutes as well) but then I realized that offsets weren’t precise enough - it is easy but not accurate. I didn’t like that my lights came on when it was still too light out.

I’ve been using this trigger for a week and it works perfectly and I haven’t noticed any adverse effects.

  trigger:
    - platform: template
      value_template: '{{ (( as_timestamp( states.sun.sun.attributes.next_dusk ) | int ) - 180) < ( as_timestamp( now() ) | int ) }}'

I have my lights coming on three minutes before true dusk and it is dynamic and will work all times of the year.

For example, on June 21 where I live, the sunsets 8:58 PM but dusk won’t occur for another 35 minutes. So on June 21, my outside lights will turn on at 9:30 PM. Likewise, on December 21, the sun sets at 5:44 PM (DST) and dusk begins 32 minutes later which means my lights turn on 6:13 PM.

Come to think of it, I probably should of just did a 30 minute offset and call it a day :roll_eyes: Well you know what they say, close only matters in horseshoes and hand grenades and sun offsets.

4 Likes

Yeah I don’t think I could bring myself to care if the difference was only 3 minutes of variation over the year. :grinning:

I’m trying to use the sun.sun entity’s attributes in a condition, as in, I have a PIR motion sensor in my basement, but I only want the lights to turn on if it’s after dusk. But I was wondering, when do the times for sun.sun get updated? The time says “next_dusk”, so if that time passes, will it update to tomorrow’s dusk, since that’s technically the “next” one? When does this update happen? After that dusk time? Midnight? Can I depend on it to use it in an automation condition?

To phrase another way, Is there any way to get what time the sunrise did happen, after it has occurred? Looking at sun.sun now, it only says when the next sunrise will happen, and that date-time is for tomorrow.

I really think that sensor should have:

  • today_sunrise
  • today_dawn
  • today_dusk
  • today_sunset
  • tomorrow_sunrise
  • tomorrow_dawn
  • tomorrow_dusk
  • tomorrow_sunset

And that would be much less confusing.

As far as I’m aware the only offset you could use would be time which is a very unreliable way of estimating the light level. (it changes throughout the year given the axial tilt and procession around the sun)
Dusk and Dawn are better as they relate to how close the sun is to the horizon
There are 3 levels of both dusk and dawn (see pnbruckners sun components or just wikipedia)
Sunset/Sunrise is 0.86° below the horizon
Civilian Dusk/Dawn is 6 degrees below
Navigational Dusk/Dawn is 12 degrees below
and Astronomical Dusk Dawn is 18 degrees below

1 Like

I was just looking at this: https://sunrise-sunset.org/api

It seems to be really simple to use, and returns a simple JSON structure. I think I’ll just set up three of these as REST sensors to get today, tomorrow, and yesterday’s times.

    {
      "results":
      {
        "sunrise":"2015-05-21T05:05:35+00:00",
        "sunset":"2015-05-21T19:22:59+00:00",
        "solar_noon":"2015-05-21T12:14:17+00:00",
        "day_length":51444,
        "civil_twilight_begin":"2015-05-21T04:36:17+00:00",
        "civil_twilight_end":"2015-05-21T19:52:17+00:00",
        "nautical_twilight_begin":"2015-05-21T04:00:13+00:00",
        "nautical_twilight_end":"2015-05-21T20:28:21+00:00",
        "astronomical_twilight_begin":"2015-05-21T03:20:49+00:00",
        "astronomical_twilight_end":"2015-05-21T21:07:45+00:00"
      },
       "status":"OK"
    }

Steve,
if you use the standard sun.sun then it will get updated a second after it’s happened.
A fortuitous circustance for Will as he has a 3 minute offset
If he had (say) a 1 minutes offset (or some silly small number) the day by day variations would cause the event to ‘happen’ and then have it ‘not happened yet’ my current day to day offset is about 1:20
If you use pnbruckners sun components then you have the option of using ‘todays sunset’ which is fixed for the whole day and only changes at midnight

1 Like

That’s no good then. I want to use it in a condition. “If motion is detected, with the condition that the current time is after dusk, then turn on the basement lights.” If next_dusk immediately updates to tomorrow’s dusk after it happens, then that condition will never be met.

so you either need a 2 minute offset, which will subsume the difference (and only needed half the time cos if it’s earlier … ) or you should just use Phil’s sun2 component.
Buy it here soon !
Or just go with his old one it will more than cover what you need

Edit: OR … you could make a binary sensor sticky with a delay_off: '00:02:00’
Your usage doesn’t need you to turn it off this way so it’ll be bang on !!!

you can use phil’s custom sun component that has both today’s sunrise and sunset along with tomorrows sunrise & sunset.

you can find it here:

1 Like

+1 for Phils component.

I use the sun angle instead of a time offset… anyway, each to their own.

Confirmed…Just tried @pnbruckner’s sun component, and it was exactly what was called for.

1 Like

Hi everyone.

Like others have said, I also wanted to have a light turn on at dusk rather than sunset.
Currently for me the difference is about 28 minutes between the two attributes of sun.sun being next_setting and next_dusk.

I did get some inspiration from the trigger that @will presented but I did not want the 180 second offset he used and I wanted it to occur at the exact moment that the next_dusk attribute of the sun.sun entity reports.

After removing the 180 second offset, i discovered the trigger never occurs because of the following statement at
https://www.home-assistant.io/docs/configuration/templating/#time

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

I understood that to mean if I used a value lower than 60 seconds in the offset, now() will never get a chance to be greater than or equal to next_dusk.

Due to this discovery, I had to find another way to have my light turn on exactly at dusk.
Here is what I found worked for me.

trigger:
  - platform: state
    entity_id: sun.sun
    attribute: next_dusk

This simple trigger does exactly what I want.

When the value for now() catches up to the next_dusk value for today, the next_dusk attribute will immediately roll over to the next day.

This trigger just waits for that attribute to change and then my light turns on.

As I am rather new to Home Assistant, I only recently found the developer tools to be handy for troubleshooting. This might be handy for newbies.

States Tab

image

as well as the template tab

Hope this helps anyone else.


If anyone still wants to use the offset, then I would suggest using this updated code. Apparantly using states.sun.sun.attributes.next_dusk is a frowned upon even though it appears to do the job.

Avoid using states.sensor.temperature.state , instead use states('sensor.temperature') . It is strongly advised to use the states() , is_state() , state_attr() and is_state_attr() as much as possible, to avoid errors and error message when the entity isn’t ready yet (e.g., during Home Assistant startup).

Source: https://www.home-assistant.io/docs/configuration/templating/#home-assistant-template-extensions

Updated code

trigger:
  - platform: template
    value_template: >-
      {{ ((as_timestamp(state_attr('sun.sun', 'next_dusk')) | int ) - 180 ) <
      (as_timestamp(utcnow()) | int ) }}

2 Likes

This is slightly off topic but I have also added a notify action to show me when next dusk will occur when the trigger in my automation occurs

alias: Light on dusk
description: ''
trigger:
  - platform: state
    entity_id: sun.sun
    attribute: next_dusk
condition: []
action:
  - type: turn_on
    device_id: xxx
    entity_id: xxx
    domain: light
  - service: notify.notify
    data_template:
      message: >
        Dusk tomorrow will be at {{ as_timestamp(state_attr("sun.sun",
        "next_dusk")) | timestamp_custom('%I:%M:%S %p') }}
mode: single

This will turn the light on at dusk as well as show the time for dusk tomorrow when the time for dusk today is reached

Thought I’d share my take on this classic automation of turning on the porch lights. This was my first ever automation, and my version of it has evolved over the past four years. Here are my latest set of requirements:

  • Be ‘on’ between 30 minutes before sunset and 30 minutes after sunrise
  • EXCEPT if it’s after 23:30h or before 07:00h (so ‘off’ in this case)
  • EXCEPT if (so, ‘on’ in these cases):
    • An exterior door is open
    • It is within 30 minutes of an exterior door being closed
    • it is within 30 minutes of somebody arriving home

So, first, I have a binary sensor that will be ‘on’ if these conditions are met, and ‘off’ otherwise. I found it frustrating that sun.sun always shows the next sunrise and not the last sunrise. I approximated by just forcing today’s date onto the setting and rising times; I’m not sure if I’ll run into a “1-hour-off” bug during daylight savings time changes.

template:
  - binary_sensor:
    - name: outside_lights_on
      state: >
        {%- set current_time = states('sensor.date_time') | as_timestamp -%}
        {%- set today_date = states('sensor.date') -%}
        {%- set today_sunset = (today_date + 'T' + state_attr('sun.sun', 'next_setting').split('T')[1]) | as_timestamp - (60 * 30) -%}
        {%- set today_sunrise = (today_date + 'T' + state_attr('sun.sun', 'next_rising').split('T')[1]) | as_timestamp + (60 * 30) -%}
        {%- set morning_time = (today_date + "T07:00:00") | as_timestamp -%}
        {%- set evening_time = (today_date + "T23:30:00") | as_timestamp -%}
        {%- set exterior_doors = ['binary_sensor.frontdoor', 'binary_sensor.backdoor'] -%}

        {# returns a JSON array of entities that have a state in the *states_to_match* array #}
        {%- macro filter_by_state (entities, states_to_match) -%}
          {{ "[" }}
          {%- for e in entities if (states(e) in states_to_match) -%}
            "{{ e }}"{{ "," if (not loop.last) else "" }}
          {%- endfor -%}
          {{ "]" }}
        {% endmacro %}

        {# returns a JSON array of entities whose states have changed within *delta* time of *ct* (current_time) #}
        {%- macro changed_in_last (entities, ct, delta) -%}
          {{ "[" }}
          {%- for e in entities if ((ct - (states[e].last_changed | as_timestamp)) < delta.total_seconds()) -%}
            "{{ e }}"{{ "," if (not loop.last) else "" }}
          {%- endfor -%}
          {{ "]" }}
        {% endmacro %}

        {%- if (current_time > today_sunset) or (current_time < today_sunrise) -%}
          {%- if (current_time >= evening_time) or (current_time <= morning_time) -%}
            {%- set people_home = states.person | selectattr('state', 'eq', 'home') | map(attribute='entity_id') | list -%}
            {%- set people_home_in_range = changed_in_last (people_home, current_time, timedelta(minutes=30)) | from_json -%}
            {%- set doors_open = filter_by_state(exterior_doors, ['on']) | from_json -%}
            {%- set doors_closed = filter_by_state(exterior_doors, ['off']) | from_json -%}
            {%- set doors_closed_in_range = changed_in_last (doors_closed, current_time, timedelta(minutes=30)) | from_json -%}

            {%- if (people_home_in_range | count > 0) -%}     true
            {%- elif (doors_open | count > 0) -%}             true
            {%- elif (doors_closed_in_range | count > 0) -%}  true
            {%- else -%}                                      false
            {%- endif -%}
          {%- else -%}
            true
          {%- endif -%}
        {%- else -%}
          false 
        {%- endif -%}

Next, I have an automation to set the state of the outside lights to whatever the sensor says they should be. Ignore the condition for now, I’ll get back to that.

automation:
  - alias: outside_lights_on_off
    mode: queued
    max: 10
    max_exceeded: silent
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id: binary_sensor.outside_lights_on
    action:
      - condition: template
        value_template: "{{ not is_state('timer.porchlights', 'active') }}"
      - service: "light.turn_{{ states('binary_sensor.outside_lights_on') }}"
        data:
          entity_id:
            - light.front_porch_lights
            - light.garage_outside_light
            - light.deck_light

After trying this out for a night, I realized that there was a problem: if somebody manually changed the state of any of those lights, it would never come back in-line with the rest until the binary sensor changed state again. This is where that timer comes in. I introduced it along with two other automations to allow people to manually turn the lights on or off outside of their scheduled time ranges. When the lights are manually switched, start a 20 minute timer, after which another automation will set the lights back to the state the binary sensor says they should be:

timer:
  porchlights:
    duration: '00:20:00'

automation:
  - alias: outside_lights_manually_switched
    mode: restart
    trigger:
      - platform: state
        entity_id:
          - light.front_porch_lights
          - light.garage_outside_light
          - light.deck_light
    condition:
      condition: template
      value_template: >
        {# Be careful not to trigger this automation when the lights are turned on or off via the actual schedule. #}
        {%- set two_seconds_ago = (now() - timedelta(seconds=2)) | as_timestamp -%}
        {%- set last_automation_run = state_attr('automation.outside_lights_on_off', 'last_triggered') | as_timestamp -%}
        {{ last_automation_run < two_seconds_ago }}
    action:
      - service: timer.cancel
        target:
          entity_id: timer.porchlights
      - service: timer.start
        target:
          entity_id: timer.porchlights
        data:
          duration: "00:20:00"

  - alias: outside_lights_timer_finished
    trigger:
      - platform: event
        event_type: timer.finished
        event_data:
          entity_id: timer.porchlights
    action:
      - service: automation.trigger
        target:
          entity_id: automation.outside_lights_on_off

I’m pretty happy with this version; it covers all the cases I could think of.