Automation driven HVAC Temperature control

I have a WaterFurnace geothermo heat pump that allows access to the thermostat data and power utilization via modbus. I documented my efforts with HA here. The below information is pulled from that post, but is only related to automation control. I’m reposting as I believe the automations might be useful to others with a smart thermostat.

To fully use them you would also need access to your HVAC power utilization. I also have Emporia Vue 3 that reports power utilization from each electrical circuit in my house. So using something like the Emporia with your smart thermostat you could use the below automations as a starting point.

Things I wanted when putting these automation together

  • Adjust thermostat heating and cooling set points via HA
  • Automatically change set points when everyone is out of the house
  • Automatically set temperature back to desired temperature when anyone returns home
  • Have a vacation mode that does larger adjustments of temperature when house is vacated
  • In the winter reduce heating temperature at night, as the wife likes it cold then
  • Controlled raising of heating set points so Aux heat is not utilized
  • While automations continue to change the thermostat heating and cooling set points, still capture the desired set points if they are manual adjusted at the thermostat

The above is what the automations discussed below accomplish. In addition, during the work on these automation I discovered that my thermostat will report multiple degree changes in the ambient temperature randomly. This would cause Aux heat to kick in. The automations respond to this, stopping the use of Aux heat.

I stated with three automations to control the furnace and this has now expanded to 7 automation. The original three are still in use. The first automation will lower the heat 1 degree or raise cooling by 2 degrees when everyone is out of the house. I added a vacation mode to this automation that will raise the cooling and lower the heat by 3 degrees. The second automation kicks off the action to get the temperature back to the desired set points as soon as someone returns home.

I use four HA helpers entities in support of these automations. The first is group.persons, which contains all of the individuals living in the house. I track individual’s home and away status with a combination of ping trackers and location information reported by the phone app. The second and third helpers are input_numbers, used to capture the desired low (Heating) and high (Cooling) temperatures. The last helper is a timer used in raise the heating one degree at a time. Here’s the automation that runs when everyone has exited the house:

alias: Thermostat temp change on Away
description: ""
triggers:
  - entity_id:
      - group.persons
    to: not_home
    from: home
    trigger: state
    alias: Change high and low temp when no one home
conditions: []
actions:
  - action: timer.cancel
    target:
      entity_id: timer.aux_heat_timer
    data: {}
  - if:
      - condition: state
        entity_id: alarm_control_panel.home_alarm
        state: armed_vacation
    then:
      - metadata: {}
        data:
          target_temp_high: "{{ states('input_number.high_temp')|float +3 }}"
          target_temp_low: "{{ states('input_number.low_temp')|float -3 }}"
        target:
          entity_id: climate.waterfurnace_zone_1
        action: climate.set_temperature
    else:
      - metadata: {}
        data:
          target_temp_high: "{{ states('input_number.high_temp')|float +2 }}"
          target_temp_low: "{{ states('input_number.low_temp')|float -1 }}"
        target:
          entity_id: climate.waterfurnace_zone_1
        action: climate.set_temperature
mode: single

The above automation raises the cooling and lowers the heating thermostat set points. The amount of change is different when the house has been put in vacation mode. I use the HA alarm panel to enable the alarm system. One of the modes on the alarm panel is vacation mode. During vacation mode the change in set points is greater than the daily exiting the house set points.

The next automation handles when a person returns to the house. This automation kicks off the steps to get the house back to the desire heading and cooling set points.
The high and low input number helpers hold the desired values and are used here. The furnace cooling set point is set to the desired level, while the heating set point is simply increased by 1 degree. A timer helper is started to continue the process of raising the temperature in the house.

alias: Thermostat temp change on Home
description: ""
triggers:
  - entity_id:
      - group.persons
    to: home
    from: not_home
    trigger: state
    alias: Change high and low temp when someone returns home
