Periodic lights blueprint

Periodic lights

Github Gist: Periodic lights · GitHub
Github Gist for RGB lights Periodic Lights XY · GitHub

Introduction

Have you ever wished there was an easy way to have your lights adjust themselves based on the time of day? Well, this blueprint does just that! This blueprint progressively changes the brightness and color temperature of lights throughout the day (this runs 24 hours). This method indexes the light properties to midnight and fits them to a sin function (hence periodic). Midnight is the minimum brightness and color temperature and noon is the maximum.

You can offset midnight by up to 2 hours in either direction if you prefer a brighter or dimmer morning or evening. There is also a disable entity so you can turn the blueprint off for whatever reason (i.e. party mode). And probably the most sought after feature, there is a change threshold, so if you manually adjust a light the automation will skip it!

When a light is initially turned on the threshold is ignored and the light is immediately adjusted, the same is true for the disable entity. When the disable entity turns off, the lights that are currently on are immediately adjusted.

Requirements

You need a light, light group, or a list of lights separated by commas. You also need a ‘disable entity’. The disable entity can be anything the turns ‘on’ and ‘off’. When this entity is ‘on’ the blueprint is disabled and halts updating.

Limitations and solicitations for improvement

As far as I can gather, the blueprint entity chooser is limited somewhat. I cannot easily have a list of lights chosen without also presenting areas and devices. Because this code loops through the light entities, and as far as I know you cannot get a list of entities from an area, you need to provide each light manually in a comma separated list. If anyone knows a method to either allow for the choosing of multiple lights with the entity picker without presenting areas and devices, or knows how to get a list of entities in an area, please help me further improve this.

Also, as this is currently written, this will only adjust the color temperature for lights that can be changed with the Kelvin property. RGB lights in particular (in my experience) won’t adjust this way. Maybe in the future I can calculate color a different way.

If you want something more fully featured, you can try my AppDaemon app in HACS or: GitHub - haberda/update_lights: This AppDaemon app progressively changes the brightness and color temperature of lights over the course of the day. This app also uses change thresholds to ignore lights that have been manually adjusted.

Blueprint code

blueprint:
  name: Periodic lights
  description: "Dim and adjust color temperature of lights progressively throughout the evening (RGB bulbs probably won't change temperature). This is indexed to the midpoint between sunset and sunrise and fit to a sin function, so the midpoint will be minimum brightness and midpoint +12hr will be maximum brightness. The midpoint can be offset by a fixed amount if lights are too dim or bright at the desired time."
  domain: automation
  source_url: https://gist.github.com/haberda/f17694969a6de15d75267667b7c955ac
  input:
    light:
      name: Light(s)
      description: The light(s) to control
      selector:
        entity:
          domain: light
    min_brightness:
      name: Minimum Brightness
      description: Minimum brightness of the light(s)
      default: 1
      selector:
        number:
          min: 0.0
          max: 100.0
          mode: slider
          step: 1.0
          unit_of_measurement: '%'
    max_brightness:
      name: Maximum Brightness
      description: Maximum brightness of the light(s)
      default: 100
      selector:
        number:
          min: 0.0
          max: 100.0
          mode: slider
          step: 1.0
          unit_of_measurement: '%'
    min_temperature:
      name: Minimum Temperature
      description: Minimum temperature of the light(s) (Kelvin)
      default: 2200
      selector:
        number:
          min: 1800
          max: 6000
          mode: slider
          step: 100.0
          unit_of_measurement: 'K'
    max_temperature:
      name: Maximum Temperature
      description: Maximum temperature of the light(s) (Kelvin)
      default: 5000
      selector:
        number:
          min: 1800
          max: 6000
          mode: slider
          step: 100.0
          unit_of_measurement: 'K'
    threshold_brightness:
      name: Brightness change threshold
      description: Threshold value, above which the lights will not be adjusted (+ or -). This is the residual value between the current brightness level and the level it would adjust to.
      default: 10
      selector:
        number:
          min: 0.0
          max: 100.0
          mode: slider
          step: 1.0
          unit_of_measurement: '%'
    transition:
      name: Transition
      description: Transition time (s)
      default: 0
      selector:
        number:
          min: 0.0
          max: 30.0
          mode: slider
          step: 1.0
          unit_of_measurement: 's'
    midnight_offset:
      name: Midnight offset
      description: Offset from midnight. Positive for brighter evening and dimmer morning. Negative for dimmer evening and brighter morning.
      default: 0
      selector:
        number:
          min: -120.0
          max: 120.0
          step: 1.0
          unit_of_measurement: min
          mode: slider
    light_on_adjust:
      name: Adjust lights when they are turned on
      description: 'If true adjusts a light immediately when it is turned on.'
      default: true
      selector:
        boolean: 
    entity_off_adjust:
      name: Adjust lights when disable entity is turned off
      description: 'If true adjusts a light immediately when the disable entity is turned off.'
      default: true
      selector:
        boolean: 
    disable_entity:
      name: Disable entity
      description: Binary sensor or input boolean that when on disables the updating of lights.
      selector:
        entity:
