1 """Support for climate devices through the SmartThings cloud API."""
3 from __future__
import annotations
6 from collections.abc
import Iterable, Sequence
10 from pysmartthings
import Attribute, Capability
14 ATTR_TARGET_TEMP_HIGH,
16 DOMAIN
as CLIMATE_DOMAIN,
31 from .const
import DATA_BROKERS, DOMAIN
32 from .entity
import SmartThingsEntity
34 ATTR_OPERATION_STATE =
"operation_state"
36 "auto": HVACMode.HEAT_COOL,
37 "cool": HVACMode.COOL,
39 "rush hour": HVACMode.AUTO,
40 "emergency heat": HVACMode.HEAT,
41 "heat": HVACMode.HEAT,
45 HVACMode.HEAT_COOL:
"auto",
46 HVACMode.COOL:
"cool",
47 HVACMode.HEAT:
"heat",
51 OPERATING_STATE_TO_ACTION = {
52 "cooling": HVACAction.COOLING,
53 "fan only": HVACAction.FAN,
54 "heating": HVACAction.HEATING,
55 "idle": HVACAction.IDLE,
56 "pending cool": HVACAction.COOLING,
57 "pending heat": HVACAction.HEATING,
58 "vent economizer": HVACAction.FAN,
59 "wind": HVACAction.FAN,
63 "auto": HVACMode.HEAT_COOL,
64 "cool": HVACMode.COOL,
66 "coolClean": HVACMode.COOL,
67 "dryClean": HVACMode.DRY,
68 "heat": HVACMode.HEAT,
69 "heatClean": HVACMode.HEAT,
70 "fanOnly": HVACMode.FAN_ONLY,
71 "wind": HVACMode.FAN_ONLY,
74 HVACMode.HEAT_COOL:
"auto",
75 HVACMode.COOL:
"cool",
77 HVACMode.HEAT:
"heat",
78 HVACMode.FAN_ONLY:
"fanOnly",
81 SWING_TO_FAN_OSCILLATION = {
83 SWING_HORIZONTAL:
"horizontal",
84 SWING_VERTICAL:
"vertical",
88 FAN_OSCILLATION_TO_SWING = {
89 value: key
for key, value
in SWING_TO_FAN_OSCILLATION.items()
95 UNIT_MAP = {
"C": UnitOfTemperature.CELSIUS,
"F": UnitOfTemperature.FAHRENHEIT}
97 _LOGGER = logging.getLogger(__name__)
102 config_entry: ConfigEntry,
103 async_add_entities: AddEntitiesCallback,
105 """Add climate entities for a config entry."""
107 Capability.air_conditioner_mode,
108 Capability.air_conditioner_fan_mode,
110 Capability.temperature_measurement,
111 Capability.thermostat_cooling_setpoint,
114 broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id]
115 entities: list[ClimateEntity] = []
116 for device
in broker.devices.values():
117 if not broker.any_assigned(device.device_id, CLIMATE_DOMAIN):
119 if all(capability
in device.capabilities
for capability
in ac_capabilities):
127 """Return all capabilities supported if minimum required are present."""
129 Capability.air_conditioner_mode,
130 Capability.demand_response_load_control,
131 Capability.air_conditioner_fan_mode,
133 Capability.thermostat,
134 Capability.thermostat_cooling_setpoint,
135 Capability.thermostat_fan_mode,
136 Capability.thermostat_heating_setpoint,
137 Capability.thermostat_mode,
138 Capability.thermostat_operating_state,
141 if Capability.thermostat
in capabilities:
144 thermostat_capabilities = [
145 Capability.temperature_measurement,
146 Capability.thermostat_heating_setpoint,
147 Capability.thermostat_mode,
149 if all(capability
in capabilities
for capability
in thermostat_capabilities):
153 Capability.air_conditioner_mode,
154 Capability.air_conditioner_fan_mode,
156 Capability.temperature_measurement,
157 Capability.thermostat_cooling_setpoint,
159 if all(capability
in capabilities
for capability
in ac_capabilities):
165 """Define a SmartThings climate entities."""
167 _enable_turn_on_off_backwards_compatibility =
False
170 """Init the class."""
178 ClimateEntityFeature.TARGET_TEMPERATURE
179 | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
180 | ClimateEntityFeature.TURN_OFF
181 | ClimateEntityFeature.TURN_ON
184 Capability.thermostat_fan_mode, Capability.thermostat
186 flags |= ClimateEntityFeature.FAN_MODE
190 """Set new target fan mode."""
191 await self.
_device_device.set_thermostat_fan_mode(fan_mode, set_status=
True)
198 """Set new target operation mode."""
199 mode = STATE_TO_MODE[hvac_mode]
200 await self.
_device_device.set_thermostat_mode(mode, set_status=
True)
207 """Set new operation mode and target temperatures."""
209 if operation_state := kwargs.get(ATTR_HVAC_MODE):
210 mode = STATE_TO_MODE[operation_state]
211 await self.
_device_device.set_thermostat_mode(mode, set_status=
True)
215 heating_setpoint =
None
216 cooling_setpoint =
None
218 heating_setpoint = kwargs.get(ATTR_TEMPERATURE)
220 cooling_setpoint = kwargs.get(ATTR_TEMPERATURE)
222 heating_setpoint = kwargs.get(ATTR_TARGET_TEMP_LOW)
223 cooling_setpoint = kwargs.get(ATTR_TARGET_TEMP_HIGH)
225 if heating_setpoint
is not None:
227 self.
_device_device.set_heating_setpoint(
228 round(heating_setpoint, 3), set_status=
True
231 if cooling_setpoint
is not None:
233 self.
_device_device.set_cooling_setpoint(
234 round(cooling_setpoint, 3), set_status=
True
237 await asyncio.gather(*tasks)
244 """Update the attributes of the climate device."""
245 thermostat_mode = self.
_device_device.status.thermostat_mode
246 self.
_hvac_mode_hvac_mode = MODE_TO_STATE.get(thermostat_mode)
249 "Device %s (%s) returned an invalid hvac mode: %s",
256 supported_modes = self.
_device_device.status.supported_thermostat_modes
257 if isinstance(supported_modes, Iterable):
258 for mode
in supported_modes:
259 if (state := MODE_TO_STATE.get(mode))
is not None:
264 "Device %s (%s) returned an invalid supported thermostat"
273 "Device %s (%s) returned invalid supported thermostat modes: %s",
282 """Return the current humidity."""
283 return self.
_device_device.status.humidity
287 """Return the current temperature."""
288 return self.
_device_device.status.temperature
292 """Return the fan setting."""
293 return self.
_device_device.status.thermostat_fan_mode
297 """Return the list of available fan modes."""
298 return self.
_device_device.status.supported_thermostat_fan_modes
302 """Return the current running hvac operation if supported."""
303 return OPERATING_STATE_TO_ACTION.get(
304 self.
_device_device.status.thermostat_operating_state
309 """Return current operation ie. heat, cool, idle."""
314 """Return the list of available operation modes."""
319 """Return the temperature we try to reach."""
321 return self.
_device_device.status.cooling_setpoint
323 return self.
_device_device.status.heating_setpoint
328 """Return the highbound target temperature we try to reach."""
330 return self.
_device_device.status.cooling_setpoint
335 """Return the lowbound target temperature we try to reach."""
337 return self.
_device_device.status.heating_setpoint
342 """Return the unit of measurement."""
343 return UNIT_MAP.get(self.
_device_device.status.attributes[Attribute.temperature].unit)
347 """Define a SmartThings Air Conditioner."""
349 _hvac_modes: list[HVACMode]
350 _enable_turn_on_off_backwards_compatibility =
False
353 """Init the class."""
363 ClimateEntityFeature.TARGET_TEMPERATURE
364 | ClimateEntityFeature.FAN_MODE
365 | ClimateEntityFeature.TURN_OFF
366 | ClimateEntityFeature.TURN_ON
369 features |= ClimateEntityFeature.SWING_MODE
371 features |= ClimateEntityFeature.PRESET_MODE
375 """Set new target fan mode."""
386 """Set new target operation mode."""
387 if hvac_mode == HVACMode.OFF:
392 if not self.
_device_device.status.switch:
393 tasks.append(self.
_device_device.switch_on(set_status=
True))
395 mode = STATE_TO_AC_MODE[hvac_mode]
399 if hvac_mode == HVACMode.FAN_ONLY:
400 supported_modes = self.
_device_device.status.supported_ac_modes
401 if WIND
in supported_modes:
404 tasks.append(self.
_device_device.set_air_conditioner_mode(mode, set_status=
True))
405 await asyncio.gather(*tasks)
411 """Set new target temperature."""
414 if operation_mode := kwargs.get(ATTR_HVAC_MODE):
415 if operation_mode == HVACMode.OFF:
416 tasks.append(self.
_device_device.switch_off(set_status=
True))
418 if not self.
_device_device.status.switch:
419 tasks.append(self.
_device_device.switch_on(set_status=
True))
423 self.
_device_device.set_cooling_setpoint(kwargs[ATTR_TEMPERATURE], set_status=
True)
425 await asyncio.gather(*tasks)
431 """Turn device on."""
432 await self.
_device_device.switch_on(set_status=
True)
438 """Turn device off."""
439 await self.
_device_device.switch_off(set_status=
True)
445 """Update the calculated fields of the AC."""
446 modes = {HVACMode.OFF}
447 for mode
in self.
_device_device.status.supported_ac_modes:
448 if (state := AC_MODE_TO_STATE.get(mode))
is not None:
452 "Device %s (%s) returned an invalid supported AC mode: %s",
461 """Return the current temperature."""
462 return self.
_device_device.status.temperature
466 """Return device specific state attributes.
468 Include attributes from the Demand Response Load Control (drlc)
469 and Power Consumption capabilities.
472 "drlc_status_duration",
475 "drlc_status_override",
477 state_attributes = {}
478 for attribute
in attributes:
479 value = getattr(self.
_device_device.status, attribute)
480 if value
is not None:
481 state_attributes[attribute] = value
482 return state_attributes
486 """Return the fan setting."""
487 return self.
_device_device.status.fan_mode
491 """Return the list of available fan modes."""
492 return self.
_device_device.status.supported_ac_fan_modes
496 """Return current operation ie. heat, cool, idle."""
497 if not self.
_device_device.status.switch:
499 return AC_MODE_TO_STATE.get(self.
_device_device.status.air_conditioner_mode)
503 """Return the list of available operation modes."""
508 """Return the temperature we try to reach."""
509 return self.
_device_device.status.cooling_setpoint
513 """Return the unit of measurement."""
514 return UNIT_MAP[self.
_device_device.status.attributes[Attribute.temperature].unit]
517 """Return the list of available swing modes."""
518 supported_swings =
None
519 supported_modes = self.
_device_device.status.attributes[
520 Attribute.supported_fan_oscillation_modes
522 if supported_modes
is not None:
524 FAN_OSCILLATION_TO_SWING.get(m, SWING_OFF)
for m
in supported_modes
526 return supported_swings
529 """Set swing mode."""
530 fan_oscillation_mode = SWING_TO_FAN_OSCILLATION[swing_mode]
531 await self.
_device_device.set_fan_oscillation_mode(fan_oscillation_mode)
540 """Return the swing setting."""
541 return FAN_OSCILLATION_TO_SWING.get(
542 self.
_device_device.status.fan_oscillation_mode, SWING_OFF
546 """Return a list of available preset modes."""
547 supported_modes: list |
None = self.
_device_device.status.attributes[
548 "supportedAcOptionalMode"
550 if supported_modes
and WINDFREE
in supported_modes:
555 """Set special modes (currently only windFree is supported)."""
556 result = await self.
_device_device.command(
558 "custom.airConditionerOptionalMode",
563 self.
_device_device.status.update_attribute_value(
"acOptionalMode", preset_mode)
None set_fan_mode(self, str fan_mode)
None async_set_hvac_mode(self, HVACMode hvac_mode)
HVACMode|None hvac_mode(self)
None async_turn_off(self)
list[str]|None _determine_preset_modes(self)
list[HVACMode] hvac_modes(self)
list[str] fan_modes(self)
list[str]|None _determine_swing_modes(self)
float|None current_temperature(self)
float target_temperature(self)
None async_set_fan_mode(self, str fan_mode)
dict[str, Any] extra_state_attributes(self)
ClimateEntityFeature _determine_supported_features(self)
None async_set_temperature(self, **Any kwargs)
str temperature_unit(self)
None async_turn_off(self)
None async_set_preset_mode(self, str preset_mode)
None async_set_swing_mode(self, str swing_mode)
None async_set_hvac_mode(self, HVACMode hvac_mode)
None __init__(self, device)
def target_temperature_high(self)
HVACAction|None hvac_action(self)
def __init__(self, device)
def _determine_features(self)
None async_set_temperature(self, **Any kwargs)
def temperature_unit(self)
def current_temperature(self)
None async_set_fan_mode(self, str fan_mode)
def current_humidity(self)
def target_temperature_low(self)
None async_set_hvac_mode(self, HVACMode hvac_mode)
def target_temperature(self)
list[HVACMode] hvac_modes(self)
None async_schedule_update_ha_state(self, bool force_refresh=False)
None async_write_ha_state(self)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Sequence[str]|None get_capabilities(Sequence[str] capabilities)
Any|None get_capability(HomeAssistant hass, str entity_id, str capability)