Skip to content

Instantly share code, notes, and snippets.

@adwuk
Last active November 13, 2022 21:09
Show Gist options
  • Save adwuk/e843789bdb6ab4bdcce371c9152aecad to your computer and use it in GitHub Desktop.
Save adwuk/e843789bdb6ab4bdcce371c9152aecad to your computer and use it in GitHub Desktop.
Multi-zone heating and hot water control using Home Assistant pyscript custom component
# declarations
import datetime
from collections import defaultdict
thermostat_states = defaultdict(dict)
thermostat_triggers = []
# globals - alter to suit
heating_switch = "switch.boiler_room_boiler_switch_1"
heating_schedule = "schedule.central_heating_schedule"
heating_override = "input_boolean.central_heating_override"
hot_water_switch = "switch.boiler_room_boiler_switch_2"
hot_water_schedule = "schedule.hot_water_schedule"
hot_water_override = "input_boolean.hot_water_override"
co_alarm_status = "binary_sensor.utility_room_sensor_co_co_alarm"
away_status = "input_boolean.away"
override_duration = 3600
heating_off_months = ["May", "Jun", "Jul", "Aug"]
# turn off both hot water and heating overrides if away is on
@time_trigger("startup")
@state_trigger(f"{away_status} == 'on'")
def override_reset():
if (state.get(hot_water_override) != "off"):
homeassistant.turn_off(entity_id=hot_water_override)
if (state.get(heating_override) != "off"):
homeassistant.turn_off(entity_id=heating_override)
# turn off the heating override after duration expires or away is on
@state_trigger(f"{heating_override} == 'on'")
def heating_override_reset():
task.unique("heating_override_reset")
task.sleep(override_duration)
if (state.get(heating_override) != "off"):
homeassistant.turn_off(entity_id=heating_override)
# turn off the hot water override after duration expires or away is on
@state_trigger(f"{hot_water_override} == 'on'")
def hot_water_override_reset():
task.unique("hot_water_override_reset")
task.sleep(override_duration)
if (state.get(hot_water_override) != "off"):
homeassistant.turn_off(entity_id=hot_water_override)
# control the heating
@state_trigger(heating_schedule)
@state_trigger(heating_override)
@state_trigger(co_alarm_status)
@state_trigger("sensor.heating_zones")
def heating_control():
month = datetime.datetime.now().strftime("%b")
if (month in heating_off_months):
if (state.get(heating_switch) != "off"):
switch.turn_off(entity_id=heating_switch)
else:
if ((state.get(heating_schedule) == "on" or state.get(heating_override) == "on") and
state.get(co_alarm_status) == "off" and state.get("sensor.heating_zones") == "heat"):
if (state.get(heating_switch) != "on"):
switch.turn_on(entity_id=heating_switch)
else:
if (state.get(heating_switch) != "off"):
switch.turn_off(entity_id=heating_switch)
# control the hot water
@time_trigger("startup")
@state_trigger(hot_water_schedule)
@state_trigger(hot_water_override)
@state_trigger(co_alarm_status)
@state_trigger(away_status)
def hot_water_control():
if (state.get(away_status) == "on"):
if (state.get(hot_water_switch) != "off"):
switch.turn_off(entity_id=hot_water_switch)
else:
if ((state.get(hot_water_schedule) == "on" or state.get(hot_water_override) == "on") and
state.get(co_alarm_status) == "off"):
if (state.get(hot_water_switch) != "on"):
switch.turn_on(entity_id=hot_water_switch)
else:
if (state.get(hot_water_switch) != "off"):
switch.turn_off(entity_id=hot_water_switch)
# perform a zone check and update state sensor
def zone_update():
house_state = "off"
try:
x = state.get("sensor.heating_zones")
except:
state.set("sensor.heating_zones", "off")
for zone in thermostat_states:
zone_state = "off"
for thermostat in thermostat_states[zone]:
if thermostat_states[zone][thermostat] == "heat":
zone_state = "heat"
house_state = "heat"
break
try:
if (state.getattr(f"sensor.heating_zones.{zone}") != zone_state):
state.setattr(f"sensor.heating_zones.{zone}", zone_state)
except:
state.setattr(f"sensor.heating_zones.{zone}", zone_state)
if (state.get("sensor.heating_zones") != house_state):
state.set("sensor.heating_zones", house_state)
# trigger factory
def thermostat_trigger_factory(zone, thermostat, tempsensor=None, accuracy=0.5):
# check if there is an optional tempsensor, if not assume the thermostat has one
if (tempsensor == None):
tempsensor = f"{thermostat}.current_temperature"
# function called at startup and when the thermostat/temp sensor changes
@time_trigger("startup")
@state_trigger(f"{thermostat}")
@state_trigger(f"{thermostat}.temperature")
@state_trigger(f"{tempsensor}")
def thermostat_trigger():
# get the current states
mode = state.get(f"{thermostat}")
temp = float(state.get(f"{tempsensor}"))
setpoint = float(state.get(f"{thermostat}.temperature"))
heating = state.get(heating_switch)
low_temp = setpoint - accuracy*2.0
high_temp = setpoint - accuracy
# check the thermostat is on 'heat' and the temp is below the setpoint
# save the state of the thermostat[zone][thermostat]
if (mode == "heat"):
if (temp > high_temp):
tstate = "off"
elif (temp > low_temp and heating == "off"):
tstate = "off"
else:
tstate = "heat"
else:
tstate = "off"
# set the state and report it
thermostat_states[f"{zone}"][f"{thermostat}"] = tstate
#log.info(f"{thermostat}={temp},mode={mode},low={low_temp},high={high_temp},state={tstate}")
# now update all the zones and house states
zone_update()
# keep a pointer to the trigger function in a global
thermostat_triggers.append(thermostat_trigger)
# create the thermostat triggers
# factory(zone_name/string, thermostat_entity/string, temp_sensor_entity/string, temp_accuracy/float)
thermostat_trigger_factory("kitchen", "climate.kitchen_radiator_cooking_thermostat")
thermostat_trigger_factory("kitchen", "climate.kitchen_radiator_cupboard_thermostat")
thermostat_trigger_factory("kitchen", "climate.kitchen_radiator_tv_thermostat")
thermostat_trigger_factory("sitting", "climate.sitting_room_radiator_large_thermostat")
thermostat_trigger_factory("sitting", "climate.sitting_room_radiator_small_thermostat")
thermostat_trigger_factory("dining", "climate.dining_room_radiator_thermostat")
thermostat_trigger_factory("master", "climate.master_bedroom_radiator_thermostat")
thermostat_trigger_factory("master", "climate.master_bathroom_radiator_thermostat")
thermostat_trigger_factory("ali", "climate.ali_bedroom_radiator_thermostat")
thermostat_trigger_factory("cat", "climate.cat_bedroom_radiator_thermostat")
thermostat_trigger_factory("bathroom","climate.bathroom_radiator_thermostat")
thermostat_trigger_factory("guest", "climate.guest_bathroom_radiator_thermostat")
thermostat_trigger_factory("guest", "climate.guest_bedroom_radiator_door_thermostat")
thermostat_trigger_factory("guest", "climate.guest_bedroom_radiator_gable_thermostat")
thermostat_trigger_factory("hall", "climate.hall_radiator_climate", "sensor.hall_multisensor_temperature_air")
thermostat_trigger_factory("landing", "climate.landing_radiator_stairs_climate", "sensor.landing_multisensor_temperature_air")
thermostat_trigger_factory("landing", "climate.landing_radiator_gym_climate", "sensor.landing_multisensor_temperature_air")
thermostat_trigger_factory("utility", "climate.utility_room_radiator_climate", "sensor.utility_room_multisensor_temperature_air")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment