Introduction
With the recent energy price increases across the world it seemed like an appropriate time to post details of my project that attempts to save energy in the home by turning down the heating based on room occupancy.
I started automating my home heating system around 2 years ago and have been tweaking & improving it ever since. I made a number of changes over last summer with a view to using multiple sensors to help save energy and I am now in a position to compare how my energy usage has changed between the winter just gone and the previous winter (details are provided at the end of this post however if you don’t want to scroll down then the result is that savings of around 26% were achieved).
I should add that my heating system is all electric however most of my implementation could easily be replicated in other systems such as those which use automated TRV’s.
Heating System Overview
My heating system utilises Delonghi Fivvy oil filled electric panel radiators which utilise a fourth pilot wire signal to allow a central controller to set the rooms mode based on a timer that supports up to 4 independent zones. The desired room temperature is set on the radiators directly using a remote control and the central controller changes the mode (Comfort/Comfort-1C/Comfort-2C/Eco etc.) based on the program.
In order to replace the original controller and interface the system to Home Assistant I used several Qubino Z-Wave Flush Pilot Wire Modules (model number ZMNHJD1 for the EU). The number of modules required depends on the degree of control that is required, in my current setup I currently have 4 modules installed and each controls one or more radiators as follows:
- Zone 1 - 4 x radiators in the hall and other bedrooms.
- Zone 2 - 3 x radiators in the lounge/dining room.
- Zone 3 - 1 x radiator in the study (home office).
- Zone 4 - 1 x radiator in the master bedroom.
The bathrooms have wall mounted heaters that are controlled independently using Shelly 1’s and a traditional Thermostat in Home Assistant.
The above Qubino modules essentially operate as a dimmable light and the heater mode can be changed by setting the brightness level between 0 – 100%. In response, a variable 230VAC signal is sent on the pilot wire output that controls the radiator mode. The module also supports an external temperature sensor and 3 configurable switch inputs which I am not using. I currently use Aqara Zigbee temperature sensors in my setup as this was easier than installing hardwired temperature sensors in suitable locations away from the radiators.
Methods used to save Energy
I have used a number of different methods to try and reduce my overall energy consumption and these are summarised below:
- Change all radiator modes to Eco when the home is detected as unoccupied.
- Change a single radiators mode to Eco when the room is detected as unoccupied for greater than 30 minutes (primarily uses Z-Wave PIR sensors but also other sensors such as TV’s).
- Change a single radiators mode to Eco when certain inhibit conditions for the room are detected e.g. open balcony door, open window, cooling fan running.
- Dynamically adjust a radiators on time based on the rooms current temperature and it’s typical heat-up rate (oC/hr). This has the effect of bringing on the heating earlier if it is especially cold but also delays the heating coming on if the room is already warm e.g. due to the winter sun).
- Use a modified Workday Binary Sensor to better suit our needs (essentially we stay up later on a Friday and go to bed earlier on a Sunday due to work so a traditional Monday to Friday workday sensor does not always work for us).
User Interface
There are two timers defined, one for Sleeping Areas and another for Living Areas. Each timer has a number of input_datetime
entities which are used to define the on/off times for workdays and non-workdays respectively. In the case of the on times, these reflect the time the user wants the room up to temperature, the actual on time is determined by subtracting the calculated heat-up time as described above.
Each radiator (or group of radiators) has its own thermostat which uses the HACS Simple Thermostat custom card by @nervetattoo.
Features of the above card include:
- Manual Mode toggle that allows the user to disable the timer function and manually select the preset using the buttons along the bottom of the card.
- Inhibit Indication - if the heating is inhibited (forced to the Eco Preset) then the cause is shown via an orange icon.
- Boost function that sets the Preset to Comfort for 1 hour and then reverts to the previous setting.
- Advance feature that advances to the next timer setting (useful if the heating in on and you know you are going out shortly).
- On at Night which sets the Preset to Eco during the night rather than Away.
Configuration
Most of the configuration is “home grown” yaml however I am making use of the HACS Home Assistant Qubino Wire Integration by @piitaya which is a customised thermostat specifically designed to work with the Qubino modules. Essentially it makes the relevant preset modes available on the thermostat card and allows you to control the modes without having to worry about hard-coding the relevant brightness levels for each of these.
I have included the yaml for the major parts on the configuration and hope to upload a more comprehensive version to Github once I have tidied things up a bit.
Customised Workday Binary Sensor:
workday_heating:
friendly_name: "Workday Heating"
value_template: >
{% set today = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'][now().weekday()] %}
{% set hour = now().hour %}
{% if is_state('binary_sensor.workday', 'on') %}
{{ 'false' if (today == 'Friday') and (hour > 12) else 'true' }}
{% elif is_state('binary_sensor.workday', 'off') %}
{{ 'true' if (today == 'Sunday') and (hour > 12) else 'false' }}
{% endif %}
Example Zone Occupancy Binary Sensor:
z4_occupied:
friendly_name: "Z4 Occupied"
device_class: occupancy
value_template: >-
{{is_state('binary_sensor.neo2', 'on') or
is_state('binary_sensor.neo3', 'on') or
is_state('binary_sensor.neo4', 'on') }}
delay_on: "00:00:01"
delay_off: "00:30:00"
Example Zone Inhibit Binary Sensor:
z4_inhibit:
friendly_name: "Z4 Inhibit Active"
value_template: >-
{{ is_state('switch.gosund02', 'on') or is_state('input_boolean.z4_manual_operation', 'on') or
is_state('binary_sensor.home_occupied', 'off') or is_state('binary_sensor.ensuite_window', 'on') }}
Example Zone Timer State Binary Sensor:
z4_timer:
friendly_name: "Zone 4 Timer"
# Calc heat-up time and limit between 15 mins to 2.0 hrs
value_template: >
{% set time = (now().hour * 3600) + (now().minute * 60) %}
{% set heatup_time = ((20.0 - states('sensor.bed1_temperature') | float(15)) / 1.5 * 3600.0 ) | int %}
{% set heatup_time = [[heatup_time , 900] | max, 7200] | min %}
{% if is_state('binary_sensor.workday_heating', 'on') %}
{% set on1 = state_attr('input_datetime.z1_on_time_am', 'timestamp') - heatup_time %}
{% set off1 = state_attr('input_datetime.z1_off_time_am', 'timestamp') %}
{% set on2 = state_attr('input_datetime.z1_on_time_pm', 'timestamp') - heatup_time %}
{% set off2 = state_attr('input_datetime.z1_off_time_pm', 'timestamp') %}
{% else %}
{% set on1 = state_attr('input_datetime.z1_on_time_am_nwd', 'timestamp') - heatup_time %}
{% set off1 = state_attr('input_datetime.z1_off_time_am_nwd', 'timestamp') %}
{% set on2 = state_attr('input_datetime.z1_on_time_pm_nwd', 'timestamp') - heatup_time %}
{% set off2 = state_attr('input_datetime.z1_off_time_pm_nwd', 'timestamp') %}
{% endif %}
{% if ((time >= on1) and (time < off1)) or ((time >= on2) and (time < off2)) %} true
{% else %} false
{% endif %}
Example Zone Preset Actual Sensor (used for testing and will eventually be removed):
z4_preset_act:
friendly_name: Zone 4 Preset Act
value_template: >-
{% set mapper =
{ '0':'Unknown', '10':'Standby', '20':'Away', '30':'Eco', '40':'Comfort -2C', '50':'Comfort -1C', '100':'Comfort' } %}
{% set state = (state_attr('light.qubino_pw04_level','brightness') | int * 100 / 255) | int | string %}
{{ mapper[state] if state in mapper else 'Unknown' }}
Example Zone Heat-up time Sensor:
z1_heatup_time:
friendly_name: "Zone 1 Heatup Time"
unit_of_measurement: min
value_template: >
{{ [((21.0 - states('sensor.bed1_temperature') | float(19.5) | float) / 1.5 * 60.0 ) | int, 900] | min }}
The main Heating Control Automation Blueprint:
blueprint:
name: Heating Control
description: Controls heating for a given zone thermostat
domain: automation
input:
advance:
name: Advance
description: The entity that activates the heating advance function for the zone.
selector:
entity:
domain: input_boolean
boost:
name: Boost
description: The entity that activates the heating boost function for the zone.
selector:
entity:
domain: switch
inhibit:
name: Inhibit
description: The entity that indicatess when the heating for the zone is inhibited (e.g. due to an open window).
selector:
entity:
domain: binary_sensor
manual_operation:
name: Manual Operation
description: The entity that activates manual operation or the zone (the automatic timer and advance/boost functions are disabled when Manual Operation is on).
selector:
entity:
domain: input_boolean
night:
name: Night
description: The entity that activates the night function for the zone (selects heating on with the Eco Preset during the night).
selector:
entity:
domain: input_boolean
preset_act:
name: Preset Actual
description: The entity that displays the current preset for the zone.
selector:
entity:
domain: sensor
qubino:
name: Qubino
description: The enitity that controls the Qubino Pilot Wire Module for the zone.
selector:
entity:
domain: light
thermostat:
name: Thermostat
description: The entity that acts as the user interface for the zone in the Front End.
selector:
entity:
domain: climate
timer:
name: Timer
description: The entity that indicates when the zones automatic timer is in the on state.
selector:
entity:
domain: binary_sensor
warm_up_timer:
name: Warm-up Timer
description: The entity that keeps the heating on for a pre-defined period after the heating turns on.
selector:
entity:
domain: timer
warm_up_duration:
name: Warm-up Duration
description: Duration for the Warm-up Timer.
selector:
time:
mode: restart
max: 10
variables:
boost_entity: !input boost
night_entity: !input night
inhibit_entity: !input inhibit
qubino_entity: !input qubino
timer_entity: !input timer
warm_up_timer_entity: !input warm_up_timer
advance_entity: !input advance
trigger:
- entity_id: !input inhibit
platform: state
- entity_id: !input night
platform: state
- entity_id: !input advance
platform: state
to: "off"
- entity_id: !input advance
id: advance_on
platform: state
to: "on"
- entity_id: !input boost
platform: state
to: "off"
- entity_id: !input manual_operation
platform: state
to: "off"
- entity_id: !input timer
id: timer_off
platform: state
to: "off"
- entity_id: !input timer
id: timer_on
platform: state
to: "on"
- entity_id: !input boost
id: boost_on
platform: state
to: "on"
- entity_id: input_boolean.heating_start
id: heating_start_on
platform: state
to: "on"
- entity_id: !input manual_operation
id: manual_on
platform: state
to: "on"
- entity_id: !input warm_up_timer
platform: state
to: idle
condition: []
# Perform admin tasks prior to setting heating for this zone
action:
- choose:
- conditions:
- condition: trigger
id: advance_on
sequence:
- service: switch.turn_off
target:
entity_id: !input boost
- conditions:
- condition: trigger
id: boost_on
sequence:
- service: input_boolean.turn_off
target:
entity_id: !input advance
- conditions:
- condition: trigger
id: manual_on
sequence:
- service: homeassistant.turn_off
target:
entity_id:
- !input advance
- !input boost
- !input thermostat
- conditions:
- condition: trigger
id: timer_off
sequence:
- service: input_boolean.turn_off
target:
entity_id: !input advance
- conditions:
- condition: trigger
id: timer_on
sequence:
- service: input_boolean.turn_off
target:
entity_id: !input advance
- service: timer.start
data:
duration: !input warm_up_duration
target:
entity_id: !input warm_up_timer
default: []
# Only continue if heating_start flag is on and this zone is not in manual
- condition: and
conditions:
- condition: state
entity_id: input_boolean.heating_start
state: "on"
- condition: state
entity_id: !input manual_operation
state: "off"
# Small delay to allow admin tasks to fully complete
- delay: 00:00:01
# Set heating for this zone
- service: homeassistant.turn_on
data_template:
brightness_pct: >
{% if is_state(boost_entity, 'on') %} 100
{% elif is_state(timer_entity, 'on') and is_state(advance_entity, 'on') %} 10
{% elif (is_state(timer_entity, 'on') or is_state(advance_entity, 'on')) and is_state(warm_up_timer_entity, 'active') %} 100
{% elif (is_state(timer_entity, 'on') or is_state(advance_entity, 'on')) and is_state(inhibit_entity, 'off') %} 50
{% elif is_state(timer_entity, 'on') and is_state(inhibit_entity, 'on') %} 30
{% elif is_state(timer_entity, 'off') and is_state(night_entity, 'on') and is_state('binary_sensor.night_time', 'on') %} 30
{% elif is_state(timer_entity, 'off') %} 10
{% else %} 0
{% endif %}
entity_id: "{{ qubino_entity }}"
- delay: 00:00:01
- service: homeassistant.update_entity
data: {}
entity_id: !input preset_act
In addition to the above there are a number of helpers for each zone which I have configured via the UI:
- Zone 4 Advance (Toggle)
- Zone 4 Manual Operation (Toggle)
- Zone 4 on at Night (Toggle)
- Zone 4 Warm Up Timer (Timer)
Possible Improvements
- Add additonal Qubino modules to provide more granular control of rooms.
- Use the Proximity Integration to detect when we are approaching home to turn on the heating (currently this relys on prescence detection so there can be a delay before the home heats up).
- Use bed occupied detection to determine when everyone has gone to bed (this would avoid the 30 minute occupancy delay before radiators turn off).
- Rewrite the Blueprint to set preset modes by name rather than by dimmer percentage (this would improve readability of the code).
Results
Firstly congratulations if you made if this far!
The table below shows the difference in our energy consumption over the past two winters i.e. before and after implementing the automations described above. This shows a consistent monthly reduction totalling 2836KWh which equates to a saving of ÂŁ414 ($541) at our current unit rate of 14.6p/kWh. Note that this will have a much larger effect when our current fixed rate deal ends in July and the unit rate is expected to more than double!
I do accept that there may be other factors at play such as how cold a winter is etc, however I am pleased with the results so far and will continue to monitor this going forward and aim to implement additional improvements.
Please note that this is very much a work in progress but I hope that it might give ideas or inspiration to others and welcome any comments or suggestions.