conditions: []
actions:
  - metadata: {}
    data:
      target_temp_low: >-
        {{  state_attr('climate.waterfurnace_zone_1', 'target_temp_low' ) |
        float +1 }}
      target_temp_high: "{{ states('input_number.high_temp') | float }}"
    target:
      entity_id: climate.waterfurnace_zone_1
    action: climate.set_temperature
  - action: timer.start
    target:
      entity_id: timer.aux_heat_timer
    data:
      duration: "00:05:00"
mode: single

The next automation will reduce the temperature by two degrees when everyone should be sleeping and then raise it before people get out of bed. Originally I would also adjust the cooling temperature at night, however I found this wasn’t really desired as we keep the house pretty warm in the summer:

alias: Night Thermostat Heat Shifts
description: ""
triggers:
  - at:
      - "06:30:00"
      - "22:15:00"
    trigger: time
conditions:
  - condition: state
    entity_id: group.persons
    state: home
actions:
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ trigger.now.hour == 6 }}"
          - condition: template
            value_template: >-
              {{ state_attr('climate.waterfurnace_zone_1', 'target_temp_low') <
              states('input_number.low_temp')|float }}
        sequence:
          - metadata: {}
            data:
              target_temp_low: >-
                {{  state_attr('climate.waterfurnace_zone_1', 'target_temp_low'
                ) | float +1 }}
              target_temp_high: >-
                {{ state_attr('climate.waterfurnace_zone_1', 'target_temp_high'
                )|float }}
            target:
              entity_id: climate.waterfurnace_zone_1
            action: climate.set_temperature
          - action: timer.start
            target:
              entity_id: timer.aux_heat_timer
            data:
              duration: "01:30:00"
        alias: If 6:30 and not at desired heat then increase temp by 1
      - conditions:
          - condition: template
            value_template: "{{ trigger.now.hour == 22 }}"
        sequence:
          - action: timer.cancel
            target:
              entity_id: timer.aux_heat_timer
            data: {}
          - metadata: {}
            data:
              target_temp_low: "{{ states('input_number.low_temp')|float -2 }}"
              target_temp_high: >-
                {{ state_attr('climate.waterfurnace_zone_1', 'target_temp_high'
                )|float }}
            target:
              entity_id: climate.waterfurnace_zone_1
            action: climate.set_temperature
        alias: If 22:15 reduce temperature by 2 degrees

The WaterFurnace will use Aux heat if the temperature difference is greater than 2 degrees. Originally, I would just bump the heating by two degrees in the morning, which should not have kicked off Aux heat. For some strange reason the thermostat ambient temperature will drop a little, even though the house is getting warmer. Every couple of days the aux heat would kick on, which is bad from an energy use perspective. To deal with this I changed the automation to only up the heating 1 degree at a time. I added a timer helper that is used to continue the process of raising the heating temperature until the heating set point gets to the desired level.

The above automation does the initial one degree temperature increase and starts the timer. When the timer finishes the automation below runs and determines if we still need to keep raising the temperature. Before raising the temperature the automation checks the current energy usage of the WaterFurnace to make sure it’s at a good place to raise the temperature another degree. I read that the WaterFurnace is most efficient in the middle of it’s compressors working range. If we haven’t finished raising the temperature to the desired level the timer is started again.

alias: Handle Heating Adjustment Timer
description: ""
triggers:
  - event_type: timer.finished
    event_data:
      entity_id: timer.aux_heat_timer
    trigger: event
conditions:
  - condition: template
    value_template: >-
      {{ state_attr('climate.waterfurnace_zone_1', 'target_temp_low') <
      states('input_number.low_temp')|float }}
    alias: If temp below desired temp and someone home
  - condition: state
    entity_id: group.persons
    state: home
