Sync entity states without causing infinite loop

I have an input_select that changes the number value of another entity. It’s set up so that when I change the input select, an automation trigger fires that then sets the number value.

Now I would like the input_select to also reflect the current state of the number entity… BUT, if I set up another automation to update the input_select whenever the number entity changes, then I run into this problem of an infinite loop where the two automations just keep triggering each other back and forth…

The purpose of the input_select is to give me a text representation of the number for the frontend, and to make an easy way for me and family to adjust the number value with a limited option set.

Is there a better way to do this? Or how can I handle avoiding the infinite loop problem when trying to keep both values in sync?

When an automation is triggered by a state change, the trigger comes with all sorts of metadata, including the old state (from_state), the new state (to_state), as well as details about who made the change.

One approach would be to put a condition in your automation that it only syncs the other entity of the change was made by a user, and not based on an automation.

You can do some experiments and look at the trace data for the automation to see exactly what data inside the trigger you could use to set up such a condition.

post the code you’ve got. this seems quite solveable but easiest to do that by modifying the code you’ve already written

In this particular case, I’ve only written one half of the equation, the automation that syncs up the number to the input_select. I haven’t done the other half because it’s essentially just the inverse of this same automation (changing the input_select state based on the number changing), and I didn’t want to turn it on because it will create the infinite loop problem I described. I’ve had this problem in the past with other similar automations I tried.

This is my current automation to change the number based on changing the input_select value:

alias: Change Office Air Purifier Gear Mode Setting With Input Select
description: ""
trigger:
  - platform: state
    entity_id:
      - input_select.office_air_purifier_mode_select
condition: []
action:
  - choose:
      - conditions:
          - condition: state
            entity_id: input_select.office_air_purifier_mode_select
            state: "Off"
        sequence:
          - action: switch.turn_off
            metadata: {}
            data: {}
            target:
              entity_id: switch.office_air_purifier_power_switch
      - conditions:
          - condition: state
            entity_id: input_select.office_air_purifier_mode_select
            state: Sleeping
        sequence:
          - action: number.set_value
            target:
              entity_id: number.office_air_purifier_gearmode
            data:
              value: "1"
      - conditions:
          - condition: state
            entity_id: input_select.office_air_purifier_mode_select
            state: Low
        sequence:
          - action: number.set_value
            target:
              entity_id: number.office_air_purifier_gearmode
            data:
              value: "2"
      - conditions:
          - condition: state
            entity_id: input_select.office_air_purifier_mode_select
            state: High
        sequence:
          - action: number.set_value
            target:
              entity_id: number.office_air_purifier_gearmode
            data:
              value: "3"
mode: restart

ok, an infinite loop in this case shouldn’t happen because if you set an entity to its current value it won’t trigger the automation. Ie, on to on will not trigger an automation set to fire when state becomes on.

however if you want to be extra paranoid about it, you and set the condition of one of the automations to check the target’s state.

Ie if off then target is already off, if sleeping then target is 1 etc.

If you take this approach you might look into using jinja dictionaries which will simplify your code… Ie you could map sleeping low and high to 1,2,3 and do those in one call

I’m assuming a level of jinja/yaml expertise given some of your questions… If I’m over assuming, holler at me…

You’re totally right! When a state changes on one of the two entities that triggers one of the two automations, it should simply set the second entity to the matching state… at that point it should stop firing either automation since both entity states would be in sync and would no longer be changing…

Huh… now I am wondering what I did with my lighting automation in the past that actually DID cause the infinite loop I described… maybe I’ll have to revisit that one soon. It shouldn’t have had this problem either, but my lights kept flickering back and forth between on and off states… so I must’ve done something dumb that I missed at the time.

Thanks for the second set of eyes on this!

As for your thoughts on using jinja dictionaries, I like to think I’m decent with YAML but have never been great with the jinja templates… any resources you can recommend to better understand what you’ve described? If you have a simple example of mapping like you described above, and it’s not too much effort, that would be awesome!

here’s an example of using a dict to simplify your yaml.

alias: Change Office Air Purifier Gear Mode Setting With Input Select
description: ""
trigger:
  - platform: state
    entity_id:
      - input_select.office_air_purifier_mode_select
condition: []
action:
  - choose:
      - conditions:
          - condition: state
            entity_id: input_select.office_air_purifier_mode_select
            state: "Off"
        sequence:
          - action: switch.turn_off
            metadata: {}
            data: {}
            target:
              entity_id: switch.office_air_purifier_power_switch
      - conditions:
          - condition: state
            entity_id: input_select.office_air_purifier_mode_select
            state: 
              - 'Sleeping'
              - 'Low'
              - 'High'
        sequence:
          - action: number.set_value
            target:
              entity_id: number.office_air_purifier_gearmode
            data:
              value: >
                {% set dict = { 'Sleeping': 1, 
                                'Low' : 2,
                                'High': 3 } 
                %}
                {{ dict['input_select.office_air_purifier_mode_select'] }}
mode: restart

wrt the infinite loop, there could be one where if you imagine you have switch1 and switch2 that you’re working to keep in sync…

if you have switch1 turn on and off quickly before the automation for switch2 can run…

so switch1 goes on then off. switch2 is off, but the automation gets queued to turn switch2 on, but switch1 is already off… that automation then turns switch1 on… which creates the cycle.

a couple things you could do to solve this. 1) put the two automations into 1 using multiple triggers and set the mode to single (not queued). and 2) when the script is triggerred, sit the target switch to the current source switch’s state… ie, even if the automation was triggered by switch1 becoming ‘on’, set switch 2 to the current state of switch1.

All too complicated!

Remove the trigger on the state on one of the helper values.
In every place that set that helper value change it to script.
In the script change both the earlier target helper value and the derived helper value.

Now there trigger is not on the state change anymore, but on the actual input.

Just make a template select, this is what they were made for.

template:
- select:
  - name: Air Purifier Mode
    state: >
      {% set is_on = is_state('switch.office_air_purifier_power_switch', 'on') %}
      {% set value = states('number.office_air_purifier_gearmode') | int %}
      {% set values = {1: 'Sleeping', 2: 'Low', 3: 'High'} %}
      {% if is_on %}
        {{ values.get(value, 'Off') }}
      {% else %}
        Off
      {% endif %}
    options:
    - Off
    - Sleeping
    - Low
    - High
    select_option:
    - variables:
        options:
          Sleeping: 1
          Low: 2
          High: 3
    - choose:
      - conditions:
          - condition: template
            value_template: "{{ option == 'Off' }}"
        sequence:
          - action: switch.turn_off
            metadata: {}
            data: {}
            target:
              entity_id: switch.office_air_purifier_power_switch
      - conditions:
          - condition: template
            value_template: "{{ option | int in options }}"
        sequence:
          - action: number.set_value
            target:
              entity_id: number.office_air_purifier_gearmode
            data:
              value: "{{ options.get(option) }}"