Total Connect Comfort "hold" modes

I’ve just starting to use the Total Connect Comfort integration, and having a strange issue with the “hold” modes. My theromostat is a Honeywell RTH9580WF1005, and works fine using the TCC app.

This termostat has three modes: normal (follow schedule), temporary hold (until next scheduled change), and permanent hold. HA shows I have three preset_modes: “none”, “away”, and “hold”.

Setting “none” causes the thermostat to follow the schedule, as I anticipated.

Setting “away” and “hold” are both behaving unexpectedly. If I move from “none” to “away”, every other time it sets the thermostat to “temporary” or “permanent” hold. For example:

  1. “none” → “away” yields temporary hold
  2. “away” → “none” yields following schedule
  3. “none” → “away” yields permanent mode
  4. “away” → “none” yields following schedule
  5. “none” → “away” yields temporary hold

The same thing happens with “hold” as “away”. I had expected one of these two modes to be the temporary hold and the other the permanent hold. The documentation says “away” is the permanent hold, and doesn’t mention the “hold” mode.

I’ve printed the HA device state, and the “permanent hold” attribute is reflecting the same as what I’m seeing. ie: half the time set to true and the other half false.

It takes a while for the settings to take effect, so I’m waiting for both the thermostat and my HA dashboard to show each new state before trying the next step. ie: Assuming looking at the thermostat control and the HA console is a good representation of steady state, I’m not causing a race condition.

For completeness, here is my code snippet (device_id truncated):

     sequence:
         - device_id: f4...
            domain: climate
            entity_id: climate.house
            type: set_preset_mode
            preset_mode: away
          - action: climate.set_temperature
            metadata: {}
            data:
              target_temp_high: 84
              target_temp_low: 55
            target:
              device_id: f4...

Am I doing something wrong? Are successfully setting each of the 3 modes?

Some additional info: The above yaml was from building a script using the HA UI, with the only tweak being me changing the entity_id from number to name.

Reading the integration documentation, they propose a differently formed yaml:

      sequence:
          - action: climate.set_preset_mode
            target:
              entity_id: climate.home
            data:
              preset_mode: "away"
          - action: climate.set_temperature
            target:
              entity_id: climate.home
            data:
              target_temp_high: 84
              target_temp_low: 55
              hvac_mode: heat_cool

Using this, “away” consistently gives me a permanent hold, but sometimes (about one out of 3 attempts) either the target_temp_low or target_temp_high fails to be set. Specifying “hold” instead of “away” makes no difference – I get a permanent hold where one of the target temps sometimes fails to be set.

I’m a newbie, so the difference in meaning between these two configs isn’t clear to me.

Sorry, I was hoping to help out, but after looking at the code itself, I can’t figure it out.
The comments in the code say that both hold mode and away mode are for permanent hold, but also says that “away” mode is a Somecomfort proprietary mode that doesn’t work the way one thinks it should.
If I were to make a recommendation, it would be to use the “hold” mode (and “none” mode) and stay away from the “away” mode. And also, The Honeywell TC Integration doc for climate only has one example but it doesn’t mention setting temp high/low, just plain old “temperature”.

I did some additional experiments, learned a few things, but haven’t gotten to a solution that consistently works…

First, in the “Configure” form of the Honeywell Total Connect Comfort, there are two values: “Away cool temperature” and “Away heat temperature”. When I set the thermostat to “away” mode, it automatically tries to set “target_temp_high” and “target_temp_low” to these. This too is unreliable. Sometimes both get set, sometimes one, sometimes the other, and sometimes none. And from my testing, it appears this additional “set” step is only done with “away”, and not done with “hold”. Looking at the integration source code, I confirmed these values are used in “away”, but not “hold”.

I was suspecting there might be a race condition between setting the mode and setting the target temperatures, so I added a 45 second delay between the two actions (my understanding is the integration updates it’s state every 30 seconds). I tried both “away” and “home”, and it’s still unreliable, working ~2/3 of the time, and one or both values not being set the other ~1/3 of the time.

One thing that led me to think about a race condition is the timing on when my actions actually took effect on the thermostat itself. Here’s the timeline for a “hold” (times are close but not exact):

  • Run script
  • Thermostat shows mode change 12 seconds later. (Delay varied between 2 and 14 sec)
  • Thermostat shows target_temp_low as changed 60 seconds later. (Delay varied between 4 and 60 sec)
  • Thermostat shows target_temp_high as changed 20 seconds later. (Delay was consistently 20 sec)

Which target temp changes first appears random. And as I said, sometimes neither gets set and sometimes one but not the other. Note: I waited 5 min before I considered it failed.

I see the following quote on the integration page:

Due to the instability of the Honeywell total connect system, actions within automations should repeat until success similar to the following example…

Perhaps this is the root cause of my issues and I need to add retries? Given I fail 1/3 of the time, I’d like to assume that the retries are for a more subtle instability issue, but ? Anyone have any experience with this?

