| 
 | 1 | +"""EHEIM Digital climate."""  | 
 | 2 | + | 
 | 3 | +from typing import Any  | 
 | 4 | + | 
 | 5 | +from eheimdigital.heater import EheimDigitalHeater  | 
 | 6 | +from eheimdigital.types import EheimDigitalClientError, HeaterMode, HeaterUnit  | 
 | 7 | + | 
 | 8 | +from homeassistant.components.climate import (  | 
 | 9 | + PRESET_NONE,  | 
 | 10 | + ClimateEntity,  | 
 | 11 | + ClimateEntityFeature,  | 
 | 12 | + HVACAction,  | 
 | 13 | + HVACMode,  | 
 | 14 | +)  | 
 | 15 | +from homeassistant.const import (  | 
 | 16 | + ATTR_TEMPERATURE,  | 
 | 17 | + PRECISION_HALVES,  | 
 | 18 | + PRECISION_TENTHS,  | 
 | 19 | + UnitOfTemperature,  | 
 | 20 | +)  | 
 | 21 | +from homeassistant.core import HomeAssistant  | 
 | 22 | +from homeassistant.exceptions import HomeAssistantError  | 
 | 23 | +from homeassistant.helpers.entity_platform import AddEntitiesCallback  | 
 | 24 | + | 
 | 25 | +from . import EheimDigitalConfigEntry  | 
 | 26 | +from .const import HEATER_BIO_MODE, HEATER_PRESET_TO_HEATER_MODE, HEATER_SMART_MODE  | 
 | 27 | +from .coordinator import EheimDigitalUpdateCoordinator  | 
 | 28 | +from .entity import EheimDigitalEntity  | 
 | 29 | + | 
 | 30 | +# Coordinator is used to centralize the data updates  | 
 | 31 | +PARALLEL_UPDATES = 0  | 
 | 32 | + | 
 | 33 | + | 
 | 34 | +async def async_setup_entry(  | 
 | 35 | + hass: HomeAssistant,  | 
 | 36 | + entry: EheimDigitalConfigEntry,  | 
 | 37 | + async_add_entities: AddEntitiesCallback,  | 
 | 38 | +) -> None:  | 
 | 39 | + """Set up the callbacks for the coordinator so climate entities can be added as devices are found."""  | 
 | 40 | + coordinator = entry.runtime_data  | 
 | 41 | + | 
 | 42 | + async def async_setup_device_entities(device_address: str) -> None:  | 
 | 43 | + """Set up the light entities for a device."""  | 
 | 44 | + device = coordinator.hub.devices[device_address]  | 
 | 45 | + | 
 | 46 | + if isinstance(device, EheimDigitalHeater):  | 
 | 47 | + async_add_entities([EheimDigitalHeaterClimate(coordinator, device)])  | 
 | 48 | + | 
 | 49 | + coordinator.add_platform_callback(async_setup_device_entities)  | 
 | 50 | + | 
 | 51 | + for device_address in entry.runtime_data.hub.devices:  | 
 | 52 | + await async_setup_device_entities(device_address)  | 
 | 53 | + | 
 | 54 | + | 
 | 55 | +class EheimDigitalHeaterClimate(EheimDigitalEntity[EheimDigitalHeater], ClimateEntity):  | 
 | 56 | + """Represent an EHEIM Digital heater."""  | 
 | 57 | + | 
 | 58 | + _attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO]  | 
 | 59 | + _attr_hvac_mode = HVACMode.OFF  | 
 | 60 | + _attr_precision = PRECISION_TENTHS  | 
 | 61 | + _attr_supported_features = (  | 
 | 62 | + ClimateEntityFeature.TARGET_TEMPERATURE  | 
 | 63 | + | ClimateEntityFeature.TURN_ON  | 
 | 64 | + | ClimateEntityFeature.TURN_OFF  | 
 | 65 | + | ClimateEntityFeature.PRESET_MODE  | 
 | 66 | + )  | 
 | 67 | + _attr_target_temperature_step = PRECISION_HALVES  | 
 | 68 | + _attr_preset_modes = [PRESET_NONE, HEATER_BIO_MODE, HEATER_SMART_MODE]  | 
 | 69 | + _attr_temperature_unit = UnitOfTemperature.CELSIUS  | 
 | 70 | + _attr_preset_mode = PRESET_NONE  | 
 | 71 | + _attr_translation_key = "heater"  | 
 | 72 | + | 
 | 73 | + def __init__(  | 
 | 74 | + self, coordinator: EheimDigitalUpdateCoordinator, device: EheimDigitalHeater  | 
 | 75 | + ) -> None:  | 
 | 76 | + """Initialize an EHEIM Digital thermocontrol climate entity."""  | 
 | 77 | + super().__init__(coordinator, device)  | 
 | 78 | + self._attr_unique_id = self._device_address  | 
 | 79 | + self._async_update_attrs()  | 
 | 80 | + | 
 | 81 | + async def async_set_preset_mode(self, preset_mode: str) -> None:  | 
 | 82 | + """Set the preset mode."""  | 
 | 83 | + try:  | 
 | 84 | + if preset_mode in HEATER_PRESET_TO_HEATER_MODE:  | 
 | 85 | + await self._device.set_operation_mode(  | 
 | 86 | + HEATER_PRESET_TO_HEATER_MODE[preset_mode]  | 
 | 87 | + )  | 
 | 88 | + except EheimDigitalClientError as err:  | 
 | 89 | + raise HomeAssistantError from err  | 
 | 90 | + | 
 | 91 | + async def async_set_temperature(self, **kwargs: Any) -> None:  | 
 | 92 | + """Set a new temperature."""  | 
 | 93 | + try:  | 
 | 94 | + if ATTR_TEMPERATURE in kwargs:  | 
 | 95 | + await self._device.set_target_temperature(kwargs[ATTR_TEMPERATURE])  | 
 | 96 | + except EheimDigitalClientError as err:  | 
 | 97 | + raise HomeAssistantError from err  | 
 | 98 | + | 
 | 99 | + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:  | 
 | 100 | + """Set the heating mode."""  | 
 | 101 | + try:  | 
 | 102 | + match hvac_mode:  | 
 | 103 | + case HVACMode.OFF:  | 
 | 104 | + await self._device.set_active(active=False)  | 
 | 105 | + case HVACMode.AUTO:  | 
 | 106 | + await self._device.set_active(active=True)  | 
 | 107 | + except EheimDigitalClientError as err:  | 
 | 108 | + raise HomeAssistantError from err  | 
 | 109 | + | 
 | 110 | + def _async_update_attrs(self) -> None:  | 
 | 111 | + if self._device.temperature_unit == HeaterUnit.CELSIUS:  | 
 | 112 | + self._attr_min_temp = 18  | 
 | 113 | + self._attr_max_temp = 32  | 
 | 114 | + self._attr_temperature_unit = UnitOfTemperature.CELSIUS  | 
 | 115 | + elif self._device.temperature_unit == HeaterUnit.FAHRENHEIT:  | 
 | 116 | + self._attr_min_temp = 64  | 
 | 117 | + self._attr_max_temp = 90  | 
 | 118 | + self._attr_temperature_unit = UnitOfTemperature.FAHRENHEIT  | 
 | 119 | + | 
 | 120 | + self._attr_current_temperature = self._device.current_temperature  | 
 | 121 | + self._attr_target_temperature = self._device.target_temperature  | 
 | 122 | + | 
 | 123 | + if self._device.is_heating:  | 
 | 124 | + self._attr_hvac_action = HVACAction.HEATING  | 
 | 125 | + self._attr_hvac_mode = HVACMode.AUTO  | 
 | 126 | + elif self._device.is_active:  | 
 | 127 | + self._attr_hvac_action = HVACAction.IDLE  | 
 | 128 | + self._attr_hvac_mode = HVACMode.AUTO  | 
 | 129 | + else:  | 
 | 130 | + self._attr_hvac_action = HVACAction.OFF  | 
 | 131 | + self._attr_hvac_mode = HVACMode.OFF  | 
 | 132 | + | 
 | 133 | + match self._device.operation_mode:  | 
 | 134 | + case HeaterMode.MANUAL:  | 
 | 135 | + self._attr_preset_mode = PRESET_NONE  | 
 | 136 | + case HeaterMode.BIO:  | 
 | 137 | + self._attr_preset_mode = HEATER_BIO_MODE  | 
 | 138 | + case HeaterMode.SMART:  | 
 | 139 | + self._attr_preset_mode = HEATER_SMART_MODE  | 
0 commit comments