basnijholt / adaptive-lighting

Adaptive Lighting custom component for Home Assistant
https://basnijholt.github.io/adaptive-lighting/
Apache License 2.0
1.77k stars 131 forks source link

Take Over Control When Group Is Mismatched (`watched_lights`) #451

Open cramertj opened 1 year ago

cramertj commented 1 year ago

The README says:

A good rule to follow is that if you always control lights together (like bulbs in a ceiling fixture), then they should be in a Zigbee group. Then, only expose the group (and not individual bulbs) in Home Assistant Dashboards and external systems like Google Home or Apple HomeKit.

Unfortunately, it's often the case that I want to change the color or brightness of single lights, and I have several overlapping zones of lights that I control. This means that, when controlling lights at a granularity lower than the groups that are exposed to Adaptive Lighting, I get annoying behavior:

Pointing AL at each individual light or all the intersections of subgroups of lights destroys performance.

One potential solution to this issue would be to allow specifying to AL a separate set of "watched" lights from the control groups. These watched lights would not be used for control, but would be used to trigger "take over control". If a watched light is changed manually, or if a watched light is set to a different on/off state from others, take over control would be triggered. For example:

adaptive_lighting:
  lights:
    - light.living_room_lights
  watched_lights:
    - light.table_lamp
    - light.sconce_left
    - light.sconce_right
    # etc.

would only send adaptations to light.living_room_lights. If light.table_lamp is on, but light.sconce_left is on, however, light.living_room_lights would not be adapted. Similarly, if light.table_lamp has its color changed manually, light.living_room_lights would stop being adapted.

cramertj commented 1 year ago

I'd also be interested in implementing this change if it (or a similar solution to this issue) is wanted.

th3w1zard1 commented 1 year ago

Can you elaborate a lil bit? Is your light.living_room_lights made up of different lights than the ones you have listed in watched_lights?

Why not just create a separate config for each light? The section of the readme you linked to is specified for zigbee groups and you don't even say if you're using zigbee.

th3w1zard1 commented 1 year ago

In your example, does living_room_lights attributes get updated by all of watched_lights? Specifically what updates the color temp and brightness values of living_room_lights?

I see what you're saying now. I think the best way to handle this is with an automation for now? Just call adaptive_lighting.set_manual_control for the light if there's any state changes to the individual lights in your watched_lights

cramertj commented 1 year ago

@th3w1zard1

Is your light.living_room_lights made up of different lights than the ones you have listed in watched_lights?

No, to clarify, in my example living_room_lights is a zigbee group which contains table_lamp, sconce_left, and sconce_right.

Why not just create a separate config for each light? The section of the readme you linked to is specified for zigbee groups and you don't even say if you're using zigbee.

Yes, I am using zigbee. Sorry, I figured that was implied from the question, but I realize now that I didn't specify. Thanks for clarifying! Manually specifying the individual lights as arguments to AL results in poor performance given that all of the lights are read and adapted individually.

I think the best way to handle this is with an automation for now? Just call adaptive_lighting.set_manual_control for the light if there's any state changes to the individual lights in your watched_lights

Right-- the trick is to figure out which state changes to the individual lights are triggered by (1) simple turn on (2) modification by adaptive_lighting or (3) manual brightness/color modification. (1) or (2) should not result in calls to set_manual_control, but (3) should. Simply listening to state changes for the lights would result in manual control in all cases (1, 2, and 3).

th3w1zard1 commented 1 year ago

Hmm I see... Try this automation. Input your lights in the correct spots.

alias: "[Adaptive Lighting] State Changed -> Set manual control DEV"
description: ""
trigger:
  - platform: event
    event_type: state_changed
condition:
  - condition: template
    value_template: "{{ domain == \"light\" }}"
  - condition: template
    value_template: "{{ newstate == \"on\" }}"
  - condition: template
    value_template: "{{ oldstate == \"on\" }}"
  - condition: template
    value_template: "{{ context_parent_id == none }}"
  - condition: template
    value_template: "{{ context_user_id == none }}"
  - condition: template
    value_template: "{{ \"adapt_lgt_\" not in context_id }}"
  - condition: or
    conditions:
      - condition: template
        value_template: "{{ (new_color_temp|float(0) - old_color_temp|float(0))|abs >= 20 }}"
      - condition: template
        value_template: "{{ (new_brightness|float(0) -  old_brightness|float(0))|abs >= 25 }}"
  - condition: or
    conditions:
      - condition: template
        value_template: "{{ light == 'light.entity_id_individual_light_A' }}"
      - condition: template
        value_template: "{{ light == 'light.entity_id_individual_light_B and so on' }}"
action:
  - service: system_log.write
    data:
      message: >-
        Monitor Adaptive Lighting ({{ light }}/{{ newlight }}/{{ oldlight }})
        changed by {{ context_user_id }}/{{ context_id }}/{{ context_parent_id
        }}. Brightness ({{ old_brightness }}-> {{ new_brightness }}). Color Temp
        ({{ old_color_temp }}-> {{ new_color_temp }})
      level: warning
  - service: adaptive_lighting.set_manual_control
    data:
      entity_id: "{{ \"your_main_adaptive_lighting_switch_goes_here\" }}"
      lights: "{{ your_main_lightgroup_goes_here }}"
      manual_control: true