I’m back from a short vacation, and working on this again. I’ve simplified (for now) to a script that just sets the preset_mode, with a retry loop (with a configurable delay between retries and max retries). I’ve discovered a few interesting things that may be helpful to others:

I’ve been using my script to set preset_mode to ‘hold’, and looking at both the physical thermostat UI as well as the HA state to determine if the mode has been set. Once the mode is set and confirmed through both interfaces, I’ve been manually cancelling the hold mode via the physical thermostat’s UI and then waiting until HA sees the same ‘none’ state before continuing.

  • If I re-run the script to put the thermostat back into ‘hold’ mode shortly after the above checks for “steady state”, it silently fails (does nothing).
  • But if I wait a full 10 minutes since I manually cancelled the hold via the physical thermostat’s UI, it works! I haven’t figured out the exact duration I need to wait, but I have confirmed that 5 minutes is not enough, while 10 minutes is enough.
  • Even if my script is set to do a retry once a minute and I let it run for 15 minutes, it fails. I need to wait 10+ minutes before issuing a command. Attempting commands within that 10 minute window seems to extend that 10 minute window.
  • If I modify my script to have a 10 minute delay before retrying, it works on the second retry. ie: the first attempt which happens within a minute or two fails, but the second one that happens 10 minutes after that works.
  • If I use my script to cancel the hold (set preset_mode to ‘none’) instead of cancelling the hold via the thermostat’s UI, this 10 minute “dead” window does not occur. ie: I can run the script to move back to ‘hold’ as soon as I’ve confirmed everything is in “steady state” as described above.

I hit a few instances where the ‘10 minutes of no response’ happened even when just using the integration, but not sure of what triggers it. I’ve factored that into my solution below.

After significant experimentation, I’ve built a ‘wrapper script’ for controlling the thermostat, included below. I’m pretty new to HA, so assume there are better ways to implement much of this. I’d love to get feedback! In particular, this seems way to complex. ?? I’m also wondering if much of this should actually be pushed down into the integration itself?

Notes:

  • This works on thermostats set to both heat, and to heat/cool.
  • I rely on a gmail notifier for when things fail. Substitute whatever you’d like.
  • The variable ‘waits’ is an array of how long to wait for the action to happen before it’s considered failed… and then retried. I start out with a couple short retries (network error or other random failure), and then do a 10min (600 sec) delay due to the issue I noted above, followed by a couple additional short retries if needed. This can certainly be adjusted if your observations are different than mine,.
  • I use a non-standard but I think more intuitive set of ‘modes’ (to reduce confusion, I call them ‘settings’). These are documented below, along with a description of each argument.
alias: "Thermostat: Control"
description: |-
  Arguments:
    - 'house' must be the name of the thermostat. ie entity climate.house
    - 'setting' must be 'sched', 'temp', 'perm', or undefined.
      'sched' means it's following the schedule.
      'temp' means a temporary hold (until next scheduled change).
      'perm' means a permanent hold (eg vacation mode)
      Note that 'setting' can only be undefined if changing temperature 
      but not mode (increasing the temperature by 1 degree).
      Note that changing the temperature when in 'sched' implies an
      automatic change to 'temp'.
    - 'low' is optional, and is the lowest temperature before heat turns on.
      If not specified, the current low temperature state is used, with the
      exception of if setting to 'perm', in which case 55 is used.
    - 'high' is optional, and is the highest temperature before a/c turns on.
      If not specified, the current high temperature state is used, with the
      exception of if setting to 'perm', 84 is used.
