Climate Control Examples¶
These examples show HVAC and temperature management patterns.
Basic Thermostat Control¶
Control a thermostat based on schedule:
from dataclasses import dataclass
import datetime
from domovoy.applications import AppBase, AppConfigBase
from domovoy.plugins.hass.types import EntityID
@dataclass
class SchedulePoint:
time: datetime.time
temperature: float
@dataclass
class ThermostatConfig(AppConfigBase):
thermostat: EntityID
schedule: list[SchedulePoint]
class ScheduledThermostat(AppBase[ThermostatConfig]):
async def initialize(self) -> None:
for point in self.config.schedule:
self.callbacks.run_daily(
self.set_temperature,
point.time,
target_temp=point.temperature,
)
async def set_temperature(self, target_temp: float) -> None:
await self.hass.services.climate.set_temperature(
entity_id=self.config.thermostat,
temperature=target_temp,
)
# Usage
register_app(
app_class=ScheduledThermostat,
app_name="main_thermostat",
config=ThermostatConfig(
thermostat="climate.main_floor", # type: ignore
schedule=[
SchedulePoint(datetime.time(6, 0), 72), # Wake
SchedulePoint(datetime.time(8, 0), 68), # Away
SchedulePoint(datetime.time(17, 0), 72), # Home
SchedulePoint(datetime.time(22, 0), 68), # Sleep
],
),
)
Smart HVAC with Priority Sensors¶
Use the best available temperature sensor:
from dataclasses import dataclass
from domovoy.plugins.hass.domains import SensorEntity, BinarySensorEntity
@dataclass
class SmartHVACConfig(AppConfigBase):
thermostat: EntityID
default_sensor: SensorEntity
presence_priority: dict[BinarySensorEntity, SensorEntity]
target_temperature: float
hysteresis: float = 0.5
class SmartHVAC(AppBase[SmartHVACConfig]):
async def initialize(self) -> None:
# Create sensor to show which temp sensor is active
self.active_sensor = await self.servents.create_sensor(
servent_id="active_temp_sensor",
name="Active Temperature Sensor",
)
# Create control state sensor
self.state_sensor = await self.servents.create_sensor(
servent_id="hvac_state",
name="HVAC Control State",
)
# Monitor presence sensors
for presence in self.config.presence_priority.keys():
self.callbacks.listen_state(
presence,
self.update_priority,
immediate=True,
)
# Periodic control loop
self.callbacks.run_every(
Interval(minutes=5),
self.control_loop,
"now",
)
async def update_priority(self) -> None:
"""Select best temperature sensor based on presence."""
for presence, temp_sensor in self.config.presence_priority.items():
presence_state = self.hass.get_state(presence)
sensor_state = self.hass.get_state(temp_sensor)
if presence_state == "on" and sensor_state != "unavailable":
await self.active_sensor.set_to(str(temp_sensor))
return
await self.active_sensor.set_to(str(self.config.default_sensor))
async def control_loop(self) -> None:
"""Main HVAC control logic."""
# Get active sensor
active = self.active_sensor.get_state()
current_temp = self.utils.parse_float(
self.hass.get_state(active) # type: ignore
)
if current_temp is None:
return
target = self.config.target_temperature
diff = current_temp - target
# Hysteresis-based control
if diff < -self.config.hysteresis:
mode = "heat"
elif diff > self.config.hysteresis:
mode = "cool"
else:
mode = "off"
await self.state_sensor.set_to(
mode,
{
"current_temp": current_temp,
"target_temp": target,
"differential": diff,
},
)
await self.hass.services.climate.set_hvac_mode(
entity_id=self.config.thermostat,
hvac_mode=mode,
)
Pause HVAC When Windows Open¶
Stop heating/cooling when doors or windows are open:
@dataclass
class PauseConfig(AppConfigBase):
thermostat: EntityID
pause_sensors: list[EntityID] # Doors/windows
pause_delay: Interval # How long open before pausing
resume_delay: Interval # How long closed before resuming
class PausableHVAC(AppBase[PauseConfig]):
saved_mode: str | None = None
async def initialize(self) -> None:
for sensor in self.config.pause_sensors:
self.callbacks.listen_state(sensor, self.check_pause)
async def check_pause(self) -> None:
# Check if any sensor is open
any_open = any(
self.hass.get_state(s) == "on"
for s in self.config.pause_sensors
)
if any_open:
await self.pause_hvac()
else:
await self.resume_hvac()
async def pause_hvac(self) -> None:
current_mode = self.hass.get_state(self.config.thermostat)
if current_mode != "off" and self.saved_mode is None:
self.saved_mode = current_mode
self.log.info("Pausing HVAC, window/door open")
await self.hass.services.climate.set_hvac_mode(
entity_id=self.config.thermostat,
hvac_mode="off",
)
async def resume_hvac(self) -> None:
if self.saved_mode:
self.log.info("Resuming HVAC mode: {mode}", mode=self.saved_mode)
await self.hass.services.climate.set_hvac_mode(
entity_id=self.config.thermostat,
hvac_mode=self.saved_mode,
)
self.saved_mode = None
Mode Selection with ServEnts¶
Create a mode selector for HVAC control:
from typing import Literal
HVACMode = Literal["Auto", "Cool", "Heat", "Off", "Schedule"]
@dataclass
class HVACModeConfig(AppConfigBase):
thermostat: EntityID
modes: list[HVACMode]
class HVACModeController(AppBase[HVACModeConfig]):
async def initialize(self) -> None:
self.mode_select = await self.servents.create_select(
servent_id="hvac_mode",
name="HVAC Mode",
options=list(self.config.modes),
default_state="Auto",
)
self.callbacks.listen_state(
self.mode_select.get_entity_id(),
self.on_mode_change,
immediate=True,
)
async def on_mode_change(self, new) -> None:
self.log.info("HVAC mode changed to: {mode}", mode=new)
if new == "Off":
await self.hass.services.climate.set_hvac_mode(
entity_id=self.config.thermostat,
hvac_mode="off",
)
elif new == "Schedule":
# Enable schedule-based control
pass
else:
await self.hass.services.climate.set_hvac_mode(
entity_id=self.config.thermostat,
hvac_mode=new.lower(),
)
Freeze Protection¶
Automatically protect against freezing:
@dataclass
class FreezeProtectionConfig(AppConfigBase):
outdoor_temp_sensor: SensorEntity
freeze_threshold: float = 35.0 # °F
protection_temp: float = 55.0
class FreezeProtection(AppBase[FreezeProtectionConfig]):
protection_active: bool = False
async def initialize(self) -> None:
self.callbacks.listen_state(
self.config.outdoor_temp_sensor,
self.check_freeze,
immediate=True,
)
async def check_freeze(self, new) -> None:
temp = self.utils.parse_float(new)
if temp is None:
return
if temp <= self.config.freeze_threshold and not self.protection_active:
await self.activate_protection()
elif temp > self.config.freeze_threshold + 5 and self.protection_active:
await self.deactivate_protection()
async def activate_protection(self) -> None:
self.protection_active = True
self.log.warning("Freeze protection activated!")
await self.hass.services.climate.set_temperature(
entity_id=self.config.thermostat,
temperature=self.config.protection_temp,
)
async def deactivate_protection(self) -> None:
self.protection_active = False
self.log.info("Freeze protection deactivated")
Key Concepts¶
Hysteresis: Prevent rapid on/off cycling
Priority sensors: Use best available data source
Pause conditions: Stop HVAC when inappropriate
Mode selection: User-controllable operation modes
Freeze protection: Safety overrides
ServEnts: Create control UI in Home Assistant