How to Create Zone-Based Heating in Home Assistant with a Nest and Smart TRVs


How to Create Zone-Based Heating in Home Assistant with a Nest and Smart TRVs

This guide will show you how to transform your standard heating setup into a smart, zone-based system using Home Assistant.

The Problem
Most homes have a single, central thermostat (like a Nest) that controls the boiler. When the room with that thermostat reaches its target temperature, the boiler shuts off, even if other rooms in the house are still cold.

The Solution
We will use Home Assistant to create a “smarter” logic. In this setup, your main thermostat will act as a simple on/off switch for the boiler. The temperature in each individual room will be managed by a smart TRV (Thermostatic Radiator Valve).
The boiler will only turn ON if at least one room in your house is calling for heat. It will only turn OFF when every single room has reached its target temperature. This gives you true, energy-efficient, zone-based heating.


What You’ll Need
• Home Assistant installed and running.
• A primary thermostat that controls your boiler (e.g., Nest, Hive), integrated into Home Assistant.
• Smart TRVs for each radiator you want to control (e.g., Kasa, Tado, Shelly), also integrated into Home Assistant.


Step 1: Create the “Overall Heating Demand” Sensor
First, we need to create a central “brain”—a virtual sensor that knows when any room needs heat.
A. Set up a separate file for your templates
To keep your main configuration file clean, it’s best practice to put custom sensors in a separate file.

  1. In your configuration.yaml file, add the following line. This tells Home Assistant to look in a file named templates.yaml for all your template sensor configurations.
    YAML

In configuration.yaml

template: !include templates.yaml

  1. In the same /config/ directory, create a new, blank file named templates.yaml.

B. Add the sensor code
Copy the code below and paste it into your newly created templates.yaml file. You must replace the placeholder entity IDs with the actual entity IDs from your Home Assistant setup.
YAML

In templates.yaml

- binary_sensor:
    - name: "Overall Heating Demand"
      unique_id: overall_heating_demand_sensor # You can change this, but keep it unique
      state: >
        {% set climate_entities = [
          'climate.living_room_trv',  # <-- REPLACE with a TRV entity ID
          'climate.bedroom_trv',      # <-- REPLACE with a TRV entity ID
          'climate.office_trv'        # <-- REPLACE and ADD more TRVs for every room
        ] %}
        
        {% set ns = namespace(heating_needed=false) %}
        {% for entity in climate_entities %}
          {% if state_attr(entity, 'current_temperature') is not none and state_attr(entity, 'temperature') is not none and state_attr(entity, 'current_temperature') < state_attr(entity, 'temperature') %}
            {% set ns.heating_needed = true %}
          {% endif %}
        {% endfor %}
        
        {{ ns.heating_needed }}

C. Activate the new sensor

  1. Save both files.
  2. Go to Developer Tools > YAML in Home Assistant.
  3. Click the RELOAD TEMPLATE ENTITIES button. You will now have a new sensor called binary_sensor.overall_heating_demand.

Step 2: Create the “Turn Boiler ON” Automation
This automation will force your main thermostat into a “heating” state whenever the demand sensor turns on.

  1. Go to Settings > Automations & Scenes and click + CREATE AUTOMATION.
  2. Choose Start with a blank automation.
  3. Give it a name, like Heating - Turn Boiler ON.
    Under Triggers (“When”):
    • Click + ADD TRIGGER and choose Entity.
    • A new dropdown will appear; ensure it is set to State.
    • Entity: Select your new sensor, binary_sensor.overall_heating_demand.
    • To: Type on.
    • From: Type off.
    Under Actions (“Then do”):
    We will tell the main thermostat to set its target temperature very high, forcing it to call for heat.
    • Click + ADD ACTION and choose Perform action.
    • Action: Select Set target temperature.
    • Targets: Click + CHOOSE DEVICE and select your main thermostat (e.g., the Nest).
    • Target temperature: A new box will appear. Type 30.
    Click Save to finish.

Step 3: Create the “Turn Boiler OFF” Automation
This automation will force your main thermostat into an “idle” state whenever the demand sensor turns off.

  1. Create another new, blank automation.
  2. Give it a name, like Heating - Turn Boiler OFF.
    Under Triggers (“When”):
    • Click + ADD TRIGGER and choose Entity.
    • Ensure the next dropdown is set to State.
    • Entity: Select your sensor, binary_sensor.overall_heating_demand.
    • To: Type off.
    • From: Type on.
    Under Actions (“Then do”):
    We will now set the target temperature very low, forcing the boiler to turn off.
    • Click + ADD ACTION and choose Perform action.
    • Action: Select Set target temperature.
    • Targets: Click + CHOOSE DEVICE and select your main thermostat.
    • Target temperature: Type 10.
    Click Save.

Step 4: Test Your New System
Your automations are now live. To test them:
• To Test “ON”: Go to any TRV in a warm room and increase its setpoint temperature to be higher than the current room temperature. Within a minute, your main thermostat’s target temperature should jump to 30°C.
• To Test “OFF”: Go to every TRV and ensure its setpoint is lower than its current room temperature. Once the last one is set, your main thermostat’s target temperature should drop to 10°C.
• To Debug: If an automation doesn’t run, go to Settings > Automations & Scenes, click the three-dots (⋮) next to it, and select Traces to see a detailed log of what happened.
You’re all set! You now have a fully automated, zone-based heating system that provides greater comfort and efficiency.

1 Like

Great guide! Thanks for posting. I’ve been doing something similar but with a smart relay to fire the boiler.

I also use temperature sensors in each room to tell the TRVs what the temperature is.

Heating guys will say that zoning is inefficient, and it’s kind of true.

However I like to use a system like this to actually balance the house temperature all over and keep it stable. We have a south facing house so one side is hot and one side is cold. There’s also a big difference upstairs and downstairs. If you want a stable temp everywhere, this is the way to go.

Regular TRVs cater for this too, but it’s fiddlier to dial in and needs constant tweaks.

It’s also nice on occasion to be able to warm up one room, even if it isn’t the most efficient thing to do.

1 Like

I found exactly the same thing! The orientation of my house means the living room where the nest was located would heat up quickly especially when it ia sunny and they leave the back of the house to cold! So for me this is working well. I have kasa smart trvs so im using the scheduling through thier app to give me some extra control over what times i want the room to heat.

I have actually just updated my templates.yaml file as found out my kasa trvs have a deadband of .5 degrees which meant they dont actually kick in till the temp has dropped by .5 under the set temp. The new code takes this into consideration so im not just firing up the boiker before they are allowing the radiators to heat.

- binary_sensor:
    - name: "Overall Heating Demand"
      unique_id: overall_heating_demand_sensor
      state: >
        {% set deadband = 0.5 %}
        {% set climate_entities = [
          'climate.bedroom_thermostat',
          'climate.dining_room_thermostat',
          'climate.living_room_wall_thermostat',
          'climate.living_room_window_thermostat',
          'climate.nursery_thermostat'
        ] %}

        {% set ns = namespace(heating_needed=false) %}
        {% for entity in climate_entities %}
          {# The only change is subtracting the deadband from the set temperature #}
          {% if state_attr(entity, 'current_temperature') is not none and state_attr(entity, 'temperature') is not none and state_attr(entity, 'current_temperature') < (state_attr(entity, 'temperature') - deadband) %}
            {% set ns.heating_needed = true %}
          {% endif %}
        {% endfor %}

        {{ ns.heating_needed }}