Switch everything off except excluded entities

When searching for a good way to turn off all lights except x, y and z I didn’t find any solutions that are functional and easy configurable at the same time.

So this is a minimal example of my approach. Everything is configured in one config file (scripts.yaml) and easy to maintain for future changes. This method can be adapted for other device classes as well. If you want to turn off all media players or switches excluding some entities, just change the service call in line 4 and the exclude list below.

Prerequisite: This approach requires the service to accept entity_ids as comma separated lists. At the time of writing I successfully tested this with the light, switch and media_player turn_on & turn_off service calls. There maybe others as well, but be prepared, that it might not work for your use case.

all_out:
  sequence:
    - service: light.turn_off
      data_template:
        entity_id: >
          {% set exclude_light = [
            'light.house_number',
            'light.aquarium',
            'light.entrance'
          ] %}
          {%- for device in states.light|rejectattr('entity_id','in',exclude_light)|rejectattr('state','in','off') %}{%- if loop.first %}{%- else %}, {% endif %}{{device.entity_id }}{%- if loop.last %}{% endif %}{%- endfor  %}

Some explanation for the data_template:

set exclude_light is a list of entity_ids that shouldn’t be affected by the script, i.e. excluded. You won’t get errors, if you forget to delete the comma after the last entry, but of course it’s good programming style if you remember to do it. :slight_smile:

states.light|rejectattr('entity_id','in',exclude_light)|rejectattr('state','in','off') is a set of (jinja) filters for objects.

It starts with all light entity objects known to HA (states.light). The first filter excludes all light entities from the list with an entity_id contained in the exclude_light list defined before: “…|rejectattr('entity_id','in',exclude_light)…”

The second filter excludes all lights which are already off and don’t need to be switched off again: “...|rejectattr('state','in','off')...”. You may omit this filter, as it not crucial for the template to work, but just turning off devices that actually need to be turned off significantly reduces the noise on the HA event bus if you have a lot of devices in your config.

The filtered list is fetched to the for loop. The loop concatenates the entity_ids ({{device.entity_id }}) of the remaining entities to a comma separated list, omiting commas for the first ({%- if loop.first %}{%- else %}, {% endif %}) and the last ({%- if loop.last %}{% endif %}) loop pass to suppress commas before the first and after the last entity_id.

9 Likes

Not a bad workaround, thanks!
Let’s not forget tho - the feature request is still valid and should be fairly easy to implement.

A simple built-in exclude function that everyone can start using easily would still be ideal.

Thank you for sharing this, and specially thank you for explaining the templating. Understanding/learning jinja is helpful to all.

I have to add, that there is one flaw in this method. If everything is turned off already, the template above provides an empty result for the entity_id, leading to errors like this in the log:

2018-11-23 13:18:48 ERROR (MainThread) [homeassistant.core] Invalid service data for light.turn_off: Entity ID  is an invalid entity id for dictionary value @ data['entity_id']. Got ''

To avoid this error, you may drop the filter for devices already turned off, i.e. deleting the “...|rejectattr('state','in','off')...” part from the filter.

Another way to avoid an empty result would be to add one of the turned off entities back to the list, but I didn’t find an elegant way to do that without completely rewriting the template. If you have a nice solution for this, don’t hesitate to post it here. :slight_smile:

2 Likes

Hi all,
I wanted to share another way that I had to come up with. I use Alexa Media Player, WLED instances and AdGuard Home that create many switches. I could probably hide those since I do not use them, but I wanted to solve it programmatically.

This creates a list with exclusions by pattern ‘starts with’ and ‘ends with’

lights_out:
  alias: 'All lights and switches off script'
  sequence:
    - service: light.turn_off
      data_template:
        entity_id: >
          {% set exclude_light = [
            'light.fake'
          ] %}
          {%- for device in states.light|rejectattr('entity_id','in',exclude_light) %}{%- if loop.first %}{%- else %}, {% endif %}{{device.entity_id }}{%- if loop.last %}{% endif %}{%- endfor  %}
    - service: switch.turn_off
      data_template:
        entity_id: >
          {%- for device in states.switch if not ( null 
            or device.entity_id.startswith('switch.laundry_closet')
            or device.entity_id.startswith('switch.christmas_lights')
            or device.entity_id.startswith('switch.front_door_outside_plug')
            or device.entity_id.startswith('switch.adguard')
            or device.entity_id.startswith('switch.aleksey')
            or device.entity_id.startswith('switch.this_device')
            or device.entity_id.startswith('switch.living_room')
            or device.entity_id.startswith('switch.bedroom')
            or device.entity_id.startswith('switch.everywhere')
            or device.entity_id.endswith('sync_send')
            or device.entity_id.endswith('sync_receive')) -%}
          {%- if loop.first %}{%- else %}, {% endif %}{{device.entity_id }}{%- if loop.last %}{% endif %}{%- endfor  %}

