core/homeassistant/components/lyric/climate.py

309 lines
9.8 KiB
Python

"""Support for Honeywell Lyric climate platform."""
from __future__ import annotations
import logging
from time import localtime, strftime, time
from aiolyric.objects.device import LyricDevice
from aiolyric.objects.location import LyricLocation
import voluptuous as vol
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL,
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
CURRENT_HVAC_OFF,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import LyricDeviceEntity
from .const import (
DOMAIN,
LYRIC_EXCEPTIONS,
PRESET_HOLD_UNTIL,
PRESET_NO_HOLD,
PRESET_PERMANENT_HOLD,
PRESET_TEMPORARY_HOLD,
PRESET_VACATION_HOLD,
)
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
LYRIC_HVAC_ACTION_OFF = "EquipmentOff"
LYRIC_HVAC_ACTION_HEAT = "Heat"
LYRIC_HVAC_ACTION_COOL = "Cool"
LYRIC_HVAC_MODE_OFF = "Off"
LYRIC_HVAC_MODE_HEAT = "Heat"
LYRIC_HVAC_MODE_COOL = "Cool"
LYRIC_HVAC_MODE_HEAT_COOL = "Auto"
LYRIC_HVAC_MODES = {
HVAC_MODE_OFF: LYRIC_HVAC_MODE_OFF,
HVAC_MODE_HEAT: LYRIC_HVAC_MODE_HEAT,
HVAC_MODE_COOL: LYRIC_HVAC_MODE_COOL,
HVAC_MODE_HEAT_COOL: LYRIC_HVAC_MODE_HEAT_COOL,
}
HVAC_MODES = {
LYRIC_HVAC_MODE_OFF: HVAC_MODE_OFF,
LYRIC_HVAC_MODE_HEAT: HVAC_MODE_HEAT,
LYRIC_HVAC_MODE_COOL: HVAC_MODE_COOL,
LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL,
}
HVAC_ACTIONS = {
LYRIC_HVAC_ACTION_OFF: CURRENT_HVAC_OFF,
LYRIC_HVAC_ACTION_HEAT: CURRENT_HVAC_HEAT,
LYRIC_HVAC_ACTION_COOL: CURRENT_HVAC_COOL,
}
SERVICE_HOLD_TIME = "set_hold_time"
ATTR_TIME_PERIOD = "time_period"
SCHEMA_HOLD_TIME = {
vol.Required(ATTR_TIME_PERIOD, default="01:00:00"): vol.All(
cv.time_period,
cv.positive_timedelta,
lambda td: strftime("%H:%M:%S", localtime(time() + td.total_seconds())),
)
}
async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
) -> None:
"""Set up the Honeywell Lyric climate platform based on a config entry."""
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
entities = []
for location in coordinator.data.locations:
for device in location.devices:
entities.append(
LyricClimate(
coordinator, location, device, hass.config.units.temperature_unit
)
)
async_add_entities(entities, True)
platform = entity_platform.current_platform.get()
platform.async_register_entity_service(
SERVICE_HOLD_TIME,
SCHEMA_HOLD_TIME,
"async_set_hold_time",
)
class LyricClimate(LyricDeviceEntity, ClimateEntity):
"""Defines a Honeywell Lyric climate entity."""
def __init__(
self,
coordinator: DataUpdateCoordinator,
location: LyricLocation,
device: LyricDevice,
temperature_unit: str,
) -> None:
"""Initialize Honeywell Lyric climate entity."""
self._temperature_unit = temperature_unit
# Setup supported hvac modes
self._hvac_modes = [HVAC_MODE_OFF]
# Add supported lyric thermostat features
if LYRIC_HVAC_MODE_HEAT in device.allowedModes:
self._hvac_modes.append(HVAC_MODE_HEAT)
if LYRIC_HVAC_MODE_COOL in device.allowedModes:
self._hvac_modes.append(HVAC_MODE_COOL)
if (
LYRIC_HVAC_MODE_HEAT in device.allowedModes
and LYRIC_HVAC_MODE_COOL in device.allowedModes
):
self._hvac_modes.append(HVAC_MODE_HEAT_COOL)
super().__init__(
coordinator,
location,
device,
f"{device.macID}_thermostat",
device.name,
None,
)
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return self._temperature_unit
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self.device.indoorTemperature
@property
def hvac_action(self) -> str:
"""Return the current hvac action."""
action = HVAC_ACTIONS.get(self.device.operationStatus.mode, None)
if action == CURRENT_HVAC_OFF and self.hvac_mode != HVAC_MODE_OFF:
action = CURRENT_HVAC_IDLE
return action
@property
def hvac_mode(self) -> str:
"""Return the hvac mode."""
return HVAC_MODES[self.device.changeableValues.mode]
@property
def hvac_modes(self) -> list[str]:
"""List of available hvac modes."""
return self._hvac_modes
@property
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""
device = self.device
if not device.hasDualSetpointStatus:
return device.changeableValues.heatSetpoint
return None
@property
def target_temperature_low(self) -> float | None:
"""Return the upper bound temperature we try to reach."""
device = self.device
if device.hasDualSetpointStatus:
return device.changeableValues.coolSetpoint
return None
@property
def target_temperature_high(self) -> float | None:
"""Return the upper bound temperature we try to reach."""
device = self.device
if device.hasDualSetpointStatus:
return device.changeableValues.heatSetpoint
return None
@property
def preset_mode(self) -> str | None:
"""Return current preset mode."""
return self.device.changeableValues.thermostatSetpointStatus
@property
def preset_modes(self) -> list[str] | None:
"""Return preset modes."""
return [
PRESET_NO_HOLD,
PRESET_HOLD_UNTIL,
PRESET_PERMANENT_HOLD,
PRESET_TEMPORARY_HOLD,
PRESET_VACATION_HOLD,
]
@property
def min_temp(self) -> float:
"""Identify min_temp in Lyric API or defaults if not available."""
device = self.device
if LYRIC_HVAC_MODE_COOL in device.allowedModes:
return device.minCoolSetpoint
return device.minHeatSetpoint
@property
def max_temp(self) -> float:
"""Identify max_temp in Lyric API or defaults if not available."""
device = self.device
if LYRIC_HVAC_MODE_HEAT in device.allowedModes:
return device.maxHeatSetpoint
return device.maxCoolSetpoint
async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
device = self.device
if device.hasDualSetpointStatus:
if target_temp_low is None or target_temp_high is None:
raise HomeAssistantError(
"Could not find target_temp_low and/or target_temp_high in arguments"
)
_LOGGER.debug("Set temperature: %s - %s", target_temp_low, target_temp_high)
try:
await self._update_thermostat(
self.location,
device,
coolSetpoint=target_temp_low,
heatSetpoint=target_temp_high,
)
except LYRIC_EXCEPTIONS as exception:
_LOGGER.error(exception)
else:
temp = kwargs.get(ATTR_TEMPERATURE)
_LOGGER.debug("Set temperature: %s", temp)
try:
await self._update_thermostat(self.location, device, heatSetpoint=temp)
except LYRIC_EXCEPTIONS as exception:
_LOGGER.error(exception)
await self.coordinator.async_refresh()
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set hvac mode."""
_LOGGER.debug("Set hvac mode: %s", hvac_mode)
try:
await self._update_thermostat(
self.location, self.device, mode=LYRIC_HVAC_MODES[hvac_mode]
)
except LYRIC_EXCEPTIONS as exception:
_LOGGER.error(exception)
await self.coordinator.async_refresh()
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set preset (PermanentHold, HoldUntil, NoHold, VacationHold) mode."""
_LOGGER.debug("Set preset mode: %s", preset_mode)
try:
await self._update_thermostat(
self.location, self.device, thermostatSetpointStatus=preset_mode
)
except LYRIC_EXCEPTIONS as exception:
_LOGGER.error(exception)
await self.coordinator.async_refresh()
async def async_set_hold_time(self, time_period: str) -> None:
"""Set the time to hold until."""
_LOGGER.debug("set_hold_time: %s", time_period)
try:
await self._update_thermostat(
self.location,
self.device,
thermostatSetpointStatus=PRESET_HOLD_UNTIL,
nextPeriodTime=time_period,
)
except LYRIC_EXCEPTIONS as exception:
_LOGGER.error(exception)
await self.coordinator.async_refresh()