"""AirTouch 4 component to control of AirTouch 4 Climate Devices.""" from __future__ import annotations import logging from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( FAN_AUTO, FAN_DIFFUSE, FAN_FOCUS, FAN_HIGH, FAN_LOW, FAN_MEDIUM, HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY, HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE AT_TO_HA_STATE = { "Heat": HVAC_MODE_HEAT, "Cool": HVAC_MODE_COOL, "AutoHeat": HVAC_MODE_AUTO, # airtouch reports either autoheat or autocool "AutoCool": HVAC_MODE_AUTO, "Auto": HVAC_MODE_AUTO, "Dry": HVAC_MODE_DRY, "Fan": HVAC_MODE_FAN_ONLY, } HA_STATE_TO_AT = { HVAC_MODE_HEAT: "Heat", HVAC_MODE_COOL: "Cool", HVAC_MODE_AUTO: "Auto", HVAC_MODE_DRY: "Dry", HVAC_MODE_FAN_ONLY: "Fan", HVAC_MODE_OFF: "Off", } AT_TO_HA_FAN_SPEED = { "Quiet": FAN_DIFFUSE, "Low": FAN_LOW, "Medium": FAN_MEDIUM, "High": FAN_HIGH, "Powerful": FAN_FOCUS, "Auto": FAN_AUTO, "Turbo": "turbo", } AT_GROUP_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY] HA_FAN_SPEED_TO_AT = {value: key for key, value in AT_TO_HA_FAN_SPEED.items()} _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Airtouch 4.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] info = coordinator.data entities: list[ClimateEntity] = [ AirtouchGroup(coordinator, group["group_number"], info) for group in info["groups"] ] entities.extend( AirtouchAC(coordinator, ac["ac_number"], info) for ac in info["acs"] ) _LOGGER.debug(" Found entities %s", entities) async_add_entities(entities) class AirtouchAC(CoordinatorEntity, ClimateEntity): """Representation of an AirTouch 4 ac.""" _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE _attr_temperature_unit = TEMP_CELSIUS def __init__(self, coordinator, ac_number, info): """Initialize the climate device.""" super().__init__(coordinator) self._ac_number = ac_number self._airtouch = coordinator.airtouch self._info = info self._unit = self._airtouch.GetAcs()[self._ac_number] @callback def _handle_coordinator_update(self): self._unit = self._airtouch.GetAcs()[self._ac_number] return super()._handle_coordinator_update() @property def device_info(self) -> DeviceInfo: """Return device info for this device.""" return DeviceInfo( identifiers={(DOMAIN, self.unique_id)}, name=self.name, manufacturer="Airtouch", model="Airtouch 4", ) @property def unique_id(self): """Return unique ID for this device.""" return f"ac_{self._ac_number}" @property def current_temperature(self): """Return the current temperature.""" return self._unit.Temperature @property def name(self): """Return the name of the climate device.""" return f"AC {self._ac_number}" @property def fan_mode(self): """Return fan mode of the AC this group belongs to.""" return AT_TO_HA_FAN_SPEED[self._airtouch.acs[self._ac_number].AcFanSpeed] @property def fan_modes(self): """Return the list of available fan modes.""" airtouch_fan_speeds = self._airtouch.GetSupportedFanSpeedsForAc(self._ac_number) return [AT_TO_HA_FAN_SPEED[speed] for speed in airtouch_fan_speeds] @property def hvac_mode(self): """Return hvac target hvac state.""" is_off = self._unit.PowerState == "Off" if is_off: return HVAC_MODE_OFF return AT_TO_HA_STATE[self._airtouch.acs[self._ac_number].AcMode] @property def hvac_modes(self): """Return the list of available operation modes.""" airtouch_modes = self._airtouch.GetSupportedCoolingModesForAc(self._ac_number) modes = [AT_TO_HA_STATE[mode] for mode in airtouch_modes] modes.append(HVAC_MODE_OFF) return modes async def async_set_hvac_mode(self, hvac_mode): """Set new operation mode.""" if hvac_mode not in HA_STATE_TO_AT: raise ValueError(f"Unsupported HVAC mode: {hvac_mode}") if hvac_mode == HVAC_MODE_OFF: return await self.async_turn_off() await self._airtouch.SetCoolingModeForAc( self._ac_number, HA_STATE_TO_AT[hvac_mode] ) # in case it isn't already, unless the HVAC mode was off, then the ac should be on await self.async_turn_on() self._unit = self._airtouch.GetAcs()[self._ac_number] _LOGGER.debug("Setting operation mode of %s to %s", self._ac_number, hvac_mode) self.async_write_ha_state() async def async_set_fan_mode(self, fan_mode): """Set new fan mode.""" if fan_mode not in self.fan_modes: raise ValueError(f"Unsupported fan mode: {fan_mode}") _LOGGER.debug("Setting fan mode of %s to %s", self._ac_number, fan_mode) await self._airtouch.SetFanSpeedForAc( self._ac_number, HA_FAN_SPEED_TO_AT[fan_mode] ) self._unit = self._airtouch.GetAcs()[self._ac_number] self.async_write_ha_state() async def async_turn_on(self): """Turn on.""" _LOGGER.debug("Turning %s on", self.unique_id) # in case ac is not on. Airtouch turns itself off if no groups are turned on # (even if groups turned back on) await self._airtouch.TurnAcOn(self._ac_number) async def async_turn_off(self): """Turn off.""" _LOGGER.debug("Turning %s off", self.unique_id) await self._airtouch.TurnAcOff(self._ac_number) self.async_write_ha_state() class AirtouchGroup(CoordinatorEntity, ClimateEntity): """Representation of an AirTouch 4 group.""" _attr_supported_features = SUPPORT_TARGET_TEMPERATURE _attr_temperature_unit = TEMP_CELSIUS _attr_hvac_modes = AT_GROUP_MODES def __init__(self, coordinator, group_number, info): """Initialize the climate device.""" super().__init__(coordinator) self._group_number = group_number self._airtouch = coordinator.airtouch self._info = info self._unit = self._airtouch.GetGroupByGroupNumber(self._group_number) @callback def _handle_coordinator_update(self): self._unit = self._airtouch.GetGroupByGroupNumber(self._group_number) return super()._handle_coordinator_update() @property def device_info(self) -> DeviceInfo: """Return device info for this device.""" return DeviceInfo( identifiers={(DOMAIN, self.unique_id)}, manufacturer="Airtouch", model="Airtouch 4", name=self.name, ) @property def unique_id(self): """Return unique ID for this device.""" return self._group_number @property def min_temp(self): """Return Minimum Temperature for AC of this group.""" return self._airtouch.acs[self._unit.BelongsToAc].MinSetpoint @property def max_temp(self): """Return Max Temperature for AC of this group.""" return self._airtouch.acs[self._unit.BelongsToAc].MaxSetpoint @property def name(self): """Return the name of the climate device.""" return self._unit.GroupName @property def current_temperature(self): """Return the current temperature.""" return self._unit.Temperature @property def target_temperature(self): """Return the temperature we are trying to reach.""" return self._unit.TargetSetpoint @property def hvac_mode(self): """Return hvac target hvac state.""" # there are other power states that aren't 'on' but still count as on (eg. 'Turbo') is_off = self._unit.PowerState == "Off" if is_off: return HVAC_MODE_OFF return HVAC_MODE_FAN_ONLY async def async_set_hvac_mode(self, hvac_mode): """Set new operation mode.""" if hvac_mode not in HA_STATE_TO_AT: raise ValueError(f"Unsupported HVAC mode: {hvac_mode}") if hvac_mode == HVAC_MODE_OFF: return await self.async_turn_off() if self.hvac_mode == HVAC_MODE_OFF: await self.async_turn_on() self._unit = self._airtouch.GetGroups()[self._group_number] _LOGGER.debug( "Setting operation mode of %s to %s", self._group_number, hvac_mode ) self.async_write_ha_state() @property def fan_mode(self): """Return fan mode of the AC this group belongs to.""" return AT_TO_HA_FAN_SPEED[self._airtouch.acs[self._unit.BelongsToAc].AcFanSpeed] @property def fan_modes(self): """Return the list of available fan modes.""" airtouch_fan_speeds = self._airtouch.GetSupportedFanSpeedsByGroup( self._group_number ) return [AT_TO_HA_FAN_SPEED[speed] for speed in airtouch_fan_speeds] async def async_set_temperature(self, **kwargs): """Set new target temperatures.""" temp = kwargs.get(ATTR_TEMPERATURE) _LOGGER.debug("Setting temp of %s to %s", self._group_number, str(temp)) self._unit = await self._airtouch.SetGroupToTemperature( self._group_number, int(temp) ) self.async_write_ha_state() async def async_set_fan_mode(self, fan_mode): """Set new fan mode.""" if fan_mode not in self.fan_modes: raise ValueError(f"Unsupported fan mode: {fan_mode}") _LOGGER.debug("Setting fan mode of %s to %s", self._group_number, fan_mode) self._unit = await self._airtouch.SetFanSpeedByGroup( self._group_number, HA_FAN_SPEED_TO_AT[fan_mode] ) self.async_write_ha_state() async def async_turn_on(self): """Turn on.""" _LOGGER.debug("Turning %s on", self.unique_id) await self._airtouch.TurnGroupOn(self._group_number) # in case ac is not on. Airtouch turns itself off if no groups are turned on # (even if groups turned back on) await self._airtouch.TurnAcOn( self._airtouch.GetGroupByGroupNumber(self._group_number).BelongsToAc ) # this might cause the ac object to be wrong, so force the shared data # store to update await self.coordinator.async_request_refresh() self.async_write_ha_state() async def async_turn_off(self): """Turn off.""" _LOGGER.debug("Turning %s off", self.unique_id) await self._airtouch.TurnGroupOff(self._group_number) # this will cause the ac object to be wrong # (ac turns off automatically if no groups are running) # so force the shared data store to update await self.coordinator.async_request_refresh() self.async_write_ha_state()