303 lines
8.9 KiB
Python
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()
|