"""Climate-сущность FGLair: режим, уставка, fan, swing, preset ECO/BOOST.""" from __future__ import annotations from typing import Any, Optional from homeassistant.components.climate import ( ATTR_TEMPERATURE, ClimateEntity, ClimateEntityFeature, HVACMode, ) from homeassistant.const import UnitOfTemperature from homeassistant.core import callback from pyfglair import Prop from pyfglair.templates import prop_info from .const import ( CAP_FAN, CAP_HVAC, CAP_SWING_HORIZONTAL, CAP_SWING_VERTICAL, FAN_MODE_TO_VALUE, FAN_VALUE_TO_MODE, HVAC_MODE_TO_VALUE, HVAC_VALUE_TO_MODE, OPERATION_MODE_OFF, OPERATION_MODE_ON, PRESET_BOOST, PRESET_CAP, PRESET_ECO, PRESET_PROP, SWING_HORIZONTAL_MODES, SWING_MODES, ) from .coordinator import FglairCoordinator, FglairRuntime from .entity import FglairEntity async def async_setup_entry(hass, entry, async_add_entities) -> None: runtime: FglairRuntime = entry.runtime_data async_add_entities([FglairClimate(runtime.coordinator)]) class FglairClimate(FglairEntity, ClimateEntity): _attr_translation_key = "climate" _attr_name = None _attr_temperature_unit = UnitOfTemperature.CELSIUS _enable_turn_on_off_backwards_compat = False def __init__(self, coordinator: FglairCoordinator) -> None: super().__init__(coordinator, "climate", "climate") self._last_hvac_mode: Optional[HVACMode] = None # -- свойства шаблона -------------------------------------------------- def _in_template(self, prop: Prop) -> bool: return prop_info(self.coordinator.client.template, prop) is not None def _hvac_modes(self) -> list[HVACMode]: modes: list[HVACMode] = [HVACMode.OFF] for mode, bit in CAP_HVAC.items(): if self.supports_capability(bit): modes.append(mode) return modes def _fan_modes(self) -> Optional[list[str]]: if not self._in_template(Prop.FAN_SPEED): return None return [ mode for mode, bit in CAP_FAN.items() if self.supports_capability(bit) ] def _preset_modes(self) -> Optional[list[str]]: presets = [ preset for preset, bit in PRESET_CAP.items() if self._in_template(PRESET_PROP[preset]) and self.supports_capability(bit) ] return presets or None def _vertical_swing_supported(self) -> bool: return self._in_template(Prop.AF_VERTICAL_SWING) and ( self.supports_capability(CAP_SWING_VERTICAL) ) def _horizontal_swing_supported(self) -> bool: return self._in_template(Prop.AF_HORIZONTAL_SWING) and ( self.supports_capability(CAP_SWING_HORIZONTAL) ) # -- ClimateEntity ---------------------------------------------------- @property def supported_features(self) -> ClimateEntityFeature: features = ClimateEntityFeature(0) if self._in_template(Prop.ADJUST_TEMPERATURE): features |= ClimateEntityFeature.TARGET_TEMPERATURE if self._fan_modes(): features |= ClimateEntityFeature.FAN_MODE if self._preset_modes(): features |= ClimateEntityFeature.PRESET_MODE if self._vertical_swing_supported(): features |= ClimateEntityFeature.SWING_MODE if self._horizontal_swing_supported(): features |= ClimateEntityFeature.SWING_HORIZONTAL_MODE return features @property def hvac_modes(self) -> list[HVACMode]: return self._hvac_modes() @property def hvac_mode(self) -> Optional[HVACMode]: value = self.raw_value(Prop.OPERATION_MODE) if value is None: return None if value == OPERATION_MODE_OFF: return HVACMode.OFF if value == OPERATION_MODE_ON: if self._last_hvac_mode is not None: return self._last_hvac_mode available = [mode for mode in self._hvac_modes() if mode != HVACMode.OFF] return available[0] if available else HVACMode.AUTO return HVAC_VALUE_TO_MODE.get(value) @property def target_temperature(self) -> Optional[float]: display = self.display_value(Prop.ADJUST_TEMPERATURE) return display / 10 if display is not None else None @property def target_temperature_step(self) -> Optional[float]: return self.coordinator.conversions.step_tenths / 10 @property def min_temp(self) -> float: return self.coordinator.conversions.temperature_range()[0] / 10 @property def max_temp(self) -> float: return self.coordinator.conversions.temperature_range()[1] / 10 @property def current_temperature(self) -> Optional[float]: display = self.display_value(Prop.DISPLAY_TEMPERATURE) return display / 10 if display is not None else None @property def fan_mode(self) -> Optional[str]: value = self.raw_value(Prop.FAN_SPEED) return FAN_VALUE_TO_MODE.get(value) if value is not None else None @property def fan_modes(self) -> Optional[list[str]]: return self._fan_modes() @property def preset_mode(self) -> Optional[str]: if self.raw_value(PRESET_PROP[PRESET_ECO]) == 1: return PRESET_ECO if self.raw_value(PRESET_PROP[PRESET_BOOST]) == 1: return PRESET_BOOST return None @property def preset_modes(self) -> Optional[list[str]]: return self._preset_modes() @property def swing_mode(self) -> Optional[str]: if not self._vertical_swing_supported(): return None value = self.raw_value(Prop.AF_VERTICAL_SWING) return None if value is None else ("on" if value else "off") @property def swing_modes(self) -> Optional[list[str]]: return SWING_MODES if self._vertical_swing_supported() else None @property def swing_horizontal_mode(self) -> Optional[str]: if not self._horizontal_swing_supported(): return None value = self.raw_value(Prop.AF_HORIZONTAL_SWING) return None if value is None else ("on" if value else "off") @property def swing_horizontal_modes(self) -> Optional[list[str]]: return ( SWING_HORIZONTAL_MODES if self._horizontal_swing_supported() else None ) # -- команды ---------------------------------------------------------- async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: if hvac_mode == HVACMode.OFF: await self.coordinator.async_write( [(Prop.OPERATION_MODE, OPERATION_MODE_OFF)] ) return value = HVAC_MODE_TO_VALUE.get(hvac_mode) if value is None: return self._last_hvac_mode = hvac_mode await self.coordinator.async_write([(Prop.OPERATION_MODE, value)]) async def async_turn_on(self) -> None: await self.coordinator.async_write( [(Prop.OPERATION_MODE, OPERATION_MODE_ON)] ) async def async_set_temperature(self, **kwargs: Any) -> None: temperature = kwargs.get(ATTR_TEMPERATURE) if temperature is None: return display = int(round(temperature * 10)) display = self.coordinator.conversions.clamp( Prop.ADJUST_TEMPERATURE, display ) api_value = self.coordinator.conversions.from_display( Prop.ADJUST_TEMPERATURE, display ) await self.coordinator.async_write( [(Prop.ADJUST_TEMPERATURE, api_value)] ) async def async_set_fan_mode(self, fan_mode: str) -> None: value = FAN_MODE_TO_VALUE.get(fan_mode) if value is None: return await self.coordinator.async_write([(Prop.FAN_SPEED, value)]) async def async_set_preset_mode(self, preset_mode: str) -> None: eco = 1 if preset_mode == PRESET_ECO else 0 boost = 1 if preset_mode == PRESET_BOOST else 0 updates = [(prop, value) for prop, value in ( (PRESET_PROP[PRESET_ECO], eco), (PRESET_PROP[PRESET_BOOST], boost), ) if self._in_template(prop)] await self.coordinator.async_write(updates) async def async_set_swing_mode(self, swing_mode: str) -> None: await self.coordinator.async_write( [(Prop.AF_VERTICAL_SWING, 1 if swing_mode == "on" else 0)] ) async def async_set_swing_horizontal_mode( self, swing_mode: str ) -> None: await self.coordinator.async_write( [(Prop.AF_HORIZONTAL_SWING, 1 if swing_mode == "on" else 0)] ) @callback def _handle_coordinator_update(self) -> None: value = self.raw_value(Prop.OPERATION_MODE) mode = HVAC_VALUE_TO_MODE.get(value) if value is not None else None if mode is not None: self._last_hvac_mode = mode super()._handle_coordinator_update()