mirror of
				https://github.com/home-assistant/core.git
				synced 2025-11-04 00:19:31 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			252 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			252 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Support for Bryant Evolution HVAC systems."""
 | 
						|
 | 
						|
from datetime import timedelta
 | 
						|
import logging
 | 
						|
from typing import Any
 | 
						|
 | 
						|
from evolutionhttp import BryantEvolutionLocalClient
 | 
						|
 | 
						|
from homeassistant.components.climate import (
 | 
						|
    ClimateEntity,
 | 
						|
    ClimateEntityFeature,
 | 
						|
    HVACAction,
 | 
						|
    HVACMode,
 | 
						|
)
 | 
						|
from homeassistant.const import UnitOfTemperature
 | 
						|
from homeassistant.core import HomeAssistant
 | 
						|
from homeassistant.exceptions import HomeAssistantError
 | 
						|
from homeassistant.helpers.device_registry import DeviceInfo
 | 
						|
from homeassistant.helpers.entity import Entity
 | 
						|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
 | 
						|
 | 
						|
from . import BryantEvolutionConfigEntry, names
 | 
						|
from .const import CONF_SYSTEM_ZONE, DOMAIN
 | 
						|
 | 
						|
_LOGGER = logging.getLogger(__name__)
 | 
						|
 | 
						|
 | 
						|
SCAN_INTERVAL = timedelta(seconds=60)
 | 
						|
 | 
						|
 | 
						|
async def async_setup_entry(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    config_entry: BryantEvolutionConfigEntry,
 | 
						|
    async_add_entities: AddConfigEntryEntitiesCallback,
 | 
						|
) -> None:
 | 
						|
    """Set up a config entry."""
 | 
						|
 | 
						|
    # Add a climate entity for each system/zone.
 | 
						|
    sam_uid = names.sam_device_uid(config_entry)
 | 
						|
    entities: list[Entity] = []
 | 
						|
    for sz in config_entry.data[CONF_SYSTEM_ZONE]:
 | 
						|
        system_id = sz[0]
 | 
						|
        zone_id = sz[1]
 | 
						|
        client = config_entry.runtime_data.get(tuple(sz))
 | 
						|
        climate = BryantEvolutionClimate(
 | 
						|
            client,
 | 
						|
            system_id,
 | 
						|
            zone_id,
 | 
						|
            sam_uid,
 | 
						|
        )
 | 
						|
        entities.append(climate)
 | 
						|
    async_add_entities(entities, update_before_add=True)
 | 
						|
 | 
						|
 | 
						|
class BryantEvolutionClimate(ClimateEntity):
 | 
						|
    """ClimateEntity for Bryant Evolution HVAC systems.
 | 
						|
 | 
						|
    Design note: this class updates using polling. However, polling
 | 
						|
    is very slow (~1500 ms / parameter). To improve the user
 | 
						|
    experience on updates, we also locally update this instance and
 | 
						|
    call async_write_ha_state as well.
 | 
						|
    """
 | 
						|
 | 
						|
    _attr_has_entity_name = True
 | 
						|
    _attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
 | 
						|
    _attr_supported_features = (
 | 
						|
        ClimateEntityFeature.TARGET_TEMPERATURE
 | 
						|
        | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
 | 
						|
        | ClimateEntityFeature.FAN_MODE
 | 
						|
        | ClimateEntityFeature.TURN_ON
 | 
						|
        | ClimateEntityFeature.TURN_OFF
 | 
						|
    )
 | 
						|
    _attr_hvac_modes = [
 | 
						|
        HVACMode.HEAT,
 | 
						|
        HVACMode.COOL,
 | 
						|
        HVACMode.HEAT_COOL,
 | 
						|
        HVACMode.OFF,
 | 
						|
    ]
 | 
						|
    _attr_fan_modes = ["auto", "low", "med", "high"]
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        client: BryantEvolutionLocalClient,
 | 
						|
        system_id: int,
 | 
						|
        zone_id: int,
 | 
						|
        sam_uid: str,
 | 
						|
    ) -> None:
 | 
						|
        """Initialize an entity from parts."""
 | 
						|
        self._client = client
 | 
						|
        self._attr_name = None
 | 
						|
        self._attr_unique_id = names.zone_entity_uid(sam_uid, system_id, zone_id)
 | 
						|
        self._attr_device_info = DeviceInfo(
 | 
						|
            identifiers={(DOMAIN, self._attr_unique_id)},
 | 
						|
            manufacturer="Bryant",
 | 
						|
            via_device=(DOMAIN, names.system_device_uid(sam_uid, system_id)),
 | 
						|
            name=f"System {system_id} Zone {zone_id}",
 | 
						|
        )
 | 
						|
 | 
						|
    async def async_update(self) -> None:
 | 
						|
        """Update the entity state."""
 | 
						|
        self._attr_current_temperature = await self._client.read_current_temperature()
 | 
						|
        if (fan_mode := await self._client.read_fan_mode()) is not None:
 | 
						|
            self._attr_fan_mode = fan_mode.lower()
 | 
						|
        else:
 | 
						|
            self._attr_fan_mode = None
 | 
						|
        self._attr_target_temperature = None
 | 
						|
        self._attr_target_temperature_high = None
 | 
						|
        self._attr_target_temperature_low = None
 | 
						|
        self._attr_hvac_mode = await self._read_hvac_mode()
 | 
						|
 | 
						|
        # Set target_temperature or target_temperature_{high, low} based on mode.
 | 
						|
        match self._attr_hvac_mode:
 | 
						|
            case HVACMode.HEAT:
 | 
						|
                self._attr_target_temperature = (
 | 
						|
                    await self._client.read_heating_setpoint()
 | 
						|
                )
 | 
						|
            case HVACMode.COOL:
 | 
						|
                self._attr_target_temperature = (
 | 
						|
                    await self._client.read_cooling_setpoint()
 | 
						|
                )
 | 
						|
            case HVACMode.HEAT_COOL:
 | 
						|
                self._attr_target_temperature_high = (
 | 
						|
                    await self._client.read_cooling_setpoint()
 | 
						|
                )
 | 
						|
                self._attr_target_temperature_low = (
 | 
						|
                    await self._client.read_heating_setpoint()
 | 
						|
                )
 | 
						|
            case HVACMode.OFF:
 | 
						|
                pass
 | 
						|
            case _:
 | 
						|
                _LOGGER.error("Unknown HVAC mode %s", self._attr_hvac_mode)
 | 
						|
 | 
						|
        # Note: depends on current temperature and target temperature low read
 | 
						|
        # above.
 | 
						|
        self._attr_hvac_action = await self._read_hvac_action()
 | 
						|
 | 
						|
    async def _read_hvac_mode(self) -> HVACMode:
 | 
						|
        mode_and_active = await self._client.read_hvac_mode()
 | 
						|
        if not mode_and_active:
 | 
						|
            raise HomeAssistantError(
 | 
						|
                translation_domain=DOMAIN, translation_key="failed_to_read_hvac_mode"
 | 
						|
            )
 | 
						|
        mode = mode_and_active[0]
 | 
						|
        mode_enum = {
 | 
						|
            "HEAT": HVACMode.HEAT,
 | 
						|
            "COOL": HVACMode.COOL,
 | 
						|
            "AUTO": HVACMode.HEAT_COOL,
 | 
						|
            "OFF": HVACMode.OFF,
 | 
						|
        }.get(mode.upper())
 | 
						|
        if mode_enum is None:
 | 
						|
            raise HomeAssistantError(
 | 
						|
                translation_domain=DOMAIN,
 | 
						|
                translation_key="failed_to_parse_hvac_mode",
 | 
						|
                translation_placeholders={"mode": mode},
 | 
						|
            )
 | 
						|
        return mode_enum
 | 
						|
 | 
						|
    async def _read_hvac_action(self) -> HVACAction:
 | 
						|
        """Return the current running hvac operation."""
 | 
						|
        mode_and_active = await self._client.read_hvac_mode()
 | 
						|
        if not mode_and_active:
 | 
						|
            raise HomeAssistantError(
 | 
						|
                translation_domain=DOMAIN, translation_key="failed_to_read_hvac_action"
 | 
						|
            )
 | 
						|
        mode, is_active = mode_and_active
 | 
						|
        if not is_active:
 | 
						|
            return HVACAction.OFF
 | 
						|
        match mode.upper():
 | 
						|
            case "HEAT":
 | 
						|
                return HVACAction.HEATING
 | 
						|
            case "COOL":
 | 
						|
                return HVACAction.COOLING
 | 
						|
            case "OFF":
 | 
						|
                return HVACAction.OFF
 | 
						|
            case "AUTO":
 | 
						|
                # In AUTO, we need to figure out what the actual action is
 | 
						|
                # based on the setpoints.
 | 
						|
                if (
 | 
						|
                    self.current_temperature is not None
 | 
						|
                    and self.target_temperature_low is not None
 | 
						|
                ):
 | 
						|
                    if self.current_temperature > self.target_temperature_low:
 | 
						|
                        # If the system is on and the current temperature is
 | 
						|
                        # higher than the point at which heating would activate,
 | 
						|
                        # then we must be cooling.
 | 
						|
                        return HVACAction.COOLING
 | 
						|
                    return HVACAction.HEATING
 | 
						|
        raise HomeAssistantError(
 | 
						|
            translation_domain=DOMAIN,
 | 
						|
            translation_key="failed_to_parse_hvac_mode",
 | 
						|
            translation_placeholders={
 | 
						|
                "mode_and_active": mode_and_active,
 | 
						|
                "current_temperature": str(self.current_temperature),
 | 
						|
                "target_temperature_low": str(self.target_temperature_low),
 | 
						|
            },
 | 
						|
        )
 | 
						|
 | 
						|
    async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
 | 
						|
        """Set new target hvac mode."""
 | 
						|
        if hvac_mode == HVACMode.HEAT_COOL:
 | 
						|
            hvac_mode = HVACMode.AUTO
 | 
						|
        if not await self._client.set_hvac_mode(hvac_mode):
 | 
						|
            raise HomeAssistantError(
 | 
						|
                translation_domain=DOMAIN, translation_key="failed_to_set_hvac_mode"
 | 
						|
            )
 | 
						|
        self._attr_hvac_mode = hvac_mode
 | 
						|
        self._async_write_ha_state()
 | 
						|
 | 
						|
    async def async_set_temperature(self, **kwargs: Any) -> None:
 | 
						|
        """Set new target temperature."""
 | 
						|
        if kwargs.get("target_temp_high"):
 | 
						|
            temp = int(kwargs["target_temp_high"])
 | 
						|
            if not await self._client.set_cooling_setpoint(temp):
 | 
						|
                raise HomeAssistantError(
 | 
						|
                    translation_domain=DOMAIN, translation_key="failed_to_set_clsp"
 | 
						|
                )
 | 
						|
            self._attr_target_temperature_high = temp
 | 
						|
 | 
						|
        if kwargs.get("target_temp_low"):
 | 
						|
            temp = int(kwargs["target_temp_low"])
 | 
						|
            if not await self._client.set_heating_setpoint(temp):
 | 
						|
                raise HomeAssistantError(
 | 
						|
                    translation_domain=DOMAIN, translation_key="failed_to_set_htsp"
 | 
						|
                )
 | 
						|
            self._attr_target_temperature_low = temp
 | 
						|
 | 
						|
        if kwargs.get("temperature"):
 | 
						|
            temp = int(kwargs["temperature"])
 | 
						|
            fn = (
 | 
						|
                self._client.set_heating_setpoint
 | 
						|
                if self.hvac_mode == HVACMode.HEAT
 | 
						|
                else self._client.set_cooling_setpoint
 | 
						|
            )
 | 
						|
            if not await fn(temp):
 | 
						|
                raise HomeAssistantError(
 | 
						|
                    translation_domain=DOMAIN, translation_key="failed_to_set_temp"
 | 
						|
                )
 | 
						|
            self._attr_target_temperature = temp
 | 
						|
 | 
						|
        # If we get here, we must have changed something unless HA allowed an
 | 
						|
        # invalid service call (without any recognized kwarg).
 | 
						|
        self._async_write_ha_state()
 | 
						|
 | 
						|
    async def async_set_fan_mode(self, fan_mode: str) -> None:
 | 
						|
        """Set new target fan mode."""
 | 
						|
        if not await self._client.set_fan_mode(fan_mode):
 | 
						|
            raise HomeAssistantError(
 | 
						|
                translation_domain=DOMAIN, translation_key="failed_to_set_fan_mode"
 | 
						|
            )
 | 
						|
        self._attr_fan_mode = fan_mode.lower()
 | 
						|
        self.async_write_ha_state()
 |