How to handle race condition of template fan set and get percentage values

Hi friends:
I’m trying to create a template fan for HomeKit based on an existing fan, the purpose is to expose oscillation as direction control (more user friendly in Homekit). The below is the template I created:

- platform: template
  fans:
    livingroom_homekit:
      friendly_name: "Living Room Fan Homekit"
      unique_id: livingroom_homekit

      # 1. On/off detection
      value_template: "{{ is_state('fan.zhimi_de_317588644_za4_s_2_fan', 'on') }}"

      # 2. Turn on/off actions
      turn_on:
        - action: fan.turn_on
          target:
            entity_id: fan.zhimi_de_317588644_za4_s_2_fan
      turn_off:
        - action: fan.turn_off
          target:
            entity_id: fan.zhimi_de_317588644_za4_s_2_fan

      # 3. Speed control
      percentage_template: "{{ state_attr('fan.zhimi_de_317588644_za4_s_2_fan', 'percentage') }}"
      set_percentage:
        - action: fan.set_percentage
          target:
            entity_id: fan.zhimi_de_317588644_za4_s_2_fan
          data:
            percentage: "{{ percentage }}"

      # 4. Preset modes support
      preset_modes:
        - "Direct Breeze"
        - "Natural Wind"
      preset_mode_template: "{{ state_attr('fan.zhimi_de_317588644_za4_s_2_fan', 'preset_mode') }}"
      set_preset_mode:
        - action: fan.set_preset_mode
          target:
            entity_id: fan.zhimi_de_317588644_za4_s_2_fan
          data:
            preset_mode: "{{ preset_mode }}"

      # 5. Oscillation (native support)
      oscillating_template: "{{ state_attr('fan.zhimi_de_317588644_za4_s_2_fan', 'oscillating') }}"
      set_oscillating:
        - action: fan.oscillate
          target:
            entity_id: fan.zhimi_de_317588644_za4_s_2_fan
          data:
            oscillating: "{{ oscillating }}"

      # 6. Use "direction" control in HomeKit to also toggle oscillation
      direction_template: >
        {% if state_attr('fan.zhimi_de_317588644_za4_s_2_fan', 'oscillating') %}
          forward
        {% else %}
          reverse
        {% endif %}
      set_direction:
        - action: fan.oscillate
          target:
            entity_id: fan.zhimi_de_317588644_za4_s_2_fan
          data:
            oscillating: "{{ direction == 'forward' }}"

So the problem I have is with speed control which accepts percentage from user. In Homekit when I adjust speed, it sends multiple values, and I have to deal with the race condition (an example) below:

  1. HomeKit send command 20
  2. Template fan instruct the real fan to change speed to 20
  3. HomeKit send command 50
  4. The real fan now has changed speed to 20
  5. The percentage_template returns 20, and the value 50 is ignored and no further command sent to the real fan.

This works fine if I expose the fan directly to HomeKit, however with a template in-between it creates the race condition as the example above. I’d like to ask how to handle such race condition? Many thanks in advance!

You’re not hitting a race condition, the set_position script is blocking subsequent runs of itself because it defaults to mode single.

Make a separate script with mode: parallel that runs the fan set position and call that in the set_position action.

1 Like

Thanks that’s indeed the problem - however with parallel run the result is still inconsistent. I don’t think of a good solution unless there is anything from HomeKit end to reduce the commands by waiting for a short while before sending the commands.

use queue’d then.

1 Like

Indeed queued works better.
I’m now trying another solution: I created an input_number and set the value there, then I have an automation triggered by change, and wait for 0.5 seconds before taking the latest value and send command. Will report back how it works

Edit: The input_number + automation with delay works best for this case as it also reduces the wireless command sent to the fan. Thanks for your input!