Files
core/homeassistant/components/screenlogic/climate.py
T
2026-07-08 16:23:48 +02:00

233 lines
8.0 KiB
Python

"""Support for a ScreenLogic heating device."""
from dataclasses import dataclass
import logging
from typing import Any, override
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 (
ClimateEntity,
ClimateEntityDescription,
ClimateEntityFeature,
ClimateEntityStateAttribute,
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 AddConfigEntryEntitiesCallback
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: AddConfigEntryEntitiesCallback,
) -> 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,
translation_key=f"body_{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
)
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).name.lower() 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._last_preset = None
@property
@override
def current_temperature(self) -> float:
"""Return water temperature."""
return self.entity_data[VALUE.LAST_TEMPERATURE][ATTR.VALUE]
@property
@override
def target_temperature(self) -> float:
"""Target temperature."""
return self.entity_data[VALUE.HEAT_SETPOINT][ATTR.VALUE]
@property
@override
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
if self.gateway.temperature_unit == UNIT.CELSIUS:
return UnitOfTemperature.CELSIUS
return UnitOfTemperature.FAHRENHEIT
@property
@override
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
@override
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
@override
def preset_mode(self) -> str:
"""Return current/last preset mode."""
if self.hvac_mode == HVACMode.OFF:
return HEAT_MODE(self._last_preset).name.lower()
return HEAT_MODE(self.entity_data[VALUE.HEAT_MODE][ATTR.VALUE]).name.lower()
@override
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)
@override
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)
@override
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)
@override
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(ClimateEntityStateAttribute.PRESET_MODE)
is not None
):
mode = HEAT_MODE.parse(
prev_state.attributes.get(ClimateEntityStateAttribute.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