For those of us having to work with the hardware we have, which includes no way to control the boiler other than on and off.
I use 2 generic thermostats in Home Assistant, one for downstairs and one for upstairs.
switch:
- name: "House Boiler"
state_topic: "building/dhjm/boiler/states/house"
command_topic: "building/dhjm/boiler/control/house"
payload_on: "on"
payload_off: "off"
state_on: "on"
state_off: "off"
unique_id: onewire-d99ea90a-a20b-4ae6-9b75-662b47d28c0b
- name: "Upper House Boiler"
state_topic: "building/dhjm/boiler/states/bedrooms"
command_topic: "building/dhjm/boiler/control/bedrooms"
payload_on: "on"
payload_off: "off"
state_on: "on"
state_off: "off"
unique_id: onewire-6ac98a97-2e10-4aa4-83aa-3d2622c9dcd9
climate:
- platform: generic_thermostat
name: Lower House
heater: switch.house_boiler
target_sensor: sensor.average_house_temperature
min_temp: 14
max_temp: 25
min_cycle_duration:
minutes: 15
initial_hvac_mode: "heat"
away_temp: 16.5
cold_tolerance: 0.3
hot_tolerance: 0.3
keep_alive:
minutes: 5
precision: 0.1
- platform: generic_thermostat
name: Upper House
heater: switch.upper_house_boiler
target_sensor: sensor.average_upper_bedrooms
min_temp: 14
max_temp: 25
min_cycle_duration:
minutes: 15
initial_hvac_mode: "heat"
away_temp: 16.5
cold_tolerance: 0.3
hot_tolerance: 0.3
keep_alive:
minutes: 5
precision: 0.1
On the hardware side, I’m actually using an 8 channel One Wire Relay board that is controlled by an OW-ENET-2.
Now for the fun logic part, I use Schedy in AppDaemon, with this config - which lets me compensate for all sorts of things, like outside weather conditions, season, forecast, whether I am home etc.
schedy_heating: # This is our app instance name.
module: hass_apps_loader
class: SchedyApp
actor_type: thermostat
schedule_snippets:
weather:
- x: "Add(+0.125) if is_on('binary_sensor.spring_season') else Next()"
- x: "Add(-0.35) if is_on('binary_sensor.summer_season') else Next()"
- x: "Add(+0.125) if is_on('binary_sensor.autumn_season') else Next()"
- x: "Add(+0.550) if is_on('binary_sensor.winter_season') else Next()"
- x: "Add(-0.1) if is_on('input_boolean.heating_eco_mode') else Next()"
- x: "Add(-0.4) if is_off('input_boolean.home_state_home') else Next()"
- x: "Add(+0.125) if float(state('sensor.cc_30_min_gust')) > 15 and (state('sensor.wind_dir_cardinal')[:1] == 'N' or state('sensor.wind_dir_cardinal')[:1] == 'E') else Next()"
# - x: "Add(+0.025) if (float(state('sensor.cc_outside_temperature')) < 15.1 and float(state('sensor.cc_rain_rate')) > 0) else Next()"
- x: "Add(+0.2) if float(state('sensor.cc_outside_temperature')) < -9.9 else Next()"
- x: "Add(+0.0750) if float(state('sensor.cc_outside_temperature')) < -4.9 else Next()"
- x: "Add(+0.045) if float(state('sensor.cc_outside_temperature')) < 0.1 else Next()"
- x: "Add(+0.035) if float(state('sensor.cc_outside_temperature')) < 5.1 else Next()"
- x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 10.1 else Next()"
- x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 14.1 else Next()"
- x: "Add(-2.5) if float(state('sensor.cc_outside_temperature')) > 30.1 else Next()"
- x: "Add(-1.2) if float(state('sensor.cc_outside_temperature')) > 24.9 else Next()"
- x: "Add(-0.9) if float(state('sensor.cc_outside_temperature')) > 22.9 else Next()"
- x: "Add(-0.7) if float(state('sensor.cc_outside_temperature')) > 21.9 else Next()"
- x: "Add(-0.5) if float(state('sensor.cc_outside_temperature')) > 20.9 else Next()"
- x: "Add(-0.3) if float(state('sensor.cc_outside_temperature')) > 16.8 else Next()"
- x: "Add(+0.025) if (float(state('sensor.cc_outside_temperature')) < 15.1 and float(state('sensor.openweathermap_forecast_precipitation_probability')) > 75) else Next()"
# - x: "Postprocess(lambda result: round(result, 1))"
rooms:
house:
actors:
climate.lower_house:
rescheduling_delay: 60
watched_entities:
- "input_boolean.heating_night_mode"
- "input_boolean.home_state_home"
- "sensor.cc_outside_temperature"
- "sensor.cc_30_min_gust"
- "sensor.cc_rain_rate"
- "sensor.wind_dir_cardinal"
- "input_boolean.heating_eco_mode"
- "sensor.livingroom_temperature_temperature"
- "binary_sensor.livingroom_presence"
- "binary_sensor.spring_season"
- "binary_sensor.summer_season"
- "binary_sensor.autumn_season"
- "binary_sensor.winter_season"
- "sensor.openweathermap_forecast_precipitation_probability"
friendly_name: House
schedule:
- v: 18.5
rules:
- x: "IncludeSchedule(schedule_snippets['weather'])"
- x: "Add(-0.2) if is_on('input_boolean.heating_night_mode') else Next()"
- x: "Add(+0.5) if is_on('binary_sensor.livingroom_presence') else Next()"
- x: "Add(+0.1) if (float(state('sensor.livingroom_temperature_temperature')) < 17.8 and is_on('binary_sensor.livingroom_presence')) else Next()"
- x: "Postprocess(lambda result: min(round(result,1), 18.8))"
- { v: 16.0, start: "00:00", end: "07:00" }
- { v: 17.3, start: "07:00", end: "09:00" }
- { v: 17.5, start: "09:00", end: "11:00" }
- { v: 17.7, start: "11:00", end: "12:00" }
- { v: 17.8, start: "12:00", end: "13:00" }
- { v: 18.0, start: "13:00", end: "14:00" }
- { v: 18.1, start: "14:00", end: "15:00" }
- { v: 18.2, start: "15:00", end: "16:00" }
- { v: 18.3, start: "16:00", end: "18:00" }
- { v: 18.4, start: "18:00", end: "20:00" }
- { v: 18.3, start: "20:00", end: "20:30" }
- { v: 18.2, start: "20:30", end: "21:00" }
- { v: 18.1, start: "21:00", end: "22:00" }
- { v: 18.0, start: "22:00", end: "23:00" }
- { v: 17.9, start: "23:00", end: "00:00" }
upper_house:
actors:
climate.upper_house:
rescheduling_delay: 45
watched_entities:
- "input_boolean.home_state_home"
- "sensor.cc_outside_temperature"
- "sensor.cc_30_min_gust"
- "sensor.cc_rain_rate"
- "sensor.wind_dir_cardinal"
- "input_boolean.heating_eco_mode"
- "sensor.average_upper_bedrooms"
- "binary_sensor.bedroom_presence"
- "binary_sensor.spring_season"
- "binary_sensor.summer_season"
- "binary_sensor.autumn_season"
- "binary_sensor.winter_season"
- "sensor.openweathermap_forecast_precipitation_probability"
friendly_name: Upper House
schedule:
- v: 18
rules:
- x: "IncludeSchedule(schedule_snippets['weather'])"
- x: "Add(+0.3) if is_on('binary_sensor.bedroom_presence') else Next()"
- x: "Postprocess(lambda result: min(round(result,1), 18.2))"
- { v: "OFF", start: "08:00", end: "20:00" }
- { v: 17.6, start: "20:00", end: "21:00" }
- { v: 17.7, start: "21:00", end: "22:00" }
- { v: 17.8, start: "22:00", end: "23:00" }
- { v: 17.9, start: "23:00", end: "00:00" }
- { v: 18.0, start: "00:00", end: "01:00" }
- { v: 18.1, start: "01:00", end: "02:00" }
- { v: 18.0, start: "02:00", end: "03:00" }
- { v: 17.9, start: "03:00", end: "04:00" }
- { v: 17.8, start: "04:00", end: "05:00" }
- { v: 17.7, start: "05:00", end: "06:00" }
- { v: 17.6, start: "06:00", end: "07:00" }
- { v: 17.5, start: "07:00", end: "08:00" }
attic:
actors:
climate.attic_fans:
hvac_mode_on: cool
schedule:
- v: 17.5
start: "08:00"
end: "22:00"
- v: "OFF"
friendly_name: Attic