core/homeassistant/components/aprilaire/climate.py

303 lines
8.9 KiB
Python

"""The Aprilaire climate component."""
from __future__ import annotations
from typing import Any
from pyaprilaire.const import Attribute
from homeassistant.components.climate import (
FAN_AUTO,
FAN_ON,
PRESET_AWAY,
PRESET_NONE,
ClimateEntity,
ClimateEntityFeature,
HVACAction,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PRECISION_HALVES, PRECISION_WHOLE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
DOMAIN,
FAN_CIRCULATE,
PRESET_PERMANENT_HOLD,
PRESET_TEMPORARY_HOLD,
PRESET_VACATION,
)
from .coordinator import AprilaireCoordinator
from .entity import BaseAprilaireEntity
HVAC_MODE_MAP = {
1: HVACMode.OFF,
2: HVACMode.HEAT,
3: HVACMode.COOL,
4: HVACMode.HEAT,
5: HVACMode.AUTO,
}
HVAC_MODES_MAP = {
1: [HVACMode.OFF, HVACMode.HEAT],
2: [HVACMode.OFF, HVACMode.COOL],
3: [HVACMode.OFF, HVACMode.HEAT, HVACMode.COOL],
4: [HVACMode.OFF, HVACMode.HEAT, HVACMode.COOL],
5: [HVACMode.OFF, HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO],
6: [HVACMode.OFF, HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO],
}
PRESET_MODE_MAP = {
1: PRESET_TEMPORARY_HOLD,
2: PRESET_PERMANENT_HOLD,
3: PRESET_AWAY,
4: PRESET_VACATION,
}
FAN_MODE_MAP = {
1: FAN_ON,
2: FAN_AUTO,
3: FAN_CIRCULATE,
}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add climates for passed config_entry in HA."""
coordinator: AprilaireCoordinator = hass.data[DOMAIN][config_entry.unique_id]
async_add_entities([AprilaireClimate(coordinator, config_entry.unique_id)])
class AprilaireClimate(BaseAprilaireEntity, ClimateEntity):
"""Climate entity for Aprilaire."""
_attr_fan_modes = [FAN_AUTO, FAN_ON, FAN_CIRCULATE]
_attr_min_humidity = 10
_attr_max_humidity = 50
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_translation_key = "thermostat"
@property
def precision(self) -> float:
"""Get the precision based on the unit."""
return (
PRECISION_HALVES
if self.hass.config.units.temperature_unit == UnitOfTemperature.CELSIUS
else PRECISION_WHOLE
)
@property
def supported_features(self) -> ClimateEntityFeature:
"""Get supported features."""
features = 0
if self.coordinator.data.get(Attribute.MODE) == 5:
features = features | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
else:
features = features | ClimateEntityFeature.TARGET_TEMPERATURE
if self.coordinator.data.get(Attribute.HUMIDIFICATION_AVAILABLE) == 2:
features = features | ClimateEntityFeature.TARGET_HUMIDITY
features = features | ClimateEntityFeature.PRESET_MODE
features = features | ClimateEntityFeature.FAN_MODE
return features
@property
def current_humidity(self) -> int | None:
"""Get current humidity."""
return self.coordinator.data.get(
Attribute.INDOOR_HUMIDITY_CONTROLLING_SENSOR_VALUE
)
@property
def target_humidity(self) -> int | None:
"""Get current target humidity."""
return self.coordinator.data.get(Attribute.HUMIDIFICATION_SETPOINT)
@property
def hvac_mode(self) -> HVACMode | None:
"""Get HVAC mode."""
if mode := self.coordinator.data.get(Attribute.MODE):
if hvac_mode := HVAC_MODE_MAP.get(mode):
return hvac_mode
return None
@property
def hvac_modes(self) -> list[HVACMode]:
"""Get supported HVAC modes."""
if modes := self.coordinator.data.get(Attribute.THERMOSTAT_MODES):
if thermostat_modes := HVAC_MODES_MAP.get(modes):
return thermostat_modes
return []
@property
def hvac_action(self) -> HVACAction | None:
"""Get the current HVAC action."""
if self.coordinator.data.get(Attribute.HEATING_EQUIPMENT_STATUS, 0):
return HVACAction.HEATING
if self.coordinator.data.get(Attribute.COOLING_EQUIPMENT_STATUS, 0):
return HVACAction.COOLING
return HVACAction.IDLE
@property
def current_temperature(self) -> float | None:
"""Get current temperature."""
return self.coordinator.data.get(
Attribute.INDOOR_TEMPERATURE_CONTROLLING_SENSOR_VALUE
)
@property
def target_temperature(self) -> float | None:
"""Get the target temperature."""
hvac_mode = self.hvac_mode
if hvac_mode == HVACMode.COOL:
return self.target_temperature_high
if hvac_mode == HVACMode.HEAT:
return self.target_temperature_low
return None
@property
def target_temperature_step(self) -> float | None:
"""Get the step for the target temperature based on the unit."""
return (
0.5
if self.hass.config.units.temperature_unit == UnitOfTemperature.CELSIUS
else 1
)
@property
def target_temperature_high(self) -> float | None:
"""Get cool setpoint."""
return self.coordinator.data.get(Attribute.COOL_SETPOINT)
@property
def target_temperature_low(self) -> float | None:
"""Get heat setpoint."""
return self.coordinator.data.get(Attribute.HEAT_SETPOINT)
@property
def preset_mode(self) -> str | None:
"""Get the current preset mode."""
if hold := self.coordinator.data.get(Attribute.HOLD):
if preset_mode := PRESET_MODE_MAP.get(hold):
return preset_mode
return PRESET_NONE
@property
def preset_modes(self) -> list[str] | None:
"""Get the supported preset modes."""
presets = [PRESET_NONE, PRESET_VACATION]
if self.coordinator.data.get(Attribute.AWAY_AVAILABLE) == 1:
presets.append(PRESET_AWAY)
hold = self.coordinator.data.get(Attribute.HOLD, 0)
if hold == 1:
presets.append(PRESET_TEMPORARY_HOLD)
elif hold == 2:
presets.append(PRESET_PERMANENT_HOLD)
return presets
@property
def fan_mode(self) -> str | None:
"""Get fan mode."""
if mode := self.coordinator.data.get(Attribute.FAN_MODE):
if fan_mode := FAN_MODE_MAP.get(mode):
return fan_mode
return None
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
cool_setpoint = 0
heat_setpoint = 0
if temperature := kwargs.get("temperature"):
if self.coordinator.data.get(Attribute.MODE) == 3:
cool_setpoint = temperature
else:
heat_setpoint = temperature
else:
if target_temp_low := kwargs.get("target_temp_low"):
heat_setpoint = target_temp_low
if target_temp_high := kwargs.get("target_temp_high"):
cool_setpoint = target_temp_high
if cool_setpoint == 0 and heat_setpoint == 0:
return
await self.coordinator.client.update_setpoint(cool_setpoint, heat_setpoint)
await self.coordinator.client.read_control()
async def async_set_humidity(self, humidity: int) -> None:
"""Set the target humidification setpoint."""
await self.coordinator.client.set_humidification_setpoint(humidity)
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set the fan mode."""
try:
fan_mode_value_index = list(FAN_MODE_MAP.values()).index(fan_mode)
except ValueError as exc:
raise ValueError(f"Unsupported fan mode {fan_mode}") from exc
fan_mode_value = list(FAN_MODE_MAP.keys())[fan_mode_value_index]
await self.coordinator.client.update_fan_mode(fan_mode_value)
await self.coordinator.client.read_control()
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the HVAC mode."""
try:
mode_value_index = list(HVAC_MODE_MAP.values()).index(hvac_mode)
except ValueError as exc:
raise ValueError(f"Unsupported HVAC mode {hvac_mode}") from exc
mode_value = list(HVAC_MODE_MAP.keys())[mode_value_index]
await self.coordinator.client.update_mode(mode_value)
await self.coordinator.client.read_control()
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode."""
if preset_mode == PRESET_AWAY:
await self.coordinator.client.set_hold(3)
elif preset_mode == PRESET_VACATION:
await self.coordinator.client.set_hold(4)
elif preset_mode == PRESET_NONE:
await self.coordinator.client.set_hold(0)
else:
raise ValueError(f"Unsupported preset mode {preset_mode}")
await self.coordinator.client.read_scheduling()