actions:
  - alias: If in a state that will not cause aux heat to kick in then up temp by 1
    if:
      - condition: template
        value_template: >-
          {{ states('sensor.waterfurnace_aux_heater_power_usage')|float < 600 
          }}
      - condition: template
        value_template: >-
          {{ states('sensor.waterfurnace_heat_pump_total_power_usage')|float <
          3000 }}
      - condition: template
        value_template: >-
          {{ (state_attr('climate.waterfurnace_zone_1', 'target_temp_low')|float
          -1 ) <= states('sensor.waterfurnace_zone_1_ambient_temperature')|float
          }}
    then:
      - metadata: {}
        data:
          target_temp_high: "{{ states('input_number.high_temp') | float }}"
          target_temp_low: >-
            {{ state_attr('climate.waterfurnace_zone_1', 'target_temp_low')| int
            + 1}}
        target:
          entity_id: climate.waterfurnace_zone_1
        action: climate.set_temperature
        enabled: true
      - if:
          - condition: state
            entity_id: person.brian
            state: home
        then:
          - data_template:
              message: >-
                Heating temp increased by 1 {{
                state_attr('climate.waterfurnace_zone_1',
                'target_temp_low')|float }}
            action: notify.mobile_app_brian_pixel_8
        else:
          - data_template:
              number: XXXXXXXXXX
              message: >-
                Heating temp increased by 1 {{
                state_attr('climate.waterfurnace_zone_1',
                'target_temp_low')|float }}
            action: shell_command.send_whatsapp_msg
  - action: timer.start
    target:
      entity_id: timer.aux_heat_timer
    data:
      duration: "00:15:00"
  - if:
      - condition: state
        entity_id: person.brian
        state: home
    then:
      - data_template:
          message: "Kicked off heat delay 15 minute timer "
        action: notify.mobile_app_brian_pixel_8
    else:
      - data_template:
          number: XXXXXXXXXX
          message: "Kicked off heat delay 15 minute timer "
        action: shell_command.send_whatsapp_msg
mode: single

My thermostat seems to be a little flaky and arbitrarily reports a drop in ambient temperature for no apparent reason. In some cases the ambient temperature would drops by over 2 degrees, causing the Aux heat to kick on. I added this next automation to look for the aux heat kicking on and when that happened, reduce the temperature set point to turn the aux heat back off. The timer helper mentioned above is used to ensure at some point we get back to the desired heating set point.

alias: Deal with Aux Heating condition
description: ""
triggers:
  - alias: When Aux Heat kicks in
    trigger: template
    value_template: "{{ states('sensor.waterfurnace_aux_heater_power_usage')|float > 1000  }}"
conditions: []
actions:
  - choose:
      - conditions:
          - condition: state
            entity_id: group.persons
            state: home
          - condition: template
            value_template: >-
              {{ (states('input_number.low_temp')|float - 5) <=
              states('sensor.waterfurnace_zone_1_ambient_temperature')|float}}
        sequence:
          - metadata: {}
            data:
              target_temp_high: "{{ states('input_number.high_temp') | float }}"
              target_temp_low: >-
                {{ states('sensor.waterfurnace_zone_1_ambient_temperature')| int
                + 1}}
            target:
              entity_id: climate.waterfurnace_zone_1
            action: climate.set_temperature
            enabled: true
            alias: Set WF temp to 1 degree above WF ambient_temperature
          - if:
              - condition: state
                entity_id: person.brian
                state: home
            then:
              - data_template:
                  message: >-
                    Stopping Aux Heating power {{
                    states('sensor.emporiavue3_aux_heat_19_power')|float }}
                action: notify.mobile_app_brian_pixel_8
            else:
              - data_template:
                  number: XXXXXXXXXX
                  message: >-
                    Stopping Aux Heating power {{
                    states('sensor.emporiavue3_aux_heat_19_power')|float }}
                action: shell_command.send_whatsapp_msg
            alias: Send brian notification
          - action: timer.start
            target:
              entity_id: timer.aux_heat_timer
            data:
              duration: "00:15:00"
        alias: If someone is home and we are within 5 degree of target heat
    default:
      - alias: Send notification not dealing with Aux heat event
        if:
          - condition: state
            entity_id: person.brian
            state: home
        then:
          - data_template:
              message: >-
                No one home so ignore Aux Heating power {{
                states('sensor.emporiavue3_aux_heat_19_power')|float }}
            action: notify.mobile_app_brian_pixel_8
        else:
          - data_template:
              number: XXXXXXXXXX
              message: >-
                No one home so ignore Aux Heating power {{
                states('sensor.emporiavue3_aux_heat_19_power')|float }}
            action: shell_command.send_whatsapp_msg
