Race conditions for motion-activated lights

You’d think the problem is widespread and many people have tried to solve it, but that does not seem to be the case.

I have a Philips Hue motion sensor and we all know once it detects motion, it will stay in that state for 10 seconds, and then resets to “no motion”. So if you want light on for at least 40 seconds after motion, the automation looks like below. This algorithm also is used as the standard motion-activated light blueprint that comes with Home Assitant core as far as I know.

alias: Motion hallway
mode: restart
trigger:
  - type: motion
    platform: device
    device_id: c589fa75daf9b6217cb1118767de9d74
    entity_id: binary_sensor.motion_sensor_hall_motion
    domain: binary_sensor
condition: []
action:
  - service: scene.turn_on
    target:
      entity_id: scene.light_hall_bright
  - wait_for_trigger:
      - type: no_motion
        platform: device
        device_id: c589fa75daf9b6217cb1118767de9d74
        entity_id: binary_sensor.motion_sensor_hall_motion
        domain: binary_sensor
  - delay:
      hours: 0
      minutes: 0
      seconds: 30
      milliseconds: 0
  - service: light.turn_off
    target:
      entity_id: light.light_hall

The automation uses “restart” mode, which will kill an already running instance of the automation and start a new instance. This is required if the first instance hangs in the 30s delay loop, and the motion sensor is triggered again: the light stays on and the countdown starts at 10s+30s again. No rocket science so far.
So far so good - all of this works.

However, the algorithm above has the following problems:

  1. If the light was on before, human enters the motion zone, and exists, the automation turns off the light after 40 seconds. Instead, it should stay on (like it was before).
  2. Likewise, if the light was dimmed or had a nice color, and human enters the motion zone, the automation set the light to bright white for 40 seconds (good) and then turns it off (bad). Instead, it should restore the previous state. This can be addressed by creating a dynamic scene and restoring that scene at the end of the automation. However, if the automation is reset, then the 2nd instance of that automation will again save the dynamic scene (which now is bright white), and when it finishes, the light is not returned to previous state. Instead, it stays in bright white forever. Light never is turned off.
  3. When working with dynamic scenes (to address problem 2.), then there’s yet another problem: If another automation turns the light off (e.g. because TV was turned on) while the motion-automation is in its waiting loop, then it will turn the light back on. I admit solving this problem is a bit too much to ask for, and it rarely happens.

I’ve tried working with now() to find out how long ago the automation was last triggerd (or scene saved/applied) and I tried to solve this with a variable to carry information from one instance to the next. I could not wrap my head around it and got nothing, because the 2nd instance starts just with the same information as the first one. The first instance never has a chance to finish because it will be killed in reset mode.
I tried the single mode - this solves problems 1 and 2, but then the light is turned off after 40 seconds even if there’s motion again, so I stand in the dark.
I thought of two automations to address different problems, but would not know how.
I ran out of ideas.

Instead of putting it all in one automation, I’d use a timer. That way you can check if the timer is aleady running or not when the light is on (see the difference between light is already on or motion is continuing). If the timer is already there, just restart the time, if it runs out, restore the scene.

Also, you can cancel the timer if the light is turned on for another reason.

Never did that so I’m throwing ideas that are most probably bad … sorry.

What if you trigger on motion AND no motion in single mode.
If there is a re-entry it will fail and the 30 seconds will continue.
But at the end, you can condition the turn_off based on the motion status and only do it if it is clear.
If not, it’ll keep the light on because motion was still detected and end the automation.

One time or another, the motion will be clear and it will restart this automation but this time because of the “no motion” trigger and you can therefore turn off the light or something similar.

To avoid complex things, I’d create various helpers to keep the information about your light (boolean to store the previous on/off, numbers for brightness or color) and initialize all of them as the first actions of the motion detected part. It’ll help going back to previous state as it will act like global variables.

EDIT: “Conditional turn off” or “Clear motion trigger” could be a call to one common script with all your helpers as the needed information to restore the light as it was before.

Interesting idea, but how do I create a global timer that exists outside of an automation?

Let the triggers do the work.

