I have created a script that turns off all the lights in the house that have not been updated for last 10 hours. This script will be triggered by a “sunrise” automation. (Would love to change that constant 10 hours to be last sunset, but that’s for the future). Here is my script:
turn_off_forgotten_lights:
alias: "Turn Off Forgotten Lights"
sequence:
- service: light.turn_off
data_template:
entity_id: >
{%- for entity_id in states.light|selectattr('state','eq','on')|map(attribute='entity_id') -%}
{%- set parts = entity_id.split('.') -%}
{%- if (as_timestamp(now()) - as_timestamp(states[parts[0]][parts[1]].last_updated)) > 36000 -%}
{{ entity_id }},
{%- endif -%}
{%- endfor -%}light.switchlinc_dimmer_44_XX_de
As you can see my problem is that comma. I can’t figure out a way to create a proper list. I tried to put comma before the entity only when it is not the first entity. To do so I created a variable to keep track of first entity_id. But creating this variable outside the for loop gets out of scope, and I can’t create it inside the loop as it would keep resetting. I also cannot use loop.first or loop.last because there is an if inside which means first entity doesn’t necessarily mean first loop.
For now, I end up putting a light entity (light.switchlinc_dimmer_44_XX_de) at the end so that I can end the list without a comma. This light is in my crawlspace and is pretty much always off, and I am hoping that no one will be in the crawlspace during sunrise. But I need a proper solution.
Can someone help me with creating a proper comma separated list without the hard-coded light in it? Any way to create arrays and I can then join the array with .join(',')?
turn_off_forgotten_lights:
alias: "Turn Off Forgotten Lights"
sequence:
- service: light.turn_off
data_template:
entity_id: >
{% set from = (utcnow()|as_timestamp - 10*60*60)|timestamp_utc %}
{% set from = strptime(from ~ '+0000', '%Y-%m-%d %H:%M:%S%z') %}
{{ states.light|selectattr('state','eq','on')
|selectattr('last_changed','<',from)
|map(attribute='entity_id')|join(', ') }}
This first gets a string representation of the UTC time 10 hours ago. Then it converts that back into a Python time zone aware datetime object. Then it selects all lights that are on and have a last_changed field that is later than 10 hours ago, grabs their corresponding entity_id's, and joins them into a comma separated list.
Note that you need to guard against calling this script if none of the lights meet that criteria, because then it will call the service with an empty entity_id option. Or you could add a condition to the script so the service is only called if at least one light meets the criteria have it return none in this case:
turn_off_forgotten_lights:
alias: "Turn Off Forgotten Lights"
sequence:
- service: light.turn_off
data_template:
entity_id: >
{% set from = (utcnow()|as_timestamp - 10*60*60)|timestamp_utc %}
{% set from = strptime(from ~ '+0000', '%Y-%m-%d %H:%M:%S%z') %}
{% set lights = states.light|selectattr('state','eq','on')
|selectattr('last_changed','<',from)
|map(attribute='entity_id')|join(', ') %}
{{ lights if lights | length > 0 else 'none' }}
EDIT: Updated per suggestion & correction noted in below posts.
I recently fulfilled the same requirement by supplying none when there are no entities that meet the criteria. It eliminates the need to duplicate the action template in a condition.
turn_off_forgotten_lights:
alias: "Turn Off Forgotten Lights"
sequence:
- service: light.turn_off
data_template:
entity_id: >
{% set from = (utcnow() | as_timestamp - 10*60*60) | timestamp_utc %}
{% set from = strptime(from ~ '+0000', '%Y-%m-%d %H:%M:%S%z') %}
{% set lights = states.light
| selectattr('state', 'eq', 'on')
| selectattr('last_changed', '<', from)
| map(attribute='entity_id') | join(', ') %}
{{ lights if lights | length > 0 else 'none' }}
So when there are no entities to turn off, the action quietly does nothing.
EDIT
Included change suggested by Mariusthvdb. Replaced > with <.
NOTE
Solution post now incorporates all suggested changes.
Since I was looking for something like this (find left entities in an on state for over x hours) was glad to find this.
Still, this doesn’t really do what OP asked does it? This turns off all lights that are on, and are turned on within the last 10 hours.
It doesnt only turn off lights that have been on for the last 10 hours (or more)…
or am I misreading (and testing ) this?
if a light would have been left alone for 10 hours, shouldn’t it be
Yes, I believe you are correct. Seems I got it backwards. In fact when I tested the template, it returned only lights that had gone on more recently than 10 hours ago.
I’ll update my solution above based on your and @123’s correction/suggestion.
Since all other(core or custom) integrations only show the next sunset (or am I missing the obvious here Phil) wouldn’t be the easiest way to do this to create an input_boolean.sun and set that automatically on sun_rise/sun_set. So at all times, you would know the last time it changed, and use that in the script at hand?