Feedback on multi-zone heating automation strategy

I don’t currently have any smart heating products, but I’ve been doing some research and have a few questions about how the climate platform would work and be automated to help me narrow down my search for a suitable product/ecosystem.

Broadly, I think that I have the following options:

  • A full ecosystem, where the heating controller/smart thermostat controls the boiler and TRVs (e.g. Tado, Honeywell, multitudes of others)
  • Something just to make my boiler/thermostat controllable, and using HA to automate things (e.g. Plugwise Adam or OpenTherm Gateway and whatever climate control devices my wee heart desires).

Option 1 is of course trivial. This post, and the block of YAML below, is me trying to figure out how practical Option 2 is. It should provide far greater flexibility, but I’m wondering if it’s worth the increased complexity.

TL;DR of the below YAML is:

  • Each room has a generic thermostat
    • HA keeps the set temperature in sync with the TRV set temperature
    • Temperatures are set based on time of day
  • Boiler gets turned on when we’re home and there is demand

Since I don’t have any equipment yet some of the service and entity names below will be nonsense. Hopefully my intentions are clear.

Is this pretty much what people are doing? Have you gone down this road and found it to be impractical? Or are you really happy with it?

Is there anything in HA or the climate platform I’m clearly missing which would make all this far simpler?

BTW, I know Schedy exists. It’s on my list.

Cheers

sensor:
  - platform: template
    sensors:
        # These two sensors are used to monitor changes set either via HA or on the physical TRV, so that they can be kept in sync.
        living_room_radiator_setpoint:
            friendly_name: Living Room Radiator Set Point
            device_class: temperature
            unit_of_measurement: "°C"
            value_template: "{{ state_attr('climate.living_room_radiator','target_temperature') }}"
        living_room_generic_setpoint:
            friendly_name: Living Room Generic Thermostat Set Point
            device_class: temperature
            unit_of_measurement: "°C"
            value_template: "{{ state_attr('climate.living_room','target_temperature') }}"
  - platform: min_max
    name: Highest thermostat setpoint # Used to help determine what the boiler water temperature should be.
    type: max
    entity_ids:
      - sensor.living_room_generic_setpoint
      - sensor.kitchen_generic_setpoint

binary_sensor:
  - platform: template
    sensors:
        house_heating_mode:
            friendly_name: Whether heating should be on based on house mode
            value_template: >
                {{
                   state_attr('input_select.house_mode','Home' ) or
                   state_attr('input_select.house_mode','Night')
                }}

climate:
    - platform: generic_thermostat
      name: Living Room
      heater: input_boolean.living_room_heating_demand
      target_sensor: sensor.living_room_temperature

input_boolean:
    living_room_heating_demand:
        name: On if Living Room demands heat

group:
    heating_demand:
        name: On if ANY thermostats demand heat
        entities:
            - input_boolean.living_room_heating_demand

# Temperature schedules for each zone
automation:
    - id: set_temp_living_room_1
      trigger:
          - platform: time
            at: "16:30:00"
      action:
          - service: climate.set_temperature
            entity_id: climate.living_room_generic_thermostat
            data_template:
              temperature: 23
    - id: set_temp_living_room_2
      trigger:
          - platform: time
            at: "23:00:00"
      action:
          - service: climate.set_temperature
            entity_id: climate.living_room_generic_thermostat
            data_template:
              temperature: 10

# Turn boiler on if we're home and there's demand.
# Every 5 minutes to minimise flapping.
automation:
    - id: boiler_on
      alias: Turn boiler on
      trigger:
          - platform: time_pattern
            minutes: "/5"
      condition:
          condition: and
          conditions:
            - condition: state
              entity_id: group.heating_demand
              state: "on"
            - condition: state
              entity_id: sensor.house_heating_mode
              state: "on"
      action:
          - service: switch.turn_on
            entity_id: switch.boiler
          - service: climate.set_temperature # I hope to use OpenTherm to set the radiator water temperature.
            entity_id: climate.boiler
            data_template:
              temperature: "{{ sensor.highest_thermostat_setpoint }}"

# Else turn it off
automation:
    - id: boiler_off
      alias: Turn boiler off
      trigger:
          - platform: time_pattern
            minutes: "/5"
      condition:
          condition: or
          conditions:
            - condition: state
              entity_id: group.heating_demand
              state: "off"
            - condition: state
              entity_id: sensor.house_heating_mode
              state: "off"
      action:
          - service: climate.turn_off
            entity_id: climate.boiler

# Sync thermostats
automation:
    - id: sync_living_room_thermostats
      alias: Sync Living Room TRV setpoint with Living Room Generic Thermostat setpoint
      mode: single # Only run one instance of this script at a time
      trigger:
          - platform: state
            entity_id: sensor.living_room_radiator_setpoint
          - platform: state
            entity_id: sensor.living_room_generic_setpoint
      action:
          - delay:
                seconds: 10 # Give user time to finish making their adjustments
          - service: climate.set_temperature
            data_template:
                entity_id: >
                    {% if trigger.entity_id == "sensor.living_room_radiator_setpoint" %}
                    climate.living_room
                    {% elif trigger.entity_id == "sensor.living_room_generic_setpoint" %}
                    climate.living_room_radiator
                    {% endif %}
                temperature: "{{ trigger.to_state }}"
          - delay:
                seconds: 1 # Wait a second so that the change this script makes doesn't start a new instance of itself

I’m starting to realise that the reason I can’t find much on this strategy is because it is, indeed, impractical to build a multi-zone climate system without a thermostat that also supports multiple zones.

While it’s theoretically possible to override the thermostat’s current and set temperatures, and thus cause it to turn on the boiler, it’s straying into software thermostat territory which may be a bit more than I want to try chewing, considering the potential consequences of getting it wrong.

So if anyone else ends up here, I suggest you change tack.