mode: parallel
max: 100
variables:
  light: "{{ trigger.event.data.entity_id | default }}"
  newlight: "{{ trigger.event.data.new_state.entity_id | default }}"
  oldlight: "{{ trigger.event.data.old_state.entity_id | default }}"
  domain: "{{ trigger.event.data.new_state.domain | default }}"
  newstate: "{{ trigger.event.data.new_state.state | default }}"
  oldstate: "{{ trigger.event.data.old_state.state | default }}"
  context_user_id: |-
    {% if trigger.event.data.new_state != None %}
      {{ trigger.event.data.new_state.context.user_id | default }}
    {% else %}
      {{ None }}
    {% endif %}
  context_id: |-
    {% if trigger.event.data.new_state != None %}
      {{ trigger.event.data.new_state.context.id | default }}
    {% else %}
      {{ None }}
    {% endif %}
  context_parent_id: |-
    {% if trigger.event.data.new_state != None %}
      {{ trigger.event.data.new_state.context.parent_id | default }}
    {% else %}
      {{ None }}
    {% endif %}
  new_brightness: |-
    {% if trigger.event.data.new_state != None %}
      {{ trigger.event.data.new_state.attributes.brightness | default }}
    {% else %}
      {{ None }}
    {% endif %}
  old_brightness: |-
    {% if trigger.event.data.old_state != None %}
      {{ trigger.event.data.old_state.attributes.brightness | default }}
    {% else %}
      {{ None }}
    {% endif %}
  new_color_temp: |-
    {% if trigger.event.data.new_state != None %}
      {{ trigger.event.data.new_state.attributes.color_temp | default }}
    {% else %}
      {{ None }}
    {% endif %}
  old_color_temp: |-
    {% if trigger.event.data.old_state != None %}
      {{ trigger.event.data.old_state.attributes.color_temp | default }}
    {% else %}
      {{ None }}
    {% endif %}

hopefully your feature can be implemented in the near future, but I expect this'll do what you want? Don't worry about performance impact either, I remember running this automation on a raspberry pi a few years ago.

cramertj commented 1 year ago

@th3w1zard1 It looks to me like that automation is looking for changes to individual lights that result in a change in brightness or color temperature, the context_id does not contain adapt_lgt_, no user_id or parent_id is set, and where the light was on both before and after the change. Does that sound right to you?

If so, is it enough to filter it down to just context_id does not contain adapt_lg nor a parent_id? I want manual mode to be disabled if any individual light is turned on or off (so the on before + on after isn't what I want), and I don't understand why we'd want to filter out changes with user_id values.

th3w1zard1 commented 1 year ago

The automation I pasted already filters out light changes by adaptive lighting. You shouldn't need to edit it outside of the locations I marked for you to input your lights.

th3w1zard1 commented 1 year ago

the 'adapt_lgt' stuff for context_id is necessary because otherwise any changes detected from adaptive-lighting will cause a manual control event. I don't remember how the user_id or the parent_id works, sorry. if you're having issues, try removing those lines and indeed just filter by adapt_lgt

th3w1zard1 commented 1 year ago

@cramertj I added your feature to #568 hopefully it'll be in the next release but otherwise you're more than welcome to test it out now if you like.

cramertj commented 1 year ago

@th3w1zard1 Amazing! Thank you-- I'll try it out.

th3w1zard1 commented 1 year ago

@cramertj You might be able to get away with assigning all of your individual lights to AL with the latest update. I see you've mentioned significant performance issues without groups. @basnijholt and I added a check to ensure the service data differs from the light's actual state, in other words if AL detects that the light doesn't need to be updated, it won't call light.turn_on anymore. Perhaps that plus a large enough interval you won't see the performance impact?

basnijholt commented 5 months ago

[Copy pasting this message in a few recent open issues]

I just wanted to take a moment to express my heartfelt thanks to everyone that is active in this repo. Your contributions, from answering questions to addressing issues, have been invaluable. It's amazing to see how supportive and helpful our community is!

Adaptive Lighting is all about enhancing your living spaces with smart, sunlight-responsive lighting. We've had quite a few discussions and open issues recently, and I see this as a positive sign of our community's engagement and growth. If you come across anything in the documentation that's unclear or if you have suggestions for improvement, please don't hesitate to share!. Your feedback is crucial for making Adaptive Lighting better for everyone.

On a personal note, I've recently welcomed twin boys into my family, which has been an incredible and life-changing experience. As you can imagine, my time is now more limited, and while I'm doing my best to keep up with the project, there may be delays in my responses. I appreciate your understanding and patience during this time.

Rest assured, I'm fully committed to addressing any bugs, especially those related to new Home Assistant updates, as swiftly as possible. I understand that many issues may stem from hardware limitations or misunderstandings about things like Zigbee groups. Your continued support and collaboration in helping each other out not only strengthen our community but also enhance the Adaptive Lighting experience for all.

Thank you once again for your understanding, patience, and support. Let's keep our houses well lit and adaptive for maximal enjoyment of life! πŸŒžπŸ πŸŒ™