text: """Support for climate devices through the SmartThings cloud API."""
  from __future__ import annotations
  
  import asyncio
  from collections.abc import Iterable, Sequence
  import logging
  
  from pysmartthings import Attribute, Capability
  
  from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity
  from homeassistant.components.climate.const import (
      ATTR_HVAC_MODE,
      ATTR_TARGET_TEMP_HIGH,
      ATTR_TARGET_TEMP_LOW,
      CURRENT_HVAC_COOL,
      CURRENT_HVAC_FAN,
      CURRENT_HVAC_HEAT,
      CURRENT_HVAC_IDLE,
      HVAC_MODE_AUTO,
      HVAC_MODE_COOL,
      HVAC_MODE_DRY,
      HVAC_MODE_FAN_ONLY,
      HVAC_MODE_HEAT,
      HVAC_MODE_HEAT_COOL,
      HVAC_MODE_OFF,
      SUPPORT_FAN_MODE,
      SUPPORT_TARGET_TEMPERATURE,
      SUPPORT_TARGET_TEMPERATURE_RANGE,
  )
  from homeassistant.config_entries import ConfigEntry
  from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
  from homeassistant.core import HomeAssistant
  from homeassistant.helpers.entity_platform import AddEntitiesCallback
  
  from . import SmartThingsEntity
  from .const import DATA_BROKERS, DOMAIN
  
  ATTR_OPERATION_STATE = "operation_state"
  MODE_TO_STATE = {
      "auto": HVAC_MODE_HEAT_COOL,
      "cool": HVAC_MODE_COOL,
      "eco": HVAC_MODE_AUTO,
      "rush hour": HVAC_MODE_AUTO,
      "emergency heat": HVAC_MODE_HEAT,
      "heat": HVAC_MODE_HEAT,
      "off": HVAC_MODE_OFF,
  }
  STATE_TO_MODE = {
      HVAC_MODE_HEAT_COOL: "auto",
      HVAC_MODE_COOL: "cool",
      HVAC_MODE_HEAT: "heat",
      HVAC_MODE_OFF: "off",
  }
  
  OPERATING_STATE_TO_ACTION = {
      "cooling": CURRENT_HVAC_COOL,
      "fan only": CURRENT_HVAC_FAN,
      "heating": CURRENT_HVAC_HEAT,
      "idle": CURRENT_HVAC_IDLE,
      "pending cool": CURRENT_HVAC_COOL,
      "pending heat": CURRENT_HVAC_HEAT,
      "vent economizer": CURRENT_HVAC_FAN,
  }
  
  AC_MODE_TO_STATE = {
      "auto": HVAC_MODE_HEAT_COOL,
      "cool": HVAC_MODE_COOL,
      "dry": HVAC_MODE_DRY,
      "coolClean": HVAC_MODE_COOL,
      "dryClean": HVAC_MODE_DRY,
      "heat": HVAC_MODE_HEAT,
      "heatClean": HVAC_MODE_HEAT,
      "fanOnly": HVAC_MODE_FAN_ONLY,
  }
  STATE_TO_AC_MODE = {
      HVAC_MODE_HEAT_COOL: "auto",
      HVAC_MODE_COOL: "cool",
      HVAC_MODE_DRY: "dry",
      HVAC_MODE_HEAT: "heat",
      HVAC_MODE_FAN_ONLY: "fanOnly",
  }
  
  UNIT_MAP = {"C": TEMP_CELSIUS, "F": TEMP_FAHRENHEIT}
  
  _LOGGER = logging.getLogger(__name__)
  
  
  async def async_setup_entry(
      hass: HomeAssistant,
      config_entry: ConfigEntry,
      async_add_entities: AddEntitiesCallback,
  ) -> None:
      """Add climate entities for a config entry."""
      ac_capabilities = [
          Capability.air_conditioner_mode,
          Capability.air_conditioner_fan_mode,
          Capability.switch,
          Capability.temperature_measurement,
          Capability.thermostat_cooling_setpoint,
      ]
  
      broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id]
      entities = []
      for device in broker.devices.values():
          if not broker.any_assigned(device.device_id, CLIMATE_DOMAIN):
              continue
          if all(capability in device.capabilities for capability in ac_capabilities):
              entities.append(SmartThingsAirConditioner(device))
          else:
              entities.append(SmartThingsThermostat(device))
      async_add_entities(entities, True)
  
  
  def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None:
      """Return all capabilities supported if minimum required are present."""
      supported = [
          Capability.air_conditioner_mode,
          Capability.demand_response_load_control,
          Capability.air_conditioner_fan_mode,
          Capability.power_consumption_report,
          Capability.relative_humidity_measurement,
          Capability.switch,
          Capability.temperature_measurement,
          Capability.thermostat,
          Capability.thermostat_cooling_setpoint,
          Capability.thermostat_fan_mode,
          Capability.thermostat_heating_setpoint,
          Capability.thermostat_mode,
          Capability.thermostat_operating_state,
      ]
      # Can have this legacy/deprecated capability
      if Capability.thermostat in capabilities:
          return supported
      # Or must have all of these thermostat capabilities
      thermostat_capabilities = [
          Capability.temperature_measurement,
          Capability.thermostat_cooling_setpoint,
          Capability.thermostat_heating_setpoint,
          Capability.thermostat_mode,
      ]
      if all(capability in capabilities for capability in thermostat_capabilities):
          return supported
      # Or must have all of these A/C capabilities
      ac_capabilities = [
          Capability.air_conditioner_mode,
          Capability.air_conditioner_fan_mode,
          Capability.switch,
          Capability.temperature_measurement,
          Capability.thermostat_cooling_setpoint,
      ]
      if all(capability in capabilities for capability in ac_capabilities):
          return supported
      return None
  
  
  class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
      """Define a SmartThings climate entities."""
  
      def __init__(self, device):
          """Init the class."""
          super().__init__(device)
          self._supported_features = self._determine_features()
          self._hvac_mode = None
          self._hvac_modes = None
  
      def _determine_features(self):
          flags = SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_RANGE
          if self._device.get_capability(
              Capability.thermostat_fan_mode, Capability.thermostat
          ):
              flags |= SUPPORT_FAN_MODE
          return flags
  
      async def async_set_fan_mode(self, fan_mode):
          """Set new target fan mode."""
          await self._device.set_thermostat_fan_mode(fan_mode, set_status=True)
  
          # State is set optimistically in the command above, therefore update
          # the entity state ahead of receiving the confirming push updates
          self.async_schedule_update_ha_state(True)
  
      async def async_set_hvac_mode(self, hvac_mode):
          """Set new target operation mode."""
          mode = STATE_TO_MODE[hvac_mode]
          await self._device.set_thermostat_mode(mode, set_status=True)
  
          # State is set optimistically in the command above, therefore update
          # the entity state ahead of receiving the confirming push updates
          self.async_schedule_update_ha_state(True)
  
      async def async_set_temperature(self, **kwargs):
          """Set new operation mode and target temperatures."""
          # Operation state
          if operation_state := kwargs.get(ATTR_HVAC_MODE):
              mode = STATE_TO_MODE[operation_state]
              await self._device.set_thermostat_mode(mode, set_status=True)
              await self.async_update()
  
          # Heat/cool setpoint
          heating_setpoint = None
          cooling_setpoint = None
          if self.hvac_mode == HVAC_MODE_HEAT:
              heating_setpoint = kwargs.get(ATTR_TEMPERATURE)
          elif self.hvac_mode == HVAC_MODE_COOL:
              cooling_setpoint = kwargs.get(ATTR_TEMPERATURE)
          else:
              heating_setpoint = kwargs.get(ATTR_TARGET_TEMP_LOW)
              cooling_setpoint = kwargs.get(ATTR_TARGET_TEMP_HIGH)
          tasks = []
          if heating_setpoint is not None:
              tasks.append(
                  self._device.set_heating_setpoint(
                      round(heating_setpoint, 3), set_status=True
                  )
              )
          if cooling_setpoint is not None:
              tasks.append(
                  self._device.set_cooling_setpoint(
                      round(cooling_setpoint, 3), set_status=True
                  )
              )
          await asyncio.gather(*tasks)
  
          # State is set optimistically in the commands above, therefore update
          # the entity state ahead of receiving the confirming push updates
          self.async_schedule_update_ha_state(True)
  
      async def async_update(self):
          """Update the attributes of the climate device."""
          thermostat_mode = self._device.status.thermostat_mode
          self._hvac_mode = MODE_TO_STATE.get(thermostat_mode)
          if self._hvac_mode is None:
              _LOGGER.debug(
                  "Device %s (%s) returned an invalid hvac mode: %s",
                  self._device.label,
                  self._device.device_id,
                  thermostat_mode,
              )
  
          modes = set()
          supported_modes = self._device.status.supported_thermostat_modes
          if isinstance(supported_modes, Iterable):
              for mode in supported_modes:
                  if (state := MODE_TO_STATE.get(mode)) is not None:
                      modes.add(state)
                  else:
                      _LOGGER.debug(
                          "Device %s (%s) returned an invalid supported thermostat mode: %s",
                          self._device.label,
                          self._device.device_id,
                          mode,
                      )
          else:
              _LOGGER.debug(
                  "Device %s (%s) returned invalid supported thermostat modes: %s",
                  self._device.label,
                  self._device.device_id,
                  supported_modes,
              )
          self._hvac_modes = list(modes)
  
      @property
      def current_humidity(self):
          """Return the current humidity."""
          return self._device.status.humidity
  
      @property
      def current_temperature(self):
          """Return the current temperature."""
          return self._device.status.temperature
  
      @property
      def fan_mode(self):
          """Return the fan setting."""
          return self._device.status.thermostat_fan_mode
  
      @property
      def fan_modes(self):
          """Return the list of available fan modes."""
          return self._device.status.supported_thermostat_fan_modes
  
      @property
      def hvac_action(self) -> str | None:
          """Return the current running hvac operation if supported."""
          return OPERATING_STATE_TO_ACTION.get(
              self._device.status.thermostat_operating_state
          )
  
      @property
      def hvac_mode(self):
          """Return current operation ie. heat, cool, idle."""
          return self._hvac_mode
  
      @property
      def hvac_modes(self):
          """Return the list of available operation modes."""
          return self._hvac_modes
  
      @property
      def supported_features(self):
          """Return the supported features."""
          return self._supported_features
  
      @property
      def target_temperature(self):
          """Return the temperature we try to reach."""
          if self.hvac_mode == HVAC_MODE_COOL:
              return self._device.status.cooling_setpoint
          if self.hvac_mode == HVAC_MODE_HEAT:
              return self._device.status.heating_setpoint
          return None
  
      @property
      def target_temperature_high(self):
          """Return the highbound target temperature we try to reach."""
          if self.hvac_mode == HVAC_MODE_HEAT_COOL:
              return self._device.status.cooling_setpoint
          return None
  
      @property
      def target_temperature_low(self):
          """Return the lowbound target temperature we try to reach."""
          if self.hvac_mode == HVAC_MODE_HEAT_COOL:
              return self._device.status.heating_setpoint
          return None
  
      @property
      def temperature_unit(self):
          """Return the unit of measurement."""
          return UNIT_MAP.get(self._device.status.attributes[Attribute.temperature].unit)
  
  
  class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
      """Define a SmartThings Air Conditioner."""
  
      def __init__(self, device):
          """Init the class."""
          super().__init__(device)
          self._hvac_modes = None
  
      async def async_set_fan_mode(self, fan_mode):
          """Set new target fan mode."""
          await self._device.set_fan_mode(fan_mode, set_status=True)
          # State is set optimistically in the command above, therefore update
          # the entity state ahead of receiving the confirming push updates
          self.async_write_ha_state()
  
      async def async_set_hvac_mode(self, hvac_mode):
          """Set new target operation mode."""
          if hvac_mode == HVAC_MODE_OFF:
              await self.async_turn_off()
              return
          tasks = []
          # Turn on the device if it's off before setting mode.
          if not self._device.status.switch:
              tasks.append(self._device.switch_on(set_status=True))
          tasks.append(
              self._device.set_air_conditioner_mode(
                  STATE_TO_AC_MODE[hvac_mode], set_status=True
              )
          )
          await asyncio.gather(*tasks)
          # State is set optimistically in the command above, therefore update
          # the entity state ahead of receiving the confirming push updates
          self.async_write_ha_state()
  
      async def async_set_temperature(self, **kwargs):
          """Set new target temperature."""
          tasks = []
          # operation mode
          if operation_mode := kwargs.get(ATTR_HVAC_MODE):
              if operation_mode == HVAC_MODE_OFF:
                  tasks.append(self._device.switch_off(set_status=True))
              else:
                  if not self._device.status.switch:
                      tasks.append(self._device.switch_on(set_status=True))
                  tasks.append(self.async_set_hvac_mode(operation_mode))
          # temperature
          tasks.append(
              self._device.set_cooling_setpoint(kwargs[ATTR_TEMPERATURE], set_status=True)
          )
          await asyncio.gather(*tasks)
          # State is set optimistically in the command above, therefore update
          # the entity state ahead of receiving the confirming push updates
          self.async_write_ha_state()
  
      async def async_turn_on(self):
          """Turn device on."""
          await self._device.switch_on(set_status=True)
          # State is set optimistically in the command above, therefore update
          # the entity state ahead of receiving the confirming push updates
          self.async_write_ha_state()
  
      async def async_turn_off(self):
          """Turn device off."""
          await self._device.switch_off(set_status=True)
          # State is set optimistically in the command above, therefore update
          # the entity state ahead of receiving the confirming push updates
          self.async_write_ha_state()
  
      async def async_update(self):
          """Update the calculated fields of the AC."""
          modes = {HVAC_MODE_OFF}
          for mode in self._device.status.supported_ac_modes:
              if (state := AC_MODE_TO_STATE.get(mode)) is not None:
                  modes.add(state)
              else:
                  _LOGGER.debug(
                      "Device %s (%s) returned an invalid supported AC mode: %s",
                      self._device.label,
                      self._device.device_id,
                      mode,
                  )
          self._hvac_modes = list(modes)
  
      @property
      def current_temperature(self):
          """Return the current temperature."""
          return self._device.status.temperature
  
      @property
      def extra_state_attributes(self):
          """
          Return device specific state attributes.
  
          Include attributes from the Demand Response Load Control (drlc)
          and Power Consumption capabilities.
          """
          attributes = [
              "drlc_status_duration",
              "drlc_status_level",
              "drlc_status_start",
              "drlc_status_override",
              "power_consumption_start",
              "power_consumption_power",
              "power_consumption_energy",
              "power_consumption_end",
          ]
          state_attributes = {}
          for attribute in attributes:
              value = getattr(self._device.status, attribute)
              if value is not None:
                  state_attributes[attribute] = value
          return state_attributes
  
      @property
      def fan_mode(self):
          """Return the fan setting."""
          return self._device.status.fan_mode
  
      @property
      def fan_modes(self):
          """Return the list of available fan modes."""
          return self._device.status.supported_ac_fan_modes
  
      @property
      def hvac_mode(self):
          """Return current operation ie. heat, cool, idle."""
          if not self._device.status.switch:
              return HVAC_MODE_OFF
          return AC_MODE_TO_STATE.get(self._device.status.air_conditioner_mode)
  
      @property
      def hvac_modes(self):
          """Return the list of available operation modes."""
          return self._hvac_modes
  
      @property
      def supported_features(self):
          """Return the supported features."""
          return SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
  
      @property
      def target_temperature(self):
          """Return the temperature we try to reach."""
          return self._device.status.cooling_setpoint
  
      @property
      def temperature_unit(self):
          """Return the unit of measurement."""
          return UNIT_MAP.get(self._device.status.attributes[Attribute.temperature].unit)
  