Smarter light dimming

I was never satisfied with the Flux Light Adjustment component but I always loved the idea. To me it intuitively makes sense that as the night progresses you typically would want the lights to dim. Flux does a good job changing color through the night but there are a couple of problems with the Flux component that wanted to address with these automatons/scripts that include:

  • Dimming to near zero at the end of the night
  • Balancing of lights that have different inherent brightness
  • The ability to change the brightness manually without the automation taking the control back 30 sec later
  • When a light is initially turned on automatically adjust it to the current level

To achieve the third bullet I created input numbers for each light that has the same name as each light. I also create a dim_level input number to keep track of the current ‘master dim’ level The basic input number config looks like:

  living_room_lamp:
    name: Living Room Lamp
    initial: 0
    min: 0
    max: 255
    step: 1

  dim_level:
    name: Dim Level
    initial: 12
    min: 0
    max: 255
    step: 1

When any light is turned on it and it’s corresponding input number is immediately adjusted to the current level by this automation:

  - id: '2'
    alias: 'Autoadjust Light When Turned On' #When a light is turned on adjust to appropriate dim level
    initial_state: true
    trigger:
      - platform: event
        event_type: state_changed
    condition:
      - condition: template
        value_template: "{{ trigger.event.data.entity_id.split('.')[0] == 'light' }}"
      - condition: template
        value_template: "{{ trigger.event.data.new_state.state == 'on' }}"
      - condition: template
        value_template: "{{ trigger.event.data.old_state.state == 'off' }}"
    action:
      - service: light.turn_on
        data_template:
          entity_id: '{{ trigger.event.data.entity_id }}'
          brightness: '{{ states.input_number.dim_level.state|int }}'
      - service: input_number.set_value
        data_template:
          entity_id: >
                     {% set slider_name = trigger.event.data.entity_id.split('.')[1] %}
                     {% set slider_name = slider_name.split('_lev')[0] %}
                     {{ "input_number." ~ slider_name }}
          value: '{{ states.input_number.dim_level.state|int }}'

Similarly when a light is turned off:

  - id: 'Autoadjust inputnumber When Turned off'
    alias: 'Autoadjust inputnumber When Turned off'
    initial_state: true
    trigger:
      - platform: event
        event_type: state_changed
    condition:
      - condition: template
        value_template: "{{ trigger.event.data.entity_id.split('.')[0] == 'light' }}"
      - condition: template
        value_template: "{{ trigger.event.data.new_state.state == 'off' }}"
      - condition: template
        value_template: "{{ trigger.event.data.old_state.state == 'on' }}"
    action:
      - service: input_number.set_value
        data_template:
          entity_id: >
                     {% set slider_name = trigger.event.data.entity_id.split('.')[1] %}
                     {% set slider_name = slider_name.split('_lev')[0] %}
                     {{ "input_number." ~ slider_name }}
          value: 0

I also have a script that updates the state of zwave devices which tend to be slow to update.

I have to give the Flux component credit here as it provided the core dim level calculation by calculating the percent of time that has passed between sunset and the end time that I have set to 2130. The master dim level is changed by this automation:

  - id: '14'
    alias: Dim Level # Update Dim Level Every 5 Minutes
    hide_entity: true
    trigger:
      - platform: time
        minutes: '/5'
        seconds: 00
      - platform: state
        entity_id: input_boolean.hold_lights
        to: 'off'
      - platform: homeassistant
        event: start
    condition:
      - condition: sun
        after: sunset
      - condition: state
        entity_id: input_boolean.hold_lights
        state: 'off'
    action:
      - service: input_number.set_value
        data_template:
          entity_id: input_number.dim_level
          value: > 
                 {% if  255 - (255 * (as_timestamp(states.sensor.date.state ~ " " ~ states.sensor.time.state )-(as_timestamp(states.sun.sun.attributes.next_setting)-86400))/(as_timestamp(states.sensor.date.state ~ " 21:30:00") - (as_timestamp(states.sun.sun.attributes.next_setting)-86400)))|int > 12 %} 
                   {{255 - (255 * (as_timestamp(states.sensor.date.state ~ " " ~ states.sensor.time.state )-(as_timestamp(states.sun.sun.attributes.next_setting)-86400))/(as_timestamp(states.sensor.date.state ~ " 21:30:00") - (as_timestamp(states.sun.sun.attributes.next_setting)-86400)))|int}}
                 {% else %} 
                   12
                 {% endif %}

And the change is picked up by:

  - id: '3'
    alias: 'Change Lights When Dim Level Changes' #Changes dim level when a new one is calculated
    initial_state: true
    trigger:
      - platform: state
        entity_id: input_number.dim_level
    condition:
      - condition: time
        after: '07:00'
    action:
      - service: script.turn_on
        data:
          entity_id: 
          - script.update_zwave
      - delay:
          seconds: 10
      - service: script.turn_on
        data:
          entity_id: 
          - script.light_dimmer
          - script.light_dimmer_low
          - script.light_dimmer_high

I also have an automation that changes the dim level to 255 at sunrise. So that lights turned on during the day are set to max brightness

