1 """Support for Tuya Climate."""
3 from __future__
import annotations
5 from dataclasses
import dataclass
8 from tuya_sharing
import CustomerDevice, Manager
17 ClimateEntityDescription,
26 from .
import TuyaConfigEntry
27 from .const
import TUYA_DISCOVERY_NEW, DPCode, DPType
28 from .entity
import IntegerTypeData, TuyaEntity
31 "auto": HVACMode.HEAT_COOL,
32 "cold": HVACMode.COOL,
33 "freeze": HVACMode.COOL,
34 "heat": HVACMode.HEAT,
36 "manual": HVACMode.HEAT_COOL,
38 "wind": HVACMode.FAN_ONLY,
42 @dataclass(frozen=True, kw_only=True)
44 """Describe an Tuya climate entity."""
46 switch_only_hvac_mode: HVACMode
49 CLIMATE_DESCRIPTIONS: dict[str, TuyaClimateEntityDescription] = {
54 switch_only_hvac_mode=HVACMode.COOL,
60 switch_only_hvac_mode=HVACMode.HEAT,
66 switch_only_hvac_mode=HVACMode.HEAT,
72 switch_only_hvac_mode=HVACMode.HEAT_COOL,
78 switch_only_hvac_mode=HVACMode.HEAT,
84 hass: HomeAssistant, entry: TuyaConfigEntry, async_add_entities: AddEntitiesCallback
86 """Set up Tuya climate dynamically through Tuya discovery."""
87 hass_data = entry.runtime_data
91 """Discover and add a discovered Tuya climate."""
92 entities: list[TuyaClimateEntity] = []
93 for device_id
in device_ids:
94 device = hass_data.manager.device_map[device_id]
95 if device
and device.category
in CLIMATE_DESCRIPTIONS:
100 CLIMATE_DESCRIPTIONS[device.category],
101 hass.config.units.temperature_unit,
108 entry.async_on_unload(
114 """Tuya Climate Device."""
116 _current_humidity: IntegerTypeData |
None =
None
117 _current_temperature: IntegerTypeData |
None =
None
118 _hvac_to_tuya: dict[str, str]
119 _set_humidity: IntegerTypeData |
None =
None
120 _set_temperature: IntegerTypeData |
None =
None
121 entity_description: TuyaClimateEntityDescription
123 _enable_turn_on_off_backwards_compatibility =
False
127 device: CustomerDevice,
128 device_manager: Manager,
129 description: TuyaClimateEntityDescription,
130 system_temperature_unit: UnitOfTemperature,
132 """Determine which values to use."""
136 super().
__init__(device, device_manager)
140 prefered_temperature_unit =
None
142 dpcode
in device.status
143 for dpcode
in (DPCode.TEMP_CURRENT, DPCode.TEMP_CURRENT_F)
145 dpcode
in device.status
for dpcode
in (DPCode.TEMP_SET, DPCode.TEMP_SET_F)
147 prefered_temperature_unit = UnitOfTemperature.CELSIUS
149 "f" in device.status[dpcode].lower()
150 for dpcode
in (DPCode.C_F, DPCode.TEMP_UNIT_CONVERT)
151 if isinstance(device.status.get(dpcode), str)
153 prefered_temperature_unit = UnitOfTemperature.FAHRENHEIT
160 (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP), dptype=DPType.INTEGER
163 (DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F), dptype=DPType.INTEGER
165 if fahrenheit_type
and (
166 prefered_temperature_unit == UnitOfTemperature.FAHRENHEIT
168 prefered_temperature_unit == UnitOfTemperature.CELSIUS
180 DPCode.TEMP_SET, dptype=DPType.INTEGER, prefer_function=
True
183 DPCode.TEMP_SET_F, dptype=DPType.INTEGER, prefer_function=
True
185 if fahrenheit_type
and (
186 prefered_temperature_unit == UnitOfTemperature.FAHRENHEIT
188 prefered_temperature_unit == UnitOfTemperature.CELSIUS
199 self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
208 DPCode.MODE, dptype=DPType.ENUM, prefer_function=
True
211 unknown_hvac_modes: list[str] = []
212 for tuya_mode
in enum_type.range:
213 if tuya_mode
in TUYA_HVAC_TO_HA:
214 ha_mode = TUYA_HVAC_TO_HA[tuya_mode]
218 unknown_hvac_modes.append(tuya_mode)
220 if unknown_hvac_modes:
221 self.
_attr_hvac_modes_attr_hvac_modes.append(description.switch_only_hvac_mode)
223 self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
227 description.switch_only_hvac_mode,
232 DPCode.HUMIDITY_SET, dptype=DPType.INTEGER, prefer_function=
True
234 self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY
241 DPCode.HUMIDITY_CURRENT, dptype=DPType.INTEGER
246 (DPCode.FAN_SPEED_ENUM, DPCode.WINDSPEED),
248 prefer_function=
True,
250 self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
258 DPCode.SWITCH_HORIZONTAL,
259 DPCode.SWITCH_VERTICAL,
261 prefer_function=
True,
263 self._attr_supported_features |= ClimateEntityFeature.SWING_MODE
274 if DPCode.SWITCH
in self.
devicedevice.function:
275 self._attr_supported_features |= (
276 ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
280 """Call when entity is added to hass."""
284 """Set new target hvac mode."""
285 commands = [{
"code": DPCode.SWITCH,
"value": hvac_mode != HVACMode.OFF}]
288 {
"code": DPCode.MODE,
"value": self.
_hvac_to_tuya_hvac_to_tuya[hvac_mode]}
293 """Set new target preset mode."""
294 commands = [{
"code": DPCode.MODE,
"value": preset_mode}]
298 """Set new target fan mode."""
299 self.
_send_command_send_command([{
"code": DPCode.FAN_SPEED_ENUM,
"value": fan_mode}])
302 """Set new target humidity."""
305 "Cannot set humidity, device doesn't provide methods to set it"
312 "value": self.
_set_humidity_set_humidity.scale_value_back(humidity),
318 """Set new target swing operation."""
324 "code": DPCode.SHAKE,
325 "value": swing_mode == SWING_ON,
328 "code": DPCode.SWING,
329 "value": swing_mode == SWING_ON,
332 "code": DPCode.SWITCH_VERTICAL,
333 "value": swing_mode
in (SWING_BOTH, SWING_VERTICAL),
336 "code": DPCode.SWITCH_HORIZONTAL,
337 "value": swing_mode
in (SWING_BOTH, SWING_HORIZONTAL),
343 """Set new target temperature."""
346 "Cannot set target temperature, device doesn't provide methods to"
355 self.
_set_temperature_set_temperature.scale_value_back(kwargs[
"temperature"])
363 """Return the current temperature."""
368 if temperature
is None:
376 temperature = temperature / 10
382 """Return the current humidity."""
394 """Return the temperature currently set to be reached."""
399 if temperature
is None:
406 """Return the humidity currently set to be reached."""
414 return round(self.
_set_humidity_set_humidity.scale_value(humidity))
418 """Return hvac mode."""
421 if not self.
devicedevice.status.get(DPCode.SWITCH,
True):
424 if DPCode.MODE
not in self.
devicedevice.function:
425 if self.
devicedevice.status.get(DPCode.SWITCH,
False):
430 mode := self.
devicedevice.status.get(DPCode.MODE)
431 )
is not None and mode
in TUYA_HVAC_TO_HA:
432 return TUYA_HVAC_TO_HA[mode]
435 if self.
devicedevice.status.get(DPCode.SWITCH,
False):
442 """Return preset mode."""
443 if DPCode.MODE
not in self.
devicedevice.function:
446 mode = self.
devicedevice.status.get(DPCode.MODE)
447 if mode
in TUYA_HVAC_TO_HA:
454 """Return fan mode."""
455 return self.
devicedevice.status.get(DPCode.FAN_SPEED_ENUM)
459 """Return swing mode."""
461 self.
devicedevice.status.get(dpcode)
for dpcode
in (DPCode.SHAKE, DPCode.SWING)
465 horizontal = self.
devicedevice.status.get(DPCode.SWITCH_HORIZONTAL)
466 vertical = self.
devicedevice.status.get(DPCode.SWITCH_VERTICAL)
467 if horizontal
and vertical:
470 return SWING_HORIZONTAL
472 return SWING_VERTICAL
477 """Turn the device on, retaining current HVAC (if supported)."""
478 self.
_send_command_send_command([{
"code": DPCode.SWITCH,
"value":
True}])
481 """Turn the device on, retaining current HVAC (if supported)."""
482 self.
_send_command_send_command([{
"code": DPCode.SWITCH,
"value":
False}])
None async_added_to_hass(self)
None set_hvac_mode(self, HVACMode hvac_mode)
None set_temperature(self, **Any kwargs)
None __init__(self, CustomerDevice device, Manager device_manager, TuyaClimateEntityDescription description, UnitOfTemperature system_temperature_unit)
float|None current_temperature(self)
None set_fan_mode(self, str fan_mode)
None set_humidity(self, int humidity)
def set_preset_mode(self, preset_mode)
_attr_target_temperature_step
str|None preset_mode(self)
int|None current_humidity(self)
float|None target_temperature(self)
int|None target_humidity(self)
None set_swing_mode(self, str swing_mode)
None _send_command(self, list[dict[str, Any]] commands)
DPCode|EnumTypeData|IntegerTypeData|None find_dpcode(self, str|DPCode|tuple[DPCode,...]|None dpcodes, *bool prefer_function=False, DPType|None dptype=None)
IntegerTypeData|None find_dpcode(self, str|DPCode|tuple[DPCode,...]|None dpcodes, *bool prefer_function=False, Literal[DPType.INTEGER] dptype)
DPCode|None find_dpcode(self, str|DPCode|tuple[DPCode,...]|None dpcodes, *bool prefer_function=False)
EnumTypeData|None find_dpcode(self, str|DPCode|tuple[DPCode,...]|None dpcodes, *bool prefer_function=False, Literal[DPType.ENUM] dptype)
ElkSystem|None async_discover_device(HomeAssistant hass, str host)
None async_setup_entry(HomeAssistant hass, TuyaConfigEntry entry, AddEntitiesCallback async_add_entities)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)