mode: single

The above automation disables the utilization of auxiliary heat as long as the ambient temperature in the house is within 6 degrees of the desired heating set point. This might not be desirable in a really cold environment. In my location I do not expect aux heating to turn on if the WaterFurnace geothermal capability is functional. As result I’m fine with limiting Aux heat ability to raise the house temperature.

The last two automation were added so I could change the heat and cooling set points at the thermostat, and then this would update the two helper values that keep track of the desired high and low temperature set points. These automations will not update the desired set points if one of the above automation is the cause for the change in thermostat set points. The next automation capture a change to the desired heating set point:

alias: Set low temp variable if changed by thermostat
description: ""
triggers:
  - trigger: template
    value_template: >-
      {{ state_attr('climate.waterfurnace_zone_1', 'target_temp_low')|float !=
      states('input_number.low_temp')|float }}
    alias: thermostat low temp changed
conditions:
  - condition: and
    conditions:
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.night_thermostat_temp_shifts',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.thermostate_temp_change_on_away',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.thermostate_temp_change_on_home',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.deal_with_aux_heating_condition',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.handle_heating_adjustment_timer',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.vacation_done_so_free_thermostat',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.set_thermostat_for_vaction',
          'last_triggered') > timedelta(minutes=1) }}      
    alias: If it wasn't changed by one of the automations
actions:
  - metadata: {}
    data:
      value: >-
        {{ state_attr('climate.waterfurnace_zone_1', 'target_temp_low' )|float
        }}
    target:
      entity_id: input_number.low_temp
    action: input_number.set_value
    alias: Save the value to desired low temp variable
mode: single

This final automation here captures a change to the desired the cooling set point.

alias: Set high temp variable if changed by thermostat
description: ""
triggers:
  - trigger: template
    value_template: >-
      {{ state_attr('climate.waterfurnace_zone_1', 'target_temp_high')|float !=
      states('input_number.high_temp')|float }}
    alias: thermostat high temp changed
conditions:
  - alias: If it wasn't changed by one of the automations
    condition: and
    conditions:
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.night_thermostat_temp_shifts',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.thermostate_temp_change_on_away',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.thermostate_temp_change_on_home',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.deal_with_aux_heating_condition',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.handle_heating_adjustment_timer',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.vacation_done_so_free_thermostat',
          'last_triggered') > timedelta(minutes=1) }}
      - condition: template
        value_template: >-
          {{ now() - state_attr('automation.set_thermostat_for_vaction',
          'last_triggered') > timedelta(minutes=1) }}
actions:
  - metadata: {}
    data:
      value: >-
        {{ state_attr('climate.waterfurnace_zone_1', 'target_temp_high' )|float
        }}
    target:
      entity_id: input_number.high_temp
    action: input_number.set_value
    alias: Save the value to desired high temp variable
mode: single

The above automation work well. I still have not quantitatively validated that these automation result in a reduction of power utilization. I have done checks on power utilization across a 24 hour period and it appears that power consumption is no worst than if I had just left the thermostat at a fixed set point. The positive is that my wife gets the cooler winter night temperature that she desires for sleeping. I hope in get two 24 hour periods with projected similar temperatures and then run one day with these automation and one day without them. At which point I can compare the total energy usage for these two periods and have a more definitive indication of which mode is better from an energy perspective.