TRV with modes, endless loop, self-triggering, cyclic automations again

Hi all, it’s the same topic again…

I went through number of topics about automation loops, automation triggering themselves, endless loops, etc., but each one of those seemed to have a bit different issue, some different approach to solve the issue, or - the problem stated was solvable in a simpler way.

I am stuck with my idea. I have a TRV automation in mind, for which I need multiple modes and extra behavior. I tried to simplified my exact case to show the issues I struggle with, I hope I didn’t cut too much.

That’s the idea:

  • in a room, there is one TRV device
  • heating in a room has 2 modes: comfort and eco
  • solution has an UI controls:
    • mode selector, selecting mode sets an appropriate temperature
    • temperature for each mode
  • I have a “normal” users in house, so I want a TRV knob to function as well
    • whenever a knob is turned, a comfort mode should be set as active and the set point of temperature from the TRV should be set as comfort mode’s temperature: this guarantees a master, manual override

This type of scenario, of “synced” states, without a possibility to differentiate the type of event (physical knob was turned vs it’s my automation that sets the TRV’s set point) leads into cyclic automation triggering. I can’t work it out.

  • imagine being in a “comfort” mode
  • user in UI sets mode “eco”
  • automation sets the TRV’s set point to eco temperature
  • but, a call of this service triggers the TRV state change, which we watch and understand as a physically turned knob…
  • … which changes mode back to comfort
  • this triggers the TRV’s temperature setting
  • … and finally, thanks to couple of ifs, the oscillations stops…

Here is the current state of automation:

input_number:
  dev_trv_comfort_temp:
    name: "Comfort Temperature"
    min: 5
    max: 35
    step: .5
    initial: 22
    unit_of_measurement: °C
    icon: mdi:thermometer
    mode: box
  dev_trv_eco_temp:
    name: "Eco Temperature"
    min: 5
    max: 35
    step: .5
    initial: 15
    unit_of_measurement: °C
    icon: mdi:fire
    mode: box

input_select:
  dev_trv_mode:
    icon: mdi:car-cruise-control
    initial: "comfort"
    options:
      - "comfort"
      - "eco"

automation:
  - id: dev_trv_mode_changed_to_comfort
    alias: "DEV: TRV: mode_changed_to_comfort"
    mode: queued
    trigger:
      - platform: state
        entity_id: input_select.dev_trv_mode
        to: "comfort"
        variables:
          set_temp: "{{states('input_number.dev_trv_comfort_temp')}}"
    action:
      - service: climate.set_temperature
        entity_id: climate.dev_trv_thermostat
        data:
          temperature: "{{set_temp}}"
          hvac_mode: heat

  - id: dev_trv_mode_changed_to_eco
    alias: "DEV: TRV: mode_changed_to_eco"
    mode: queued
    trigger:
      - platform: state
        entity_id: input_select.dev_trv_mode
        to: "eco"
        variables:
          set_temp: "{{states('input_number.dev_trv_eco_temp')}}"
    action:
      - service: climate.set_temperature
        entity_id: climate.dev_trv_thermostat
        data:
          temperature: "{{set_temp}}"
          hvac_mode: heat

  - id: dev_trv_device_temp_changed
    alias: "DEV: TRV: device_temp_changed"
    mode: queued
    trigger:
      - platform: state
        entity_id: climate.dev_trv_thermostat
        attribute: temperature
        id: trv_target_temp_changed
        variables:
          set_temp: "{{trigger.to_state.attributes.temperature}}"
    action:
      - if:
          - condition: template
            value_template: >
              {{states("input_select.dev_trv_mode") != "comfort"}}
        then:
          - service: input_select.select_option
            entity_id: input_select.dev_trv_mode
            data:
              option: "comfort"
      - if:
          - condition: template
            value_template: >
              {{states("input_number.dev_trv_comfort_temp") != set_temp}}
        then:
          - service: input_number.set_value
            entity_id: input_number.dev_trv_comfort_temp
            data:
              value: "{{set_temp}}"

  - id: dev_trv_ui_mode_temp_changed
    alias: "DEV: TRV: ui_mode_temp_changed"
    mode: queued
    trigger:
      - platform: state
        entity_id: input_number.dev_trv_comfort_temp
        variables:
          set_temp: "{{trigger.to_state.state}}"
          mode_updated: comfort
      - platform: state
        entity_id: input_number.dev_trv_eco_temp
        variables:
          set_temp: "{{trigger.to_state.state}}"
          mode_updated: eco

    condition:
      - and:
          - condition: template
            value_template: >
              {{states('input_select.dev_trv_mode') == mode_updated}}
          - condition: template
            value_template: >
              {{state_attr("climate.bdrm_trv", "temperature") != set_temp}}

    action:
      - service: climate.set_temperature
        entity_id: climate.dev_trv_thermostat
        data:
          temperature: "{{set_temp}}"
          hvac_mode: heat

The 2 first automations could be one, those are split for another reasons.

So, how to make it working?

How to keep in sync these entities together without triggering itself?

How can I differentiate the cause/context of what caused a different trigger. How to determine when I change the input_select.dev_trv_mode internally due to state integrity of my automation, and it’s not a change caused by a real user? I did read a bit about context, but got into a conclusion it’s not reliable.

In my mind this type of goal should be achievable by HA user without a custom integration and I bet I just deadlock in a thought process. I did currently worked around the issues with a dirty fix, which I don’t like, as it breaks a “queue” mode. I would like to not share it, to not spoil the answers.

Help appreciated!