sequence:
  - variables:
      house: "{{ house | default('undef') }}"
      mode: |-
        {% if setting == 'sched' %}
          none
        {% elif setting == 'temp' %}
          none
        {% elif setting == 'perm' %}
          hold
        {% else %}
          undef
        {% endif %}
      set_mode: |-
        {% if setting == 'sched' %}
          True
        {% elif setting == 'temp' %}
          True
        {% elif setting == 'perm' %}
          True
        {% else %}
          False
        {% endif %}
      set_temp: |-
        {% if setting == 'sched' %}
          False
        {% elif setting == 'temp' %}
          True
        {% elif setting == 'perm' %}
          True
        {% elif (low is defined) or (high is defined) %}
          True
        {% else %}
          False
        {% endif %}
      start: "{{ now().timestamp() }}"
      entity: climate.{{house}}
      low: |-
        {% if low is defined %}
          {{ low|float }}
        {% elif setting == 'perm' %}
          55
        {% elif states(entity) == 'heat_cool' %}
          {{ state_attr(entity, 'target_temp_low') }}
        {% else %}
          {{ state_attr(entity, 'temperature') }}
        {%endif%}
      high: |-
        {% if (high is defined) and (high != '') %}
          {{ high|float }}
        {% elif setting == 'perm' %}
          84
        {% elif states(entity) == 'heat_cool' %}
          {{ state_attr(entity, 'target_temp_high') }}
        {%endif%}
      waits:
        - 5
        - 30
        - 600
        - 5
        - 30
  - if:
      - condition: template
        value_template: "{{ set_mode == True }}"
    then:
      - repeat:
          sequence:
            - action: climate.set_preset_mode
              target:
                entity_id: "{{ entity }}"
              data:
                preset_mode: "{{ mode }}"
            - wait_template: "{{ is_state_attr(entity, 'preset_mode', mode) }}"
              timeout: "{{ waits[repeat.index-1] }}"
          until:
            - condition: or
              conditions:
                - condition: template
                  value_template: "{{ state_attr(entity, 'preset_mode') == mode }}"
                - condition: template
                  value_template: "{{ repeat.index == (waits|length) }}"
  - if:
      - condition: template
        value_template: >-
          {{ (set_mode == True) and (state_attr(entity, 'preset_mode') != mode)
          }}
    then:
      - action: notify.gmail_{{house}}
        metadata: {}
        data:
          target: [email protected]
          title: Thermostat Control Error
          message: >
            Error setting thermostat for {{ house }}

            Mode: requested {{ mode }}, but have {{ state_attr(entity,
            'preset_mode') }}


            Duration: "{{ now().timestamp() - start }}"
  - if:
      - condition: and
        conditions:
          - condition: template
            value_template: "{{ set_temp == True }}"
          - condition: template
            value_template: >-
              {{ (set_mode == False) or ((set_mode == True) and
              (state_attr(entity, 'preset_mode') == mode)) }}
    then:
      - if:
          - condition: template
            value_template: "{{ states(entity) == 'heat_cool' }}"
        then:
          - repeat:
              sequence:
                - action: climate.set_temperature
                  target:
                    entity_id: "{{ entity }}"
                  data:
                    target_temp_low: "{{ low }}"
                    target_temp_high: "{{ high }}"
                - wait_template: >-
                    {{ is_state_attr(entity, 'target_temp_low', low) and
                    is_state_attr(entity, 'target_temp_high', high) }}
                  timeout: "{{ waits[repeat.index-1] }}"
              until:
                - condition: or
                  conditions:
                    - condition: template
                      value_template: >-
                        {{ is_state_attr(entity, 'target_temp_low', low) and
                        is_state_attr(entity, 'target_temp_high', high) }}
                    - condition: template
                      value_template: "{{ repeat.index == (waits|length) }}"
          - if:
              - condition: template
                value_template: >-
                  {{ (state_attr(entity, 'target_temp_low') != low) or
                  (state_attr(entity, 'target_temp_high') != high) }}
            then:
              - action: notify.gmail_{{house}}
                metadata: {}
                data:
                  target: [email protected]
                  title: Thermostat Control Error
                  message: >
                    Error setting thermostat for {{ house }}

                    Target: requested {{ low }}-{{ high }}, but have {{
                    state_attr(entity, 'target_temp_low')
                    }}-{{state_attr(entity, 'target_temp_high')}}

                    Duration: "{{ now().timestamp() - start }}"
        else:
          - repeat:
              sequence:
                - action: climate.set_temperature
                  target:
                    entity_id: "{{ entity }}"
                  data:
                    temperature: "{{ low }}"
                - wait_template: "{{ is_state_attr(entity, 'temperature', low) }}"
                  timeout: "{{ waits[repeat.index-1] }}"
              until:
                - condition: or
                  conditions:
                    - condition: template
                      value_template: "{{ is_state_attr(entity, 'temperature', low) }}"
                    - condition: template
                      value_template: "{{ repeat.index == (waits|length) }}"
          - if:
              - condition: template
                value_template: "{{ (state_attr(entity, 'temperature') != low) }}"
            then:
              - action: notify.gmail_{{house}}
                metadata: {}
                data:
                  target: [email protected]
                  title: Thermostat Control Error
                  message: >
                    Error setting thermostat for {{ house }}

                    Target: requested {{ low }}, but have {{ state_attr(entity,
                    'temperature') }}

                    Duration: "{{ now().timestamp() - start }}"
mode: restart

I also created a custom sensor (Helper → Template → Template a sensor) that’s not required but makes it easy to observe the current state of the thermostat during debugging. ie: just have the card for the sensor displayed, and you can see all the settings info. Just copy/paste and fill in the correct value for HOUSE.

{% set device = 'climate.HOUSE' %}
{% if is_state_attr(device, 'preset_mode', 'hold') %}
  {% if is_state_attr(device, 'permanent_hold', true) %}
    {% set setting= 'perm' %}
  {% else %}
    {% set setting= 'temp' %}
  {% endif %} 
{% else %}
  {% set setting= 'sched' %}
{% endif %}
{% if is_state(device, 'heat_cool') %}
  {% set temp = state_attr(device, 'target_temp_low') ~ '-' ~ state_attr(device, 'target_temp_high') %}
{% else %}
  {% set temp = state_attr(device, 'temperature') %}
{% endif %}
{{setting}}-{{temp}}