Great solution @derandiunddasbo ! I use it as script this way:

lights_off_except:
  icon: mdi:home-lightbulb
  fields:
    exclude_lights:
      description: 'Excluded lights as list'
  sequence:
    - service: logbook.log
      data_template:
        entity_id: script.turn_off_lights
        name: Exclude log
        message: "Turning of all lights except: {{ exclude_lights }}"
    - service: light.turn_off
      data_template:
        entity_id: >
          {%- for device in states.light|rejectattr('entity_id','in', exclude_lights )|rejectattr('state','in','off') %}{%- if loop.first %}{%- else %}, {% endif %}{{ device.entity_id }}{%- if loop.last %}{% endif %}{%- endfor  %}

And then calling it:

service: script.lights_off_except
exclude_lights:
  - light.switch_hallway_light_light
  - light.wall_switch_hallway_light 

It looks like it works fine. This is solution for my long living problem! Can be used with other solution that query the excluded group by some condition.

2 Likes

Oh nice, calling it as a separate script makes the config looking much cleaner. Thanks for sharing @lhoracek.

Similar version… use the homeassistant option to turn off lights, switches and fans with the ability to exclude. Few extra bits in there to stop adguard being messed with and switches that happen to be connected to wifi fans. Sonos doesn’t seem to like turn_off so split them off to a separate script to call media_stop instead. Feels clunky coming from other languages, but at least conditions stop errors

all_off:
  alias: Turn Off Fans, Lights, Switches and Sonos
  mode: single
  sequence:
    - service: script.all_media_off
      data_template:
        exclude: >
          {%- for device in states|selectattr('entity_id','in',exclude) %}{%- if loop.first %}{%- else %}, {% endif %}{{ device.entity_id }}{%- endfor  %}
    - service: script.all_devices_off
      data_template:
        exclude: >
          {%- for device in states|selectattr('entity_id','in',exclude) %}{%- if loop.first %}{%- else %}, {% endif %}{{ device.entity_id }}{%- endfor  %}

all_media_off:
  alias: Turn Off Sonos
  mode: single
  sequence:
  - condition: template
    value_template: >
      {{ states.media_player|rejectattr('entity_id','in',exclude )|selectattr('state','in','playing')|list|count > 0 }}
  - service: media_player.media_stop
    data_template:
      entity_id: >
        {%- for device in states.media_player|rejectattr('entity_id','in',exclude)|selectattr('state','in','playing') %}{%- if loop.first %}{%- else %}, {% endif %}{{ device.entity_id }}{%- endfor  %}

all_devices_off:
  alias: Turn Off Fans, Lights and Switches
  mode: single
  sequence:
  - condition: template
    value_template: >
      {% set count = namespace(value=0) %}
      {%- for device in expand(states.fan,states.light,states.switch)|rejectattr('entity_id','in', exclude )|rejectattr('state','in','off')
      if not ( null or device.entity_id.startswith('switch.adguard') or (device.entity_id.startswith('switch') and device.entity_id.endswith('fan'))) -%}
      {% set count.value = count.value + 1 %}{%- endfor  %}{{ count.value > 0 }}
  - service: homeassistant.turn_off
    data_template:
      entity_id: >
        {%- for device in expand(states.fan,states.light,states.switch)|rejectattr('entity_id','in', exclude )|rejectattr('state','in','off') if not ( null 
          or device.entity_id.startswith('switch.adguard') or (device.entity_id.startswith('switch') and device.entity_id.endswith('fan'))) -%}
        {%- if loop.first %}{%- else %}, {% endif %}{{device.entity_id }}{%- endfor  %}