The following 3 scripts are really where the magic happens. These scripts loop through each light, checks if the light is on, compares the brightness to the input number and if it passes those checks within a threshold of 25 brightness units the light and input number is adjusted to the new level. The initial script just uses the current master dim level while the other 2 use an adjusted level for lights that need to be balanced because they are either too bright or too dim:

  light_dimmer:
    alias: light dimmer
    sequence:
      - service: light.turn_on
        data_template:
          entity_id: "{% for state in states.light if state.state=='on' and (state_attr(state.entity_id, 'brightness') | int - states('input_number.' ~ state.entity_id.split('.')[1]) | int) | abs < 25 and state.entity_id != 'light.office' and state.entity_id != 'light.family_room' and state.entity_id != 'light.bar' and state.entity_id != 'light.fireplace' %}{% if loop.first %}{% else %},{% endif %}{{state.entity_id}}{% endfor %}"
          brightness: '{{ states.input_number.dim_level.state|int }}'
      - service: input_number.set_value
        data_template:
          entity_id: "{% for state in states.light if state.state=='on' and (state_attr(state.entity_id, 'brightness') | int - states('input_number.' ~ state.entity_id.split('.')[1]) | int) | abs < 25 and state.entity_id != 'light.office' and state.entity_id != 'light.family_room' and state.entity_id != 'light.bar' and state.entity_id != 'light.fireplace' %}{% if loop.first %}{% else %},{% endif %}{{'input_number.' ~ state.entity_id.split('.')[1]}}{% endfor %}"
          value: '{{ states.input_number.dim_level.state|int }}'

  light_dimmer_low:
    alias: light dimmer low
    sequence:
      - service: light.turn_on
        data_template:
          entity_id: "{% for state in states.light if state.state=='on' and (state_attr(state.entity_id, 'brightness') | int - states('input_number.' ~ state.entity_id.split('.')[1]) | int) | abs < 50 and (state.entity_id == 'light.office' or state.entity_id == 'light.bar') %}{% if loop.first %}{% else %},{% endif %}{{state.entity_id}}{% endfor %}"
          brightness: '{% if states.input_number.dim_level.state|int < 235 %} {{ states.input_number.dim_level.state|int +20 }}{% else %}{{ states.input_number.dim_level.state|int}}{% endif %}'
      - service: input_number.set_value
        data_template:
          entity_id: "{% for state in states.light if state.state=='on' and (state_attr(state.entity_id, 'brightness') | int - states('input_number.' ~ state.entity_id.split('.')[1]) | int) | abs < 50 and (state.entity_id == 'light.office' or state.entity_id == 'light.bar') %}{% if loop.first %}{% else %},{% endif %}{{'input_number.' ~ state.entity_id.split('.')[1]}}{% endfor %}"
          value: '{% if states.input_number.dim_level.state|int < 235 %} {{ states.input_number.dim_level.state|int +20 }}{% else %}{{ states.input_number.dim_level.state|int}}{% endif %}'

  light_dimmer_high:
    alias: light dimmer high
    sequence:
      - service: light.turn_on
        data_template:
          entity_id: "{% for state in states.light if state.state=='on' and (state_attr(state.entity_id, 'brightness') | int - states('input_number.' ~ state.entity_id.split('.')[1]) | int) | abs < 50 and (state.entity_id == 'light.family_room' or state.entity_id == 'light.fireplace') %}{% if loop.first %}{% else %},{% endif %}{{state.entity_id}}{% endfor %}"
          brightness: '{% if states.input_number.dim_level.state|int > 30 %} {{ states.input_number.dim_level.state|int -20 }}{% else %}{{ states.input_number.dim_level.state|int}}{% endif %}'
      - service: input_number.set_value
        data_template:
          entity_id: "{% for state in states.light if state.state=='on' and (state_attr(state.entity_id, 'brightness') | int - states('input_number.' ~ state.entity_id.split('.')[1]) | int) | abs < 50 and (state.entity_id == 'light.family_room' or state.entity_id == 'light.fireplace') %}{% if loop.first %}{% else %},{% endif %}{{'input_number.' ~ state.entity_id.split('.')[1]}}{% endfor %}"
          value: '{% if states.input_number.dim_level.state|int > 30 %} {{ states.input_number.dim_level.state|int -20 }}{% else %}{{ states.input_number.dim_level.state|int}}{% endif %}'

So what do you think? Is this overly complicated or stupid? Can we make this better/simpler somehow?

7 Likes

I love this. I’ve been using the custom circadian rhythm component to change white balance of my lights in accordance with the sun.

That component also can handle dimming, but it does it in a simple way and lights are also dim in the morning while i’m getting ready.
I’m happy to see someone else taking their lighting schemes seriously.

I’m still processing the code you provided, I use a mixture of Philips Hue and LimitlessLED (Milight) bulbs and I’m excited to test the individual bulb adjustments for relative brightness.

I do similar but have a sensor that controls the dim level based on time of day. Then every 15 minutes any of my lights that are on are set to the brightness set by the sensor.

I have an automation that at turn on also sets the brightness; much like you have.

The sensor allows for smooth dimming from 90% to 40% at bedtime. I use brightness_pct as it’s easier to deal with.

That’s a cool looking component. Thanks!

Thanks eightiesguy for all your work putting this together! I have been trying to do the same thing for months and it seems you have perfected this automation! Being new to Home Assistant and coding (no computer science background) I am a little confused at how to make this work! Maybe you can help with a more basic setup, then I can build on it and add your additional enhancements! I have my bedroom lights connected to a zwave dimmer and would like to have them slowly dim to off between 8pm and 10pm. Is there a script that needs to be added and then a automation set-up referring to that script that needs to be setup? I have a good understand of setting up automations, but have not done any scripts yet. If you don’t mind walking me through what I need to do I would greatly appreciate the input! I think if I can get something simple done I can then work on some of the other enhancement you have included in your efforts. Thanks for any help you would be willing to give!