Turn off light no motion

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.

1 Like

Exactly.

But that would do the same thing.

The question is, 10 minutes after what?

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
1 Like

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:

for: "{{ input_number.turn_off_light_ground_floor_hall_after }}"

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

Sorry, Iā€™m not sure what the point is.

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 %}
1 Like

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ā€¦

1 Like

@pnbruckner and @AhmadK

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.

Sean

1 Like

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. :smiley:

@pnbruckner thanks for the info, I havenā€™t dug so deep yet :wink:

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 }}"
timer.yaml

turn_off_light_1st_floor_landing:
input_datetime.yaml

off_timer_started_light_1st_floor_landing:
  has_date: true
  has_time: true
light_adjust_off_timer.py

timer = data.get('timer')
started = float(data.get('started'))
off_delay = int(data.get('off_delay'))
ctime = float(data.get('ctime'))

args = {'entity_id': timer}
remaining = int(started+off_delay - ctime)

if remaining > 0:
    cmd = 'start'
    args['duration'] = remaining
else:
    cmd = 'finish'

hass.services.call('timer', cmd, args)

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?

@pnbruckner thanks for pointing me in the right direction. I got it working and it makes sense and see what my error was.

Sean

Also check out Entity Controller. I built that component for very similar use cases with customisation options.

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. :slight_smile:

any idea what the yaml would look like? as mine is definitely not workingā€¦

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.

2 Likes

Hey.

I have the following scenario:

  1. A light switch that turns on the light in the toilet.
  2. A motion sensor in the toilet that should turn off the light after 5 minutes if no motion is detected
  3. If there is motion the light should stay on obviously
  4. If the light turns off while someone is inside the light should turn back on again
  5. Opening the door should NOT turn on the light

Iā€™m wondering if there is a simple way to solve this.

Thanks.

Christoph

thanks so much for the simplification, this is how my functional code worked out for me

- alias: TIMER - Laundry Roomy
  initial_state: true
  mode: restart
  trigger:
  - platform: state
    entity_id: switch.laundry_room
    to: 'on'
  - platform: state
    entity_id: binary_sensor.laundry_4_in_1_sensor_home_security_motion_detection
    to: 'on'
  condition:
    - condition: state
      entity_id: switch.laundry_room
      state: 'on'
  action:
  - delay: 00:05:00
  - service: switch.turn_off
    entity_id: switch.laundry_room
1 Like