1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 12:59:34 +00:00

Migrate Tuya climate (hvac_mode/presets) to use wrapper class (#156933)

This commit is contained in:
epenet
2025-11-20 14:23:28 +01:00
committed by GitHub
parent d5fd27d2a2
commit 13e4bb4b93
3 changed files with 54 additions and 39 deletions

View File

@@ -27,6 +27,7 @@ from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType
from .entity import TuyaEntity
from .models import (
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
IntegerTypeData,
@@ -120,6 +121,12 @@ async def async_setup_entry(
(DPCode.FAN_SPEED_ENUM, DPCode.LEVEL, DPCode.WINDSPEED),
prefer_function=True,
),
hvac_mode_wrapper=DPCodeEnumWrapper.find_dpcode(
device, DPCode.MODE, prefer_function=True
),
switch_wrapper=DPCodeBooleanWrapper.find_dpcode(
device, DPCode.SWITCH, prefer_function=True
),
target_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode(
device, DPCode.HUMIDITY_SET, prefer_function=True
),
@@ -150,9 +157,11 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
description: TuyaClimateEntityDescription,
system_temperature_unit: UnitOfTemperature,
*,
current_humidity_wrapper: _RoundedIntegerWrapper | None = None,
fan_mode_wrapper: DPCodeEnumWrapper | None = None,
target_humidity_wrapper: _RoundedIntegerWrapper | None = None,
current_humidity_wrapper: _RoundedIntegerWrapper | None,
fan_mode_wrapper: DPCodeEnumWrapper | None,
hvac_mode_wrapper: DPCodeEnumWrapper | None,
switch_wrapper: DPCodeBooleanWrapper | None,
target_humidity_wrapper: _RoundedIntegerWrapper | None,
) -> None:
"""Determine which values to use."""
self._attr_target_temperature_step = 1.0
@@ -161,6 +170,8 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
super().__init__(device, device_manager)
self._current_humidity_wrapper = current_humidity_wrapper
self._fan_mode_wrapper = fan_mode_wrapper
self._hvac_mode_wrapper = hvac_mode_wrapper
self._switch_wrapper = switch_wrapper
self._target_humidity_wrapper = target_humidity_wrapper
# If both temperature values for celsius and fahrenheit are present,
@@ -234,12 +245,10 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
# Determine HVAC modes
self._attr_hvac_modes: list[HVACMode] = []
self._hvac_to_tuya = {}
if enum_type := find_dpcode(
self.device, DPCode.MODE, dptype=DPType.ENUM, prefer_function=True
):
if hvac_mode_wrapper:
self._attr_hvac_modes = [HVACMode.OFF]
unknown_hvac_modes: list[str] = []
for tuya_mode in enum_type.range:
for tuya_mode in hvac_mode_wrapper.type_information.range:
if tuya_mode in TUYA_HVAC_TO_HA:
ha_mode = TUYA_HVAC_TO_HA[tuya_mode]
self._hvac_to_tuya[ha_mode] = tuya_mode
@@ -251,7 +260,7 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
self._attr_hvac_modes.append(description.switch_only_hvac_mode)
self._attr_preset_modes = unknown_hvac_modes
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
elif get_dpcode(self.device, DPCode.SWITCH):
elif switch_wrapper:
self._attr_hvac_modes = [
HVACMode.OFF,
description.switch_only_hvac_mode,
@@ -293,24 +302,31 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
if get_dpcode(self.device, DPCode.SWITCH_VERTICAL):
self._attr_swing_modes.append(SWING_VERTICAL)
if DPCode.SWITCH in self.device.function:
if switch_wrapper:
self._attr_supported_features |= (
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
)
def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""
commands = [{"code": DPCode.SWITCH, "value": hvac_mode != HVACMode.OFF}]
if hvac_mode in self._hvac_to_tuya:
commands = []
if self._switch_wrapper:
commands.append(
{"code": DPCode.MODE, "value": self._hvac_to_tuya[hvac_mode]}
self._switch_wrapper.get_update_command(
self.device, hvac_mode != HVACMode.OFF
)
)
self._send_command(commands)
if self._hvac_mode_wrapper and hvac_mode in self._hvac_to_tuya:
commands.append(
self._hvac_mode_wrapper.get_update_command(
self.device, self._hvac_to_tuya[hvac_mode]
)
)
await self._async_send_commands(commands)
def set_preset_mode(self, preset_mode: str) -> None:
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new target preset mode."""
commands = [{"code": DPCode.MODE, "value": preset_mode}]
self._send_command(commands)
await self._async_send_dpcode_update(self._hvac_mode_wrapper, preset_mode)
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
@@ -406,34 +422,29 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
@property
def hvac_mode(self) -> HVACMode:
"""Return hvac mode."""
# If the switch off, hvac mode is off as well. Unless the switch
# the switch is on or doesn't exists of course...
if not self.device.status.get(DPCode.SWITCH, True):
# If the switch is off, hvac mode is off as well.
# Unless the switch doesn't exists of course...
if (switch_status := self._read_wrapper(self._switch_wrapper)) is False:
return HVACMode.OFF
if DPCode.MODE not in self.device.function:
if self.device.status.get(DPCode.SWITCH, False):
return self.entity_description.switch_only_hvac_mode
return HVACMode.OFF
# If the mode is known and maps to an HVAC mode, return it.
if (mode := self._read_wrapper(self._hvac_mode_wrapper)) and (
hvac_mode := TUYA_HVAC_TO_HA.get(mode)
):
return hvac_mode
if (
mode := self.device.status.get(DPCode.MODE)
) is not None and mode in TUYA_HVAC_TO_HA:
return TUYA_HVAC_TO_HA[mode]
# If the switch is on, and the mode does not match any hvac mode.
if self.device.status.get(DPCode.SWITCH, False):
# If hvac_mode is unknown, return the switch only mode.
if switch_status:
return self.entity_description.switch_only_hvac_mode
return HVACMode.OFF
@property
def preset_mode(self) -> str | None:
"""Return preset mode."""
if DPCode.MODE not in self.device.function:
if self._hvac_mode_wrapper is None:
return None
mode = self.device.status.get(DPCode.MODE)
mode = self._read_wrapper(self._hvac_mode_wrapper)
if mode in TUYA_HVAC_TO_HA:
return None
@@ -463,10 +474,10 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
return SWING_OFF
def turn_on(self) -> None:
async def async_turn_on(self) -> None:
"""Turn the device on, retaining current HVAC (if supported)."""
self._send_command([{"code": DPCode.SWITCH, "value": True}])
await self._async_send_dpcode_update(self._switch_wrapper, True)
def turn_off(self) -> None:
async def async_turn_off(self) -> None:
"""Turn the device on, retaining current HVAC (if supported)."""
self._send_command([{"code": DPCode.SWITCH, "value": False}])
await self._async_send_dpcode_update(self._switch_wrapper, False)

View File

@@ -66,6 +66,10 @@ class TuyaEntity(Entity):
LOGGER.debug("Sending commands for device %s: %s", self.device.id, commands)
self.device_manager.send_commands(self.device.id, commands)
async def _async_send_commands(self, commands: list[dict[str, Any]]) -> None:
"""Send a list of commands to the device."""
await self.hass.async_add_executor_job(self._send_command, commands)
def _read_wrapper(self, dpcode_wrapper: DPCodeWrapper | None) -> Any | None:
"""Read the wrapper device status."""
if dpcode_wrapper is None:

View File

@@ -922,7 +922,7 @@
]),
'max_temp': 40.0,
'min_temp': 5.0,
'preset_mode': 'hold',
'preset_mode': None,
'preset_modes': list([
'holiday',
]),