Eureka, halleluja, light-bulb-moment, wohoo and all that!
After what feels like solving a brain puzzle and diving deep into programming and mathematics over the last few days I finally figured it out myself
First off - after extensive searching here I’m amazed no one else seems to have done this, or asked for it… am I really the first?
Secondly - I’m equally amazed that I managed to pull this off with just a template light and a group; no automations or scripts needed
This should be pretty much on par with the dimming of lights in the IKEA Smart Home app:
- When the group brightness is adjusted, all individual lights are adjusted by the same amount
- So the “brightness distance” between lights will remain the same (until any lights reach max or minimum brightness)
- Individual lights that would turn off as the result of changing the group brightness will remain turned on
- Lights that have been turned off remain turned off when the group brightness is changed
- However, if all lights are off then any change to group brightness will turn all of them on
- When the group brightness is set to 100%, all lights currently on will also change to 100% brightness
- If all the lights are currently on when setting the group to 100%, all lights in the group will be set to 100%
Alright, enough talking, here’s my code:
groups.yaml
office:
name: Office
entities:
# The template light will ignore anything other than a light
- light.office_spotlights
- light.office_top_corner
- light.office_desk_lamp
configuration.yaml
light:
- platform: template
lights:
office_lights_smart:
friendly_name: "Office lights smart"
# Average of all lights currently on
level_template: >
{{ ( expand('group.office')
| map(attribute='attributes.brightness', default=0)
| select("greaterthan", 0)
| list or [0])
| average
| round }}
# True if any light has a brightness larger than 0
value_template: >
{{ expand('group.office')
| map(attribute='attributes.brightness', default=0)
| sum > 0 }}
turn_on:
service: light.turn_on
target:
entity_id: group.office
turn_off:
service: light.turn_off
target:
entity_id: group.office
set_level:
# Set brightness by amount to all lights (when all are off), OR only the lights that would remain on by the brightness change
- service: light.turn_on
data:
brightness_step: "{{ brightness - state_attr('light.office_lights_smart', 'brightness')|int }}"
target:
entity_id: >
{% set brightness_from = state_attr('light.office_lights_smart', 'brightness')|int %}
{% set brightness_change = brightness - brightness_from %}
{{ expand("group.office")
| selectattr('attributes.brightness')
| selectattr('attributes.brightness', '>', -brightness_change)
| map(attribute='entity_id')
| list or
expand('group.office')
| selectattr('domain', '==', 'light')
| map(attribute='entity_id')
| list if brightness != 255 else [] }}
# Set brightness to 1 for all lights that would turn off, keeping them on
- service: light.turn_on
data:
brightness_pct: 1
target:
entity_id: >
{% set brightness_from = state_attr('light.office_lights_smart', 'brightness')|int %}
{% set brightness_change = brightness - brightness_from %}
{{ expand('group.office')
| selectattr('attributes.brightness')
| selectattr('attributes.brightness', '<=', -brightness_change)
| map(attribute='entity_id')
| list
}}
# Set brightness to 100 for all lights currently on when slider moves to 100%, or all the lights if all are currently off
- service: light.turn_on
data:
brightness_pct: 100
target:
entity_id: >
{{ expand("group.office")
| selectattr('attributes.brightness')
| map(attribute='entity_id')
| list or
expand('group.office')
| selectattr('domain', '==', 'light')
| map(attribute='entity_id')
| list if brightness == 255 else [] }}
Lovelace YAML (for GIF above)
type: grid
columns: 1
square: false
cards:
- type: entities
state_color: true
title: Office lights
show_header_toggle: true
entities:
- entity: light.office_lights_smart
type: custom:slider-entity-row
step: 1
full_row: true
- entity: light.office_top_corner
type: custom:slider-entity-row
step: 1
toggle: true
- entity: light.office_spotlights
type: custom:slider-entity-row
step: 1
toggle: true
- entity: light.office_desk_lamp
type: custom:slider-entity-row
step: 1
toggle: true
Plugins used: slider-entity-row
Some lessons learned
When using brightness_step
to change the brightness of a light group the step is not forwarded to each individual light, but instead applied to the group as a whole. Would have made everything so much easier if it wasn’t like that
I started getting errors in the logs because I was trying to calculate the | average
on an empty list when all lights were off. The trick was to put that part of the filter within round brackets and add the or [0]
.
Then I used the same technique to list all the light entities whenever the list of “lights being so close to 0 that they would turn off” was empty.
So in the end what happens is that set_level
will apply
- Brightness step: (difference in brightness)
- on all lights that wouldn’t get a negative brightness by the change, i.e. turning off
or - on all lights whenever all lights are already turned off
- on all lights that wouldn’t get a negative brightness by the change, i.e. turning off
- Brightness percent: 1
- on all lights that would get a negative brightness by the change, which basically keeps them turned on
- Brightness percent: 100
- when group brightness is adjusted to max, set all lights currently on to 100% or all the lights if none were on
Don’t hesitate to ask if you want me to explain something further! I really hope this helps someone else who wants a smarter way of dimming their group of lights!
Now I just need to figure out how I can re-use this code, basically just changing out group.office_lights
for different rooms…