How to toggle all lights in an Area (or Group)?

I use Groups (another possibility seem to be Area, which makes more logical sense) to toggle a set of lights after pressing a zigbee button (button → zigbee2mqtt → MQTT message → toggle action on the group).

It works, except when someone manually toggles one of the lamps. What happens then is that the “toggle” message sent after pressing the zigbee button indeed toggles the lamps one by one, including the one in a different state.

Before jumping into writing my own “toggle” (where I would probably change the state of the majority of lamps) I would like to make sure that there is no built-in mechanism for that

  • either a “toggle group/area” action
  • or automation where I would say “if any of the lamps are on, then switch all of them off” (or similar)

All of the options you have suggested are possible…

Area-based

You can call your service on an area ID if you want to target all applicable entities in the Area. When using the UI editor, the area_id will be selectable by it’s friendly name, otherwise it can be found using the Template editor: {{area_id("Living Room")}}.

service: light.toggle
data: {}
target:
  area_id: 0352e80e71464487b396c5599c886de0

This will have the same issue when the lights have mixed states

Group-based

A light group’s ‘on’ state is based on whether ‘any’ of it’s component entities’ states are ‘on’.

trigger:
  - platform: mqtt
    topic: "living_room/switch/light"
condition: []
action:
  - service: light. turn_{{ iif( is_state('light.living_room_group', 'off'), 'on', 'off' ) }}
    data: []
    target:
      entity_id: light.living_room_group

This will turn all lights ‘off’ if any are ‘on’ but only turn them ‘on’ when all are ‘off’.

Proportional toggle

trigger:
  - platform: mqtt
    topic: "living_room/switch/light"
condition: []
action:
  - variables:
      count_on: >
        {{ expand(state_attr('light.living_room_group', 'entity_id'))
        | selectattr('state','eq', 'on') | list | count }}
      count_off: >
        {{ expand(state_attr('light.living_room_group', 'entity_id'))
        | selectattr('state','eq', 'off') | list | count }}
      light_action:  {{ iif( count_on >= count_off, 'off', 'on' ) }}
  - service: light. turn_{{ light_action }}
    data: []
    target:
      entity_id: light.living_room_group

This will turn all lights in the group ‘off’ when the number of light which are ‘on’ is greater than or equal to the number of lights that are ‘off’, otherwise it will turn all lights ‘on’.

A similar set of variables could be used to do the same based on the light entities in a given area:

  - variables:
      lights: >
        {{ expand(area_entities("living room")) | selectattr('domain', 'eq', 'light')| list }}
      count_on: "{{ lights | selectattr('state','eq', 'on') | list | count }}"
      count_off: "{{ lights | selectattr('state','eq', 'off') | list | count }}"
      light_action: "{{ iif( count_on >= count_off, 'off', 'on' ) }}"

Just be aware that some integrations will classify things in the light domain that you might not expect to be switching on/off with your room lights and you would need to reject those specific entities from the list created for the lights variable. Examples of this include indicator lights on ESPHome devices or screens controlled by the Browser Mod integration. Also, light groups may throw off the true ratio of ‘on’ vs. ‘off’.

Thank you very much for the extensive answer. Since I have quite a lot of such automation I will look at how to reuse the variables sections in a script I could just call with the area as the parameter (to get back on or off)

(I finally opened a new question about the problem with selectattr)

Holy cow, I got a bugger here:

I’m trying to create a script - one script to rule them all!
I want to be able to pass the area_id to the script so that I don’t have to duplicate this script for each area. Does anyone know if it is possible to pass a template to a variable within a script? Specifically, I’m trying to pass the area_id (in this case “office”) to the variables being declared to count_on and count_off (if I hard-code the area name, the script works)

Here is the script (“Proportional Toggle”):

alias: Proportional Toggle
icon: mdi:lightbulb-auto
description: >-
  This will turn all lights in the group ‘off’ when the number of light which
  are ‘on’ is greater than or equal to the number of lights that are ‘off’,
  otherwise it will turn all lights ‘on’.
sequence:
  - variables:
      count_on: >
        {{
        states.light|selectattr('entity_id','in',area_entities('{{AREA NAME SHOULD BE PASSED HERE}}'))|selectattr('state','eq','on')|
        list |count }}
      count_off: >
        {{
        states.light|selectattr('entity_id','in',area_entities('{{AREA NAME SHOULD BE PASSED HERE}}'))|selectattr('state','eq','off')|
        list |count }}
  - if:
      - condition: template
        value_template: "{{count_on >= count_off}}"
    then:
      - service: light.turn_off
        data: {}
        target:
          area_id: "{{area_name}}"
    else:
      - service: light.turn_on
        data: {}
        target:
          area_id: "{{area_name}}"
mode: single

Script: Proportional Toggle:

service: script.new_script
data:
  area_name: office

Yes, it is possible.

You can’t put templates inside templates i.e. {{ {{ }} }}… just put the variable in directly:

fields:
  area_name:
    name: Area Name
    description: The name of the Area
    required: true
    example: office
sequence:
  - variables:
      entities: "{{ area_entities(area_name) }}"
      count_on: >
        {{ states.light | selectattr('entity_id', 'in', entities) | selectattr('state','eq','on')
        | list | count }}
      count_off: >
        {{ states.light | selectattr('entity_id','in', entities) | selectattr('state','eq','off') |
        list | count }}
  - if:
      - condition: template
        value_template: "{{count_on >= count_off}}"
    then:
      - service: light.turn_off
        data: {}
        target:
          area_id: "{{area_name}}"
    else:
      - service: light.turn_on
        data: {}
        target:
          area_id: "{{area_name}}"
mode: single

You just made my weekend :slight_smile:

THANK YOU.

by the way…how did you know that? Is it just through wisdom and experience, or is this documented somewhere that I’ve yet to find?

It’s in the Jinja documents, linked in the very first few lines of the template documents.

Thank you guys for your help. This script has been working great.

I realize that I’m really now asking a question about programming: but how would one design this script so that it could handle more than one area at a time? Let’s say I want it to work for ‘Kitchen’ and ‘Dining Room’ (or more). It’s a loop of some kind I’m sure, but I’ve never been able to figure that out with jinja. Do you know how to do that?

Just chiming in that if you add an area selector to the field.

area_name:
  name: Area Name
  description: The name of the Area
  required: true
  example: office
  selector:
    area:

It will show up in the UI editor like this:
image

1 Like