Script Templating - turn off only lights that are on

Hey everyone

I’m using a script to turn off lights that are on in the house. I was originally using “groups.all_lights” for this, but since I have 40+ lights in my system, it take a long time - I assume because it’s interating through all of them. Instead, I thought I would write a script using a template to only turn off lights that are on, so I wrote the below

'48739802483908':
  alias: Test turning off lights
  sequence:
    - service: homeassistant.turn_off
      data_template:
        entity_id: >
          {% for state in states.light -%}
            {%- if state.state_with_unit =="on" %}
              {{state.entity_id}}
          {% endif -%}
          {%- endfor -%}

Now, when I run the script I get

Tue Jan 30 2018 21:47:33 GMT-0500 (EST)

Invalid service data for light.turn_off: Entity ID light.1st_floor_kpl__b

light.1st_floor_kpl__c            

light.1st_floor_kpl__d            

light.dining_room_micro            

light.dining_room_switch is an invalid entity id for dictionary value @ data['entity_id']. Got ['light.1st_floor_kpl__b    

Anyone know how to make this work properly?

Something is wrong with light.dining_room_switch. Your script works as expected in my network.

Thanks, but it looks to me that it’s complaining about the whole array, no? "Got ['light.1st_floor_kpl__b ". Or is this me not understanding the error?

Weird… Its odd, because using the the jinja2 template engine, this looks to me like its working correctly :frowning:

I am guessing there are multiple lights on when you see this error.

That loop is unlikely to work. You should use group.all_lights instead.

Last time I looked into something similar it was because the template doesn’t seem to actually evaluate to an array. Instead, it evaluates to a string that just looks like an array.

I posted somewhere about sending the value to a script that calls the service, which seemed to convert the value to the expected type, but I can’t find that post anymore.

Edit: found it

1 Like

Can I ask why you want to do this? To iterate over the lights this way should be no faster versus a simple homeassistant.turn_off call on the group.all_lights

To me it seems you are adding complexity that isn’t required.

Am I missing something?

They mention that it’s slow to call on 40+ lights. Some lights seem to have a fairly significant delay when calling turn on/off. If the logic for turning off group.all_lights isn’t taking into account that some are already off and skipping over them or asynchronously doing them all in parallel, it could waste a bunch of time calling turn_off on lights that are already off. Otherwise, yeah, group.all_lights would be easier.

It’s tough for me to know what is the actual issue, my assumption is that their a) the call is not being done async or b) it is async, but the receiving controller (ISY994) can’t handle all those events at once

Either way, I definitely think it is not evaluating state when sending the command to group.all_lights as it does appear to send it to all my lights - no matter the state.

ok, interesting. I will take a look at doing it this way

The issue is going full async likely gets serialized to go over a slow RF link to the actual devices.

WiFi devices maybe better due to more bandwidth being available and worth going “full async”

The controller may very be the bottleneck- I am not certain, but that misses the point. No need to send the off to 40+ devices, when only 3-4 may be on

But if asking for the state of a device takes the same time as asking them to turn off… whats the point? It really depends on how hass deals with your controller. If hass needs to ask your controller what the state of the device is, then your controller will still be a bottle neck.

A true test would be to install appdaemon. With yaml, I’m not sure how to create a for loop that can execute on a series of entities. Jinja is the limitation there.

With appdaemon, you would create an input_boolean and listen to it’s state. When its turned off, you would then cycle through lights active lights and shut them off. You would need another listener to listen to any light state change as well (to turn the input boolean on when a light in the group turned on).

I may have a chance to check this out at home later today because these speed tests pique my interest.

No, that’s actually not how this APPEARS to work… Using off w/ group.all_lights sends the command for each entity regardless of state. The script I posted asked HASS uses jinja to query the known states inside of HASS - it doesnt query the controller.

I haven’t had time to play with it, but I will either fix this as @tboyce1 suggested, or more likely, use appdaemon just so that the script will be cleaner.

You probably don’t need appdaemon even if you want to go the coding route. A python script should be enough, assuming you already have your automation trigger and only need this for the action.

I actually forgot that we could directly use scripts now. Thanks for the reminder! I will take a look at that

Did you ever get this working?

@tboyce1,

So If i am reading right would this work?

 - alias: Light's Left On!!!
  trigger:
    platform: state
    entity_id: group.trackers
    to: 'not_home'
  action:
    - service: homeassistant.turn_off
      entity_id: >
        {% for device in dict(states.light|groupby("state"))["on"] %}
        {%- if loop.first %}["{% else %}", "{% endif %}{{ device.entity_id | lower }}
        {%- if loop.last %}"]{% endif %}
        {%- endfor  %}
    - service: notify.pushover
      data_template:
        title: "Alert"
        message: "No One Is Home And The Lights Were Left On. I Turned Them Off For You"

I would test this but everyone is home and I don’t think they want the lights going off.

This is a Python script that works:

"""Turn off lights that are on."""

lights = []
for state in hass.states.all():
    if state.entity_id.startswith("light.") and state.state == "on":
        lights.append(state.entity_id)

if lights:
    data = { "entity_id" : lights }
    hass.services.call('light', 'turn_off', data)

Save it to config/python_scripts/lights_off.py and use like this:

python_script:

automation:
  - alias: "Lights off"
    trigger:
    # ...
    action:
      service: python_script.lights_off
4 Likes

I was able to make this work using the following. It does work with the exception of One bulb that thinks it is on but actually turned off at the light switch( err, family).

- alias: Light's Left On!!!
  trigger:
    platform: state
    entity_id: group.trackers
    to: 'not_home'
  action:
    - service: light.turn_off
      data_template: 
        entity_id: > 
         {%- for device in dict(states.light|groupby("state"))["on"] %}{%- if loop.first %}{% else %}, {% endif %}{{device.entity_id | lower }}{%- if loop.last %}{% endif %}{%- endfor  %}
    - service: notify.pushover
      data_template:
        title: "Alert"
        message: "No One Is Home And The Lights Were Left On. I Turned Them Off For You"

FYI this is an automation not a script.

2 Likes