Avoiding race conditions between conflicting automations

Hi!

I am new here and am struggling with something that probably has a really simple solution. I have an underfloor heating system with multiple zones controlled by valve actuators. I am trying to accomplish the following:

  • when ANY of valve switches is “on”, wait for two minutes, then turn on the water pump
  • when ALL the valve switches are “off”, turn off the water pump immediately

I created a group called switch.ufh_actuator_valves to simplify the management. The naive solution is to create two automations:

alias: "UFH: Turn off pump"
trigger:
  - platform: state
    entity_id:
      - switch.ufh_actuator_valves
    to: "on"
condition:
  - condition: state
    entity_id: switch.ufh_actuator_valves
    state: "on"
action:
  - delay:
      minutes: 2
  - service: switch.turn_on
    target:
      entity_id: switch.ufh_water_pump
mode: single
alias: "UFH: Turn off pump"
description: When last valve actuatator is closing
trigger:
  - platform: state
    entity_id:
      - switch.ufh_actuator_valves
    to: null
condition:
  - condition: not
    conditions:
      - condition: state
        entity_id: switch.ufh_actuator_valves
        state: "on"
action:
  - service: switch.turn_off
    target:
      entity_id: switch.ufh_water_pump
mode: queued

There is an obvious race condition here. If last valve is turned off while the first automation is waiting for 2 minutes the water pump may end up running although all valves are closed (which would be bad). What’s an elegant solution here?

Many thanks in advance!

one way is to use a timer instead of the delay. then the turn off can cancel the timer.

in all cases, you should protect yourself by thinking through what you would want if there is a reboot of home assistant during these conditions… and have the home assistant start even clean up as needed so that you don’t leave water running forever.

Hmm, thanks! Would something like this work?

- alias: "UFH: Start the water pump delay timer"
  trigger:
    - platform: state
      entity_id:
        - switch.ufh_actuator_valves
      to: "on"
  condition:
    - condition: not
      conditions:
        - condition: state
          entity_id: timer.ufh_water_pump_delay
          state: active
  action:
    - service: timer.start
      target:
        entity_id: timer.ufh_water_pump_delay
  mode: single
- alias: "UFH: Turn off water pump"
  trigger:
    - platform: state
      entity_id:
        - switch.ufh_actuator_valves
      to:
  condition:
    - condition: not
      conditions:
        - condition: state
          entity_id: switch.ufh_actuator_valves
          state: "on"
  action:
    - service: timer.cancel
      target:
        entity_id: timer.ufh_water_pump_delay
    - service: switch.turn_off
      target:
        entity_id: switch.ufh_water_pump_switch
  mode: queued
  max: 50
- alias: "UFH: Turn on water pump"
  trigger:
    - platform: event
      event_type: timer.finished
      event_data:
        entity_id: timer.ufh_water_pump_delay
  condition:
    - condition: state
      entity_id: switch.ufh_actuator_valves
      state: "on"
    - condition: state
      entity_id: switch.ufh_water_pump_switch
      state: "off"
  action:
    - service: switch.turn_on
      target:
        entity_id: switch.ufh_water_pump_switch
  mode: single

eyeballing it, it looks pretty reasonable. but the proof is in the pudding, give it a test :slight_smile:

some comments below.

- alias: "UFH: Start the water pump delay timer"
  trigger:
    - platform: state
      entity_id:
        - switch.ufh_actuator_valves
      to: "on"
  condition:
    - condition: not
      conditions:
        - condition: state
          entity_id: timer.ufh_water_pump_delay
          state: active
  action:
    - service: timer.start
      target:
        entity_id: timer.ufh_water_pump_delay
  mode: single

you don’t necessarily need the condition block. the question is what you want it to do. if the timer was already if/when the actuator turns on again (is this possible?) do you want it to complete the 2 minutes and turn on (so it could be conceptually 1 second later)? or do you want to restart the 2 minutes? if so, remove the condition block and let it call start again (which will restart the timer). if you want it to complete the existing timer, then keep as you have.

- alias: "UFH: Turn off water pump"
  trigger:
    - platform: state
      entity_id:
        - switch.ufh_actuator_valves
      to:
  condition:
    - condition: not
      conditions:
        - condition: state
          entity_id: switch.ufh_actuator_valves
          state: "on"
  action:
    - service: timer.cancel
      target:
        entity_id: timer.ufh_water_pump_delay
    - service: switch.turn_off
      target:
        entity_id: switch.ufh_water_pump_switch
  mode: queued
  max: 50

why do you have the above as queued?

why do you have the condition block as “not on” instead of having the trigger block be “off” and nuke the condition block? yours should work, but i’m curious. more complicated.

- alias: "UFH: Turn off water pump"
  trigger:
    - platform: state
      entity_id:
        - switch.ufh_actuator_valves
      to: "off"
  action:
    - service: timer.cancel
      target:
        entity_id: timer.ufh_water_pump_delay
    - service: switch.turn_off
      target:
        entity_id: switch.ufh_water_pump_switch

A simpler way to replace both automations.

alias: "UFH: control pump"
trigger:
  - id: 'on'
    platform: state
    entity_id:
      - switch.ufh_actuator_valves
    from: "off"
    to: "on"
    for: 
      minutes: 2
  - id: 'off'
    platform: state
    entity_id:
      - switch.ufh_actuator_valves
    from: "on"
    to: "off"
action:
  - service: "switch.turn_{{ trigger.id }}"
    target:
      entity_id: switch.ufh_water_pump
mode: single
2 Likes

do you want to restart the 2 minutes

Interesting. Actually, yes! Please note that switch.ufh_actuator_valves is actually a group.

why do you have the condition block as “not on” instead of having the trigger block be “off”

Running the water pump with all valves closed is going to damage the pump fairly quickly, that’s why I am trying to avoid this scenario at all costs. The above seemed the safest option to me:

  1. trigger on every state change
  2. ensure that at least one valve is definitely open
  3. turn off pump if all the valves are closed or unavailable or unknown

Makese sense?

This is very helpful, thank you. As expressed above, my only concern are the unavailable and unknown states. Running the water pump with all valves closed will eventually damage the pump. I want to make absolutely sure that the pump only runs when I can be certain that at least one of the valves is open.

OK, so omitting the to: "off" property on the off trigger would solve that. The only remaining question is mode: single then. How would this behave in combination with the for: { minutes: 2 }? Isn’t mode: restart more appropriate in this case?

Why do you think it would be?

If the valve switch group switches to “on” and then to “off” - let’s say - within a minute, shouldn’t the automation restart to make sure the pump is turned off? Sorry, as said, I am new to HA and am still trying to go through the documentation.

Single is fine. Triggers waiting for a state to occur for any length of time don’t actually “wait in the automation”.

The way each automation trigger works is to set up a “listener” process in the event loop listening for the required parameters specified in the trigger.

Your automation has two listeners:

  1. state of switch must be on continuously for 2 minutes.
  2. state of switch changes to “off”

The automation does not actually run until either of these occur.

Lets go through it step by step:

This will activate the trigger listener monitoring for it being in the on state for 2 minutes. Note this has not yet triggered the automation. The trigger will not occur unless the switch says in this state for two minutes. So the automation is still inactive at this point.

This will reset the “on” trigger listener and immediately trigger the automation due to the “off” state trigger listener. Now the automation will check any specified conditions and if they all pass run the actions.

1 Like