core/homeassistant/components/screenlogic/climate.py

221 lines
7.8 KiB
Python

"""Support for a ScreenLogic heating device."""
from dataclasses import dataclass
import logging
from typing import Any
from screenlogicpy.const.common import UNIT, ScreenLogicCommunicationError
from screenlogicpy.const.data import ATTR, DEVICE, VALUE
from screenlogicpy.const.msg import CODE
from screenlogicpy.device_const.heat import HEAT_MODE
from screenlogicpy.device_const.system import EQUIPMENT_FLAG
from homeassistant.components.climate import (
ATTR_PRESET_MODE,
ClimateEntity,
ClimateEntityDescription,
ClimateEntityFeature,
HVACAction,
HVACMode,
)
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from .entity import ScreenLogicPushEntity, ScreenLogicPushEntityDescription
from .types import ScreenLogicConfigEntry
_LOGGER = logging.getLogger(__name__)
SUPPORTED_MODES = [HVACMode.OFF, HVACMode.HEAT]
SUPPORTED_PRESETS = [
HEAT_MODE.SOLAR,
HEAT_MODE.SOLAR_PREFERRED,
HEAT_MODE.HEATER,
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ScreenLogicConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entry."""
coordinator = config_entry.runtime_data
gateway = coordinator.gateway
async_add_entities(
ScreenLogicClimate(
coordinator,
ScreenLogicClimateDescription(
subscription_code=CODE.STATUS_CHANGED,
data_root=(DEVICE.BODY,),
key=body_index,
),
)
for body_index in gateway.get_data(DEVICE.BODY)
)
@dataclass(frozen=True, kw_only=True)
class ScreenLogicClimateDescription(
ClimateEntityDescription, ScreenLogicPushEntityDescription
):
"""Describes a ScreenLogic climate entity."""
class ScreenLogicClimate(ScreenLogicPushEntity, ClimateEntity, RestoreEntity):
"""Represents a ScreenLogic climate entity."""
entity_description: ScreenLogicClimateDescription
_attr_hvac_modes = SUPPORTED_MODES
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.PRESET_MODE
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TURN_ON
)
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, coordinator, entity_description) -> None:
"""Initialize a ScreenLogic climate entity."""
super().__init__(coordinator, entity_description)
self._configured_heat_modes = []
# Is solar listed as available equipment?
if EQUIPMENT_FLAG.SOLAR in self.gateway.equipment_flags:
self._configured_heat_modes.extend(
[HEAT_MODE.SOLAR, HEAT_MODE.SOLAR_PREFERRED]
)
self._configured_heat_modes.append(HEAT_MODE.HEATER)
self._attr_preset_modes = [
HEAT_MODE(mode_num).title for mode_num in self._configured_heat_modes
]
self._attr_min_temp = self.entity_data[ATTR.MIN_SETPOINT]
self._attr_max_temp = self.entity_data[ATTR.MAX_SETPOINT]
self._attr_name = self.entity_data[VALUE.HEAT_STATE][ATTR.NAME]
self._last_preset = None
@property
def current_temperature(self) -> float:
"""Return water temperature."""
return self.entity_data[VALUE.LAST_TEMPERATURE][ATTR.VALUE]
@property
def target_temperature(self) -> float:
"""Target temperature."""
return self.entity_data[VALUE.HEAT_SETPOINT][ATTR.VALUE]
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
if self.gateway.temperature_unit == UNIT.CELSIUS:
return UnitOfTemperature.CELSIUS
return UnitOfTemperature.FAHRENHEIT
@property
def hvac_mode(self) -> HVACMode:
"""Return the current hvac mode."""
if self.entity_data[VALUE.HEAT_MODE][ATTR.VALUE] > 0:
return HVACMode.HEAT
return HVACMode.OFF
@property
def hvac_action(self) -> HVACAction:
"""Return the current action of the heater."""
if self.entity_data[VALUE.HEAT_STATE][ATTR.VALUE] > 0:
return HVACAction.HEATING
if self.hvac_mode == HVACMode.HEAT:
return HVACAction.IDLE
return HVACAction.OFF
@property
def preset_mode(self) -> str:
"""Return current/last preset mode."""
if self.hvac_mode == HVACMode.OFF:
return HEAT_MODE(self._last_preset).title
return HEAT_MODE(self.entity_data[VALUE.HEAT_MODE][ATTR.VALUE]).title
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Change the setpoint of the heater."""
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}")
try:
await self.gateway.async_set_heat_temp(
int(self._data_key), int(temperature)
)
except ScreenLogicCommunicationError as sle:
raise HomeAssistantError(
f"Failed to set_temperature {temperature} on body"
f" {self.entity_data[ATTR.BODY_TYPE][ATTR.VALUE]}:"
f" {sle.msg}"
) from sle
_LOGGER.debug("Set temperature for body %s to %s", self._data_key, temperature)
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the operation mode."""
if hvac_mode == HVACMode.OFF:
mode = HEAT_MODE.OFF
else:
mode = HEAT_MODE.parse(self.preset_mode)
try:
await self.gateway.async_set_heat_mode(int(self._data_key), int(mode.value))
except ScreenLogicCommunicationError as sle:
raise HomeAssistantError(
f"Failed to set_hvac_mode {mode.name} on body"
f" {self.entity_data[ATTR.BODY_TYPE][ATTR.VALUE]}:"
f" {sle.msg}"
) from sle
_LOGGER.debug("Set hvac_mode on body %s to %s", self._data_key, mode.name)
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode."""
mode = HEAT_MODE.parse(preset_mode)
_LOGGER.debug("Setting last_preset to %s", mode.name)
self._last_preset = mode.value
if self.hvac_mode == HVACMode.OFF:
return
try:
await self.gateway.async_set_heat_mode(int(self._data_key), int(mode.value))
except ScreenLogicCommunicationError as sle:
raise HomeAssistantError(
f"Failed to set_preset_mode {mode.name} on body"
f" {self.entity_data[ATTR.BODY_TYPE][ATTR.VALUE]}:"
f" {sle.msg}"
) from sle
_LOGGER.debug("Set preset_mode on body %s to %s", self._data_key, mode.name)
async def async_added_to_hass(self) -> None:
"""Run when entity is about to be added."""
await super().async_added_to_hass()
_LOGGER.debug("Startup last preset is %s", self._last_preset)
if self._last_preset is not None:
return
prev_state = await self.async_get_last_state()
if (
prev_state is not None
and prev_state.attributes.get(ATTR_PRESET_MODE) is not None
):
mode = HEAT_MODE.parse(prev_state.attributes.get(ATTR_PRESET_MODE))
_LOGGER.debug(
"Startup setting last_preset to %s from prev_state",
mode.name,
)
self._last_preset = mode.value
else:
mode = HEAT_MODE.parse(self._configured_heat_modes[0])
_LOGGER.debug(
"Startup setting last_preset to default (%s)",
mode.name,
)
self._last_preset = mode.value