mode: parallel
max_exceeded: silent
variables:
  light_list: !input 'light'
  threshold_brightness: !input 'threshold_brightness'
  min_brightness: !input 'min_brightness'
  max_brightness: !input 'max_brightness'
  min_temperature: !input 'min_temperature'
  max_temperature: !input 'max_temperature'
  midnight_offset: !input 'midnight_offset'
  light_on_adjust: !input light_on_adjust
  entity_off_adjust: !input entity_off_adjust
  pct: >
    {% if (states('sun.sun') == 'below_horizon') %}
      {% set sunset = as_timestamp(states.sun.sun.attributes.next_setting)|float - 86400 %}
      {% set sunrise = as_timestamp(states.sun.sun.attributes.next_rising)|float %}
    {% else %}
      {% set sunset = as_timestamp(states.sun.sun.attributes.next_setting)|float %}
      {% set sunrise = as_timestamp(states.sun.sun.attributes.next_rising)|float %}
    {% endif %}
    {% set midpoint = (sunset |float + sunrise |float) / 2 + (midnight_offset|float * 60) | float %}
    {{ sin(((as_timestamp(now())|float - midpoint)/86400)*pi) |abs }}
  brightness_percent: '{{ (min_brightness|int+(max_brightness|int-min_brightness|int) * pct) | int }}'
  kelvin_temperature: '{{ (min_temperature|int+(max_temperature|int-min_temperature|int) * pct) | int }}'
  compliant_lights: "{% for state in states.light if state.state=='on' and state.entity_id in light_list.split(',') and (state_attr(state.entity_id, 'brightness') | int - (brightness_percent|int*2.55)) | abs < (threshold_brightness|int*2.55) %}{% if loop.last %}{{ state.entity_id }}{% else %}{{ state.entity_id }},{% endif %}{% endfor %}"
  on_lights: "{% for state in states.light if state.state=='on' and state.entity_id in light_list.split(',') %}{% if loop.last %}{{ state.entity_id }}{% else %}{{ state.entity_id }},{% endif %}{% endfor %}"
trigger:
  - platform: time_pattern
    minutes: '/3'
    id: 'time_change'
  - platform: state
    entity_id: !input 'light'
    to: 'on'
    id: 'light_on'
  - platform: state
    entity_id: !input 'disable_entity'
    to: 'off'
    id: 'disable_off'
condition:
  - condition: state
    entity_id: !input disable_entity
    state: 'off'
  - condition: or
    conditions:
      - condition: template
        value_template: "{{compliant_lights.split(',')[0] in light_list.split(',')}}"
      - condition: trigger
        id: 'disable_off'
      - condition: trigger
        id: 'light_on'
action:
  - choose:
    - conditions:
      - condition: trigger
        id: 'disable_off'
      - condition: template 
        value_template: '{{ entity_off_adjust }}'
      - condition: template
        value_template: "{{ on_lights.split(',')[0] in light_list.split(',') }}"
      sequence:
        - service: light.turn_on
          data_template:
            entity_id: '{{ on_lights }}'
            brightness_pct: '{{ brightness_percent|int }}'
        - delay:
            hours: 0
            minutes: 0
            seconds: 0
            milliseconds: 500
        - service: light.turn_on
          data_template:
            entity_id: '{{ on_lights }}'
            kelvin: '{{ kelvin_temperature|int }}'
    - conditions:
      - condition: trigger
        id: 'light_on'
      - condition: template 
        value_template: '{{ light_on_adjust }}'
      sequence:
        - service: light.turn_on
          data_template:
            entity_id: '{{trigger.entity_id}}'
            brightness_pct: '{{ brightness_percent|int }}'
        - delay:
            hours: 0
            minutes: 0
            seconds: 0
            milliseconds: 500
        - service: light.turn_on
          data_template:
            entity_id: '{{trigger.entity_id}}'
            kelvin: '{{ kelvin_temperature|int }}'
    default:
      - condition: template
        value_template: "{{compliant_lights.split(',')[0] in light_list.split(',')}}"
      - service: light.turn_on
        data_template:
          entity_id: '{{ compliant_lights }}'
          brightness_pct: '{{ brightness_percent|int }}'
          kelvin: '{{ kelvin_temperature|int }}'
          transition: !input transition
4 Likes

How does this differ from existing solutions such as the Circadian Lighting and Adaptive Lighting custom components?

FWIW, Adaptive Lighting has been submitted for inclusion as an official integration.

1 Like

This is similar to Circadian Lighting in that is solves a similar problem. IMHO I would argue this blueprint is simpler and easier to use, and more accessible to new users than Circadian Lighting at this point, while giving you similar results.

Aside from that, this blueprint offers the change threshold for brightness that Circadian Lighting does not. This makes it easy to manually change the lights and not have to worry about setting up complex automatons to figure out if you should turn the Circadian Lighting switch off. This is the primary reason I moved away from Circadian Lighting.

I am happy to see that Circadian Lighting is on the way to becoming an official component. That old Flux component was never very good.

2 Likes

It’s Adaptive Lighting that’s in the pipeline.

1 Like

I found a minor bug preventing a comma separated list of lights from updating. If you are having trouble with this you can either: copy the updated code and update the blueprint manually, remove and re-add the blueprint, or wait for home-assistant to update it. The latter option may take some time. It is not clear to me what triggers home-assistant to check for an update. My testing instance has not updated after several days.

That’s a good read and a comprehensive list of features, though I don’t like his default setting I see I can tweak the yaml. Very tempting to depart from my own home brew version
Kudos to basnijholt

But I’m really waiting for some of the ‘new dim to warm’ to come out with a decent brightness range