I came here a few days ago and the great community helped out and explained my issue. So back now. Trying to get a light off after no motion for 10 mins.
Current automation. What am I missing.
alias: Office Light Off No Motion
description: Turn Off Office Light 10 Mins After Last Motion
trigger:
- entity_id: binary_sensor.wyzemotion
for: 00:10:00
platform: state
to: 'off'
condition:
- condition: state
entity_id: light.office_light
state: 'on'
action:
- entity_id: light.office_light
service: light.turn_off
That will only trigger the first time the motion sensor is off for 10 minutes; if the light was not ‘on’ at that time nothing will happen and then it wont trigger again until motion triggers it to on and then off again and then 10 minutes expires.
I’ve used a timer for this instead. On motion, (re)start timer for 10 minutes. When timer expires, turn off light.
If just after motion, then your automation will do that. But I think maybe you mean 10 minutes after the light goes on if there hasn’t been motion for at least 10 minutes, or 10 minutes after there is no motion if the light has been on for at least 10 minutes. So…
alias: Office Light Off No Motion
description: Turn Off Office Light 10 Mins After Last Motion
trigger:
- entity_id: light.office_light
for: 00:10:00
platform: state
to: 'on'
- entity_id: binary_sensor.wyzemotion
for: 00:10:00
platform: state
to: 'off'
condition:
- condition: state
entity_id: binary_sensor.wyzemotion
for: 00:10:00
state: 'off'
- condition: state
entity_id: light.office_light
for: 00:10:00
state: 'on'
action:
- entity_id: light.office_light
service: light.turn_off
I think the most practical way is to have the light on no more than 10 minutes using motion events as additional information that affects the simple “turn off the light after 10 minutes”.
Actually, I have a second version that I created for the very same reason, and it works most of the time but sometimes the light stays on and that’s annoying. Here it goes:
sensor.yaml
- platform: template
sensors:
time_to_turn_off_light_ground_floor_no_motion:
entity_id:
# the first two are added to change state of this sensor more real-time
- light.ground_floor_hall
- binary_sensor.pir_ground_floor_hall
# this is to re-evaluate the template every minute
- sensor.time
# this is to react on changes in the delay value
- input_number.turn_off_light_ground_floor_hall_after
value_template: >
{% set light_object = states.light.ground_floor_hall %}
{% set pir_object = states.binary_sensor.pir_ground_floor_hall %}
{# if the light is off or PIR is on - nothing to do #}
{% if light_object.state == 'on' and pir_object.state == 'off' %}
{% set now = utcnow() | as_timestamp %}
{% set light_is_on_at = light_object.last_changed | as_timestamp %}
{% set last_motion_detected_at = pir_object.last_changed | as_timestamp %}
{% set start_time = last_motion_detected_at if last_motion_detected_at > light_is_on_at else light_is_on_at %}
{% set turn_off_light_after = states('input_number.turn_off_light_ground_floor_hall_after') | int * 60 %}
{{ now > (start_time + turn_off_light_after) }}
{% endif %}
automation.yaml
- alias: turn_off_light_ground_floor_landing_if_no_motion
trigger:
- platform: state
entity_id: binary_sensor.time_to_turn_off_light_ground_floor_no_motion
to: 'on'
action:
- service: light.turn_off
entity_id: light.ground_floor_hall
looks like your solution should work, nice one!
the only issue I can see is how to change the code so it allows for dynamic delays, i.e:
because if the value of that input_number is decreased during that 10 min cycle it won’t affect the automation.
I think I’ll need template conditions instead of state ones as the latter do not support for templates, do they?
Actually, it’s most likely Phil who added templates support to for in state triggers. Unfortunately, we don’t have the same functionality in condition: state yet, it would make the code much simpler.
Anyway, I’ll post my version with configurable off delays here just in case anyone needs it.
Hmm, interesting. Yes, it might be nice to allow a template in the state condition’s for option. I just took a quick look at the code and it might not be too difficult. In fact, if this were to be done then it might also make sense to allow a template in the state option as well…
- condition: state
entity_id: ENTITY_ID
state: STATE or TEMPLATE
for: TIME or TEMPLATE
On the other hand, it might make just as much, or maybe even more, sense to (just???) add a for option to the template condition (which, of course, would accept either a fixed time or a template.)
Hmm…
EDIT: Actually, if these enhancements were made to the state condition, it might also make sense to allow a list of entity_id's, too, which would mean all the specified entities would have to satisfy the condition. Then something like this:
- condition: state
entity_id: ENTITY_ID_1
state: SOME_STATE
- condition: state
entity_id: ENTITY_ID_2
state: SOME_STATE
could be simplified to:
- condition: state
entity_id:
- ENTITY_ID_1
- ENTITY_ID_2
state: SOME_STATE
Hmm…
I wonder if the core team would go for any of this.
EDIT 2: I just realized having any kind of for option (fixed or templated) for a template condition wouldn’t be very practical. It would mean that the rendered template value hasn’t changed for that amount of time, which would not be very easy to determine. So scratch that idea! But the other ideas (for the state condition) I still think would make sense and shouldn’t be difficult to implement. I’ll consider it.
very interesting thoughts indeed!
I’d love to see some of these improvements.
so far the monster automation that works for me looks like this (I have 2 because of 2 floors):
- alias: turn_off_light_ground_floor_landing_if_no_motion
trigger:
- platform: template
value_template: >
{% set entity_id = 'light.ground_floor_hall' %}
{% set ctime = states('sensor.date_time_utc') %}
{% if is_state(entity_id, 'on') and ctime != 'unknown' %}
{% set last_changed = states[entity_id].last_changed | as_timestamp %}
{% set delay = states('input_number.turn_off_light_ground_floor_hall_after') | int * 60 %}
{{ (strptime(ctime, '%Y-%m-%d, %H:%M') | as_timestamp) > (last_changed + delay) }}
{% endif %}
- platform: template
value_template: >
{% set entity_id = 'binary_sensor.pir_ground_floor_hall' %}
{% set ctime = states('sensor.date_time_utc') %}
{% if is_state(entity_id, 'on') and ctime != 'unknown' %}
{% set last_changed = states[entity_id].last_changed | as_timestamp %}
{% set delay = states('input_number.turn_off_light_ground_floor_hall_after') | int * 60 %}
{{ (strptime(ctime, '%Y-%m-%d, %H:%M') | as_timestamp) > (last_changed + delay) }}
{% endif %}
condition:
- condition: template
value_template: >
{% set entity_id = 'light.ground_floor_hall' %}
{% if is_state(entity_id, 'on') %}
{% set last_changed = states[entity_id].last_changed | as_timestamp %}
{% set delay = states('input_number.turn_off_light_ground_floor_hall_after') | int * 60 %}
{{ (utcnow() | as_timestamp) > (last_changed + delay) }}
{% endif %}
- condition: template
value_template: >
{% set entity_id = 'binary_sensor.pir_ground_floor_hall' %}
{% if is_state(entity_id, 'off') %}
{% set last_changed = states[entity_id].last_changed | as_timestamp %}
{% set delay = states('input_number.turn_off_light_ground_floor_hall_after') | int * 60 %}
{{ (utcnow() | as_timestamp) > (last_changed + delay) }}
{% endif %}
action:
- service: light.turn_off
entity_id: light.ground_floor_hall
I had to use template trigger to react on changes in input_boolean during the for stage…
It is possible to make it shorter by making one condition out of two by combining them. Actually, it’s also possible to combine template triggers!
So here’s the combined automation (still a monster)
- alias: turn_off_light_ground_floor_hall_if_no_motion
trigger:
- platform: template
value_template: >
{% set light_id = 'light.ground_floor_hall' %}
{% set pir_id = 'binary_sensor.pir_ground_floor_hall' %}
{% set ctime = states('sensor.date_time_utc') %}
{% if ctime != 'unknown' and (is_state(light_id, 'on') or is_state(pir_id, 'off')) %}
{% set light_last_changed = states[light_id].last_changed | as_timestamp %}
{% set pir_last_changed = states[pir_id].last_changed | as_timestamp %}
{% set last_changed = light_last_changed if light_last_changed>pir_last_changed else pir_last_changed %}
{% set delay = states('input_number.turn_off_light_ground_floor_hall_after') | int * 60 %}
{{ (strptime(ctime, '%Y-%m-%d, %H:%M') | as_timestamp) > (last_changed + delay) }}
{% endif %}
condition:
- condition: template
value_template: >
{% set light_id = 'light.ground_floor_hall' %}
{% set pir_id = 'binary_sensor.pir_ground_floor_hall' %}
{% if is_state(light_id, 'on') and is_state(pir_id, 'off') %}
{% set light_last_changed = states[light_id].last_changed | as_timestamp %}
{% set pir_last_changed = states[pir_id].last_changed | as_timestamp %}
{% set last_changed = light_last_changed if light_last_changed>pir_last_changed else pir_last_changed %}
{% set delay = states('input_number.turn_off_light_ground_floor_hall_after') | int * 60 %}
{{ (utcnow() | as_timestamp) > (last_changed + delay) }}
{% endif %}
action:
- service: light.turn_off
entity_id: light.ground_floor_hall
BTW, these automations are flawed. You have to be very careful how you use entity_id's in template triggers, especially since it doesn’t have an entity_id option like template sensors do.
Basically, it has to extract the ID’s of the entities to monitor so it can know when to re-evaluate the template. The way it does that is with a regex pattern. That pattern only looks for certain things. In your case it will not find light.ground_floor_hall (in the first trigger of the first automation.) So it will not react when that entity changes (although it will evaluate every minute since it will find sensor.date_time_utc.)
To make it work (and still only specify the entity_id once), I believe you could do:
- platform: template
value_template: >
{% set entity_state_obj = states.light.ground_floor_hall %}
{% set ctime = states('sensor.date_time_utc') %}
{% if entity_state_obj.state == 'on' and ctime != 'unknown' %}
{% set last_changed = entity_state_obj.last_changed | as_timestamp %}
{% set delay = states('input_number.turn_off_light_ground_floor_hall_after') | int * 60 %}
{{ (strptime(ctime, '%Y-%m-%d, %H:%M') | as_timestamp) > (last_changed + delay) }}
{% endif %}
The point is to have an automation that switches off the light if no motion detected (as OP asked) not after a fixed period of time (10 min) but based on what user set in frontend.
then it wouldn’t extract binary_sensor.pir_ground_floor_hall as well, would it?
the thing is the automation works for me, don’t know why exactly (most likely because of sensor.date_time_utc updates).
Actually, I’ve realised that in a way a template trigger is a trigger that reacts to a change of a template sensor’s state (except that there is no entity_id option).
good point, that should work (but I have another automation for light.1st_floor_landing - it’s a problematic name and it probably won’t work).
Alternatively, it can be done as a combination of automation + template sensor I described here.
However, looking at it now and comparing it to Phil’s original version, I’m coming to a conclusion that mine one isn’t the correct substitution because of the way how state trigger with for option works.
Will need to think about it a little bit more…
The info you gave me is very good for sure. I will go through them and see how they work out and update you guys. I am still looking at Templates and learning how they work so might be a good start.
Nope, it probably wouldn’t. You can have states('light.1st_floor_landing'), because it’s a string that way, but not states.light.1st_floor_landing. You’d have to have states.light['1st_floor_landing'], but it wouldn’t find that.
In case you’re interested (and can understand regex), here’s the pattern that is used:
In general, though, it’s best not to have Object ID’s (the second half of an Entity ID) start with a number. You’d do yourself a favor changing it to `light.first_floor_landing’. It’s only two more characters.
@pnbruckner thanks for the info, I haven’t dug so deep yet
Currently I have a working version but it requires a timer, an input_datetime to store when we started the timer (as its last_changed is useless) and a short python_script as I need to call either timer.start with duration or timer.stop without any extra argument so I have no idea how to do that in an ordinary service_template call.
On the other hand, this version can survive HA restart/being down for a while and precisely reacts on off_delay changes - I think for some it’s a plus.
So here it is:
automation.yaml
- alias: start_off_timer_when_light_is_turned_on
trigger:
- platform: state
entity_id: light.1st_floor_landing
to: 'on'
- platform: state
entity_id: binary_sensor.pir_1st_floor_landing
to: 'off'
# to prevent triggering by PIR when the light is off
condition:
- condition: state
entity_id: light.1st_floor_landing
state: 'on'
action:
- service: input_datetime.set_datetime
data_template:
entity_id: input_datetime.off_timer_started_light_1st_floor_landing
datetime: "{{ utcnow().strftime('%Y-%m-%d %H:%M:%S') }}"
- service: timer.start
data_template:
entity_id: timer.turn_off_light_1st_floor_landing
duration: >
{{ states('input_number.light_1st_floor_landing_off_delay') | int * 60 }}
- alias: turn_off_light_when_off_timer_finished
trigger:
platform: event
event_type: timer.finished
event_data:
entity_id: timer.turn_off_light_1st_floor_landing
condition:
- condition: state
entity_id: light.1st_floor_landing
state: 'on'
action:
service: light.turn_off
data:
entity_id: light.1st_floor_landing
- alias: adjust_off_timer_when_off_delay_changed
trigger:
- platform: state
entity_id: input_number.light_1st_floor_landing_off_delay
- platform: homeassistant
event: start
condition:
- condition: state
entity_id: light.1st_floor_landing
state: 'on'
- condition: template
value_template: >
{{ trigger.to_state.state != trigger.from_state.state }}
action:
service: python_script.light_adjust_off_timer
data_template:
timer: timer.turn_off_light_1st_floor_landing
started: >
{{ states('input_datetime.off_timer_started_light_1st_floor_landing') | as_timestamp }}
off_delay: >
{{ states('input_number.light_1st_floor_landing_off_delay') | int * 60 }}
ctime: "{{ utcnow() | as_timestamp }}"
The most complicated part is automations and if one has more than one light to control (like myself), it’s a pain (not a real pain but 3 more automations that are more or less the same)… don’t know how to reduce it further… any ideas?
Danny this looks absolutely amazing!! I’m playing around with it now. Hopefully it can solve my problem. I’ve been trying to do something quite simple:
I have two ‘Ring’ (implementation based on MQTT) alarm motion sensors (kitchen, LR), and on the automation I was trying to say, if both (AND) of them have no motion for 10 mins, then turn off Main downstairs AC (intesishome), do this on Sun-Thur. but not matter what I tried it just didn’t work, I will be trying your amazing looking EC to hopefully help me with the issue. My original non working code looked like this:
- id: '2399999999999999'
alias: Turn off main ac when no motion
description: ''
trigger:
- entity_id: binary_sensor.living_room
for: 00:10:00
platform: state
to: 'off'
condition:
- condition: and
conditions:
- condition: state
entity_id: climate.main
state: 'on'
- condition: state
entity_id: binary_sensor.kitchen
for:
minutes: 10
state: 'off'
- condition: time
weekday:
- sun
- mon
- tue
- wed
- thu
action:
- data: {}
entity_id: climate.main
service: climate.turn_off
Create a binary sensor for this and use it as an override entity in your EC configuration. Use the motion sensors as sensor entities and set the delay to 600 seconds (10 minutes). That should do it.
I ran into this and came up with something pretty simple:
Config
Mode: restart
Triggers
Light turns on
Sensor detects motion
Condition
Light is on
Action
Delay 5 minutes
Turn light off
The key to making this work is the “restart” mode. This seems to work well, but I’m not sure if there is anything behind the scenes that may be problematic.