alias: Motion hallway
trigger:
  - platform: state
    target:
      entity_id: binary_sensor.motion_sensor_hall_motion
    from: 'off'
    to: 'on'
    variables:
      what: scene.turn_on
      who: scene.light_hall_bright
  - platform: state
    target:
      entity_id: binary_sensor.motion_sensor_hall_motion
    from: 'on'
    to: 'off'
    for: 
      seconds: 30
    variables:
      what: light.turn_off
      who: light.light_hall
condition: []
action:
  - service: "{{ what }}"
    target:
      entity_id: "{{ who }}"
mode: single
1 Like

This was a perfect example of good teamwork. None of you solved the problem, but all of you together solved it. The biggest piece was delivered by @123 with the idea of using the delay option for the off-trigger. @Olivier1974 came up with the boolean helper idea and @Edwin_D mentioned using more than one automation.
I combined all ideas and added some of my own to come up with the below solution, which solved all three (1.-3.) problems descibed above. The key was to use two automations (one to make the light bright white, one to restore light previous state) and use a boolean helper variable to remember if we are between the two automations.

  1. Add a boolean helper variable
    input_boolean.hall_motion

  2. First automation - triggered when motion detected

alias: Motion hallway detected
mode: single
trigger:
  - platform: state
    entity_id:
      - binary_sensor.motion_sensor_hall_motion
    from: "off"
    to: "on"
condition:
  - condition: state
    entity_id: input_boolean.hall_motion
    state: "off"
action:
  - service: input_boolean.turn_on
    target:
      entity_id: input_boolean.hall_motion
  - service: scene.create
    data:
      scene_id: hall_previous
      snapshot_entities: light.hallway
  - service: scene.turn_on
    target:
      entity_id: scene.light_hall_bright
  1. Second automation - triggered when no motion detected for 30 seconds
alias: Motion hallway not detected
mode: single
trigger:
  - platform: state
    entity_id:
      - binary_sensor.motion_sensor_hall_motion
    from: "on"
    to: "off"
    for:
      seconds: 30
condition: []
action:
  - if:
      - condition: template
        value_template: >-
          {{ as_timestamp(now()) -
          as_timestamp(states.light.hallway.last_changed) > 38}}
    then:
      - service: scene.turn_on
        target:
          entity_id: scene.hall_previous
  - service: input_boolean.turn_off
    target:
      entity_id: input_boolean.hall_motion

The if-then logic even solves problem (3.) and accounts for the light being turned off by another automation. It uses the last_changed timestamp, which only is updated when the light is turned off or on, but not when color or brightness would change. I experimented with the last_updated timestamp first, but that one is being updated too often, e.g. when I did not change the light’s brightness or color, HA also updates last_updated. I’m good with last_changed because the other automation turns the light off when the TV is turned on.
If a user would change the light’s color while between the two automations, that user change is overwritten by the second automation after 30 seconds latest. This is a drawback, but I found no variable to check for light state changes besides last_updated (updated too frequently) and last_changed (not updated when color/brightness changes).

Thanks to all.

1 Like
  1. The example I posted wasn’t meant to solve all the requirements you posted. It simply demonstrates that triggers can be used to perform most of the workload in a single automation. It was the foundation for adding the other requirements.

  2. The example also demonstrated that listening for the ‘no motion’ event can be part of the automation’s trigger as opposed to your original example where it’s part of the action.

  3. The State Trigger’s for option is not a “delay option for the off-trigger”. It’s effectively an internal timer that requires the state value to remain unchanged, at a specified value, for a specified duration. The timer is reset if the value changes. It’s not a simple “delay”.

  4. All of the requirements can be fulfilled by a single automation. Two automations is a matter of choice, not necessity.

  5. The input_boolean serves as a ‘flag’, to indicate status, but that role can be fulfilled by the snapshot scene. In other words, the input_boolean isn’t explicitly needed because the scene can serve as the ‘flag’ (specifically, the scene’s state value which is a timestamp).

Anyway, glad to hear you found a solution by combining several ideas.

1 Like

From settings, in the same section where the intrgrations live, the rightmost tab called helpers. You can add various type of entities here, timer is among them.

1 Like