Home Assistant Heating Control
This is my setup for controlling my central heating.
I connected a Shelly 1 relay switch to my boiler (a Vaillant), wiring it in to the demand connector. On this boiler this replaces the jumper on connector 3 & 4.

I designed and 3D printed an enclosure for the Shelly 1 (download here) and stuck it on the side of the boiler.

I use 3 Sonoff thermostats to get regular temperature readings from around the house. In home assistant these are averaged into a template sensor.
sensors:
- platform: template
sensors:
average_temperature:
friendly_name: "Average temperature"
unit_of_measurement: "°C"
device_class: temperature
value_template: >
{% set hall = states('sensor.hall_sonoff_temperature') | float %}
{% set landing = states('sensor.landing_sonoff_temperature') | float %}
{% set front_room = states('sensor.front_room_sonoff_temperature') | float %}
{{ ((hall + landing + front_room) / 3) | round(1) }}
This is then set as the target sensor in a generic thermostat and the Shelly 1 switch set as the heater -
climate:
- platform: generic_thermostat
name: Landing
heater: switch.heating
target_sensor: sensor.average_temperature
min_temp: 10
max_temp: 25
target_temp: 20
cold_tolerance: 0.5
initial_hvac_mode: heat
hot_tolerance: 0
min_cycle_duration:
minutes: 5
away_temp: 14
precision: 0.1
There are a number of inputs that determine the schedule and modes -

And all the automations that make it work -
# Boost switch turned on - set boost mode for 30 minutes
alias: Heating Boost Toggled On
description: ''
trigger:
- platform: state
entity_id: input_boolean.heating_boost
from: 'off'
to: 'on'
condition: []
action:
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.heating_boost_until
data:
timestamp: '{{ now().timestamp() + 30*60 }}'
- service: python_script.heating
mode: single
# Boost end time has passed - turn off boost mode
alias: Heating Boost Expired
description: ''
trigger:
- platform: state
entity_id: sensor.date_time
condition:
- condition: state
entity_id: input_boolean.heating_boost
state: 'on'
- condition: and
conditions:
- condition: template
value_template: '{{ states.input_datetime.heating_boost_until.attributes.timestamp
< now().timestamp() }}'
action:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.heating_boost
mode: single
# Boost mode turned off - reset target temperature
alias: Heating Boost Toggled Off
description: ''
trigger:
- platform: state
entity_id: input_boolean.heating_boost
from: 'on'
to: 'off'
condition: []
action:
- service: python_script.heating
mode: single
# Every minute, reset target temperature
alias: Heating Ticker
description: ''
trigger:
- platform: state
entity_id: sensor.time
condition: []
action:
- service: python_script.heating
mode: single
# Away mode set - reset target temperature
alias: Away Changed
description: ''
trigger:
- platform: state
entity_id: input_boolean.away
condition: []
action:
- service: python_script.heating
mode: single
Several of these automations call the following python script, which simply updates the climate generic thermostats target temperature -
on_temperature = hass.states.get('input_number.heating_on_temperature').state
off_temperature = hass.states.get('input_number.heating_off_temperature').state
boost_temperature = hass.states.get('input_number.heating_boost_temperature').state
weekday_on_1 = hass.states.get('input_datetime.weekday_on_1').state
weekday_off_1 = hass.states.get('input_datetime.weekday_off_1').state
weekday_on_2 = hass.states.get('input_datetime.weekday_on_2').state
weekday_off_2 = hass.states.get('input_datetime.weekday_off_2').state
weekend_on_1 = hass.states.get('input_datetime.weekend_on_1').state
weekend_off_1 = hass.states.get('input_datetime.weekend_off_1').state
weekend_on_2 = hass.states.get('input_datetime.weekend_on_2').state
weekend_off_2 = hass.states.get('input_datetime.weekend_off_2').state
away = hass.states.get('input_boolean.away').state
last_demand = hass.states.get('input_number.heating_last_demand').state
target_temperature = off_temperature
if away == 'on':
target_temperature = off_temperature
else:
boost_active = hass.states.get('input_boolean.heating_boost').state
if boost_active == 'on':
target_temperature = boost_temperature
else:
now = hass.states.get('sensor.time').state
if datetime.datetime.now().date().weekday() < 5: # WEEK DAY
if (now >= weekday_on_1 and now <= weekday_off_1) or (now >= weekday_on_2 and now <= weekday_off_2):
target_temperature = on_temperature
else:
if (now >= weekend_on_1 and now <= weekend_off_1) or (now >= weekend_on_2 and now <= weekend_off_2):
target_temperature = on_temperature
if target_temperature != last_demand:
hass.states.set('input_number.heating_last_demand', target_temperature)
hass.services.call('climate', 'set_temperature', { 'entity_id': 'climate.landing', 'temperature': target_temperature })
I have a Raspberry Pi 3 with a Pimoroni Hyperpixel Touch display on it that is setup to run chromium browser in kiosk mode, opening the heating page (screenshot above) of Home Assistant in kiosk mode using the Kiosk Mode custom component.

I'm designing a case for this which I will 3D print and mount on the wall, but first I want use something other than the side facing USB for powering it as it will make the case larger than it needs to be.