diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index 798c201b82e..22ea3220caf 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -31,7 +31,7 @@ from .const import ( TUYA_DISCOVERY_NEW, TUYA_HA_SIGNAL_UPDATE_ENTITY, ) -from .xternal_tuya_device_quirks import register_tuya_quirks +from .tuya_device_handlers import register_tuya_quirks # Suppress logs from the library, it logs unneeded on error logging.getLogger("tuya_sharing").setLevel(logging.CRITICAL) diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 78db8ea1f8b..fdb22412d51 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -26,9 +26,10 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import TuyaConfigEntry from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType from .entity import TuyaEntity -from .models import IntegerTypeData, StateConversionFunction +from .models import IntegerTypeData +from .tuya_device_handlers import TUYA_QUIRKS_REGISTRY, TuyaClimateDefinition +from .tuya_device_handlers.conversion import TuyaIntegerConversionFunction from .util import get_dpcode -from .xternal_tuya_quirks import TUYA_QUIRKS_REGISTRY, TuyaClimateDefinition TUYA_HVAC_TO_HA = { "auto": HVACMode.HEAT_COOL, @@ -47,9 +48,9 @@ class TuyaClimateEntityDescription(ClimateEntityDescription): """Describe an Tuya climate entity.""" switch_only_hvac_mode: HVACMode - current_temperature_state_conversion: StateConversionFunction | None = None - target_temperature_state_conversion: StateConversionFunction | None = None - target_temperature_command_conversion: StateConversionFunction | None = None + current_temperature_state_conversion: TuyaIntegerConversionFunction | None = None + target_temperature_state_conversion: TuyaIntegerConversionFunction | None = None + target_temperature_command_conversion: TuyaIntegerConversionFunction | None = None CLIMATE_DESCRIPTIONS: dict[DeviceCategory, TuyaClimateEntityDescription] = { @@ -383,7 +384,7 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): if convert := self.entity_description.target_temperature_command_conversion: value = convert( - self.device, self._current_temperature, kwargs[ATTR_TEMPERATURE] + self.device, self._set_temperature, kwargs[ATTR_TEMPERATURE] ) else: value = round( diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index bac478f1407..a07c27902c0 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -23,8 +23,8 @@ from . import TuyaConfigEntry from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType from .entity import TuyaEntity from .models import EnumTypeData, IntegerTypeData +from .tuya_device_handlers import TUYA_QUIRKS_REGISTRY, TuyaCoverDefinition, parse_enum from .util import get_dpcode -from .xternal_tuya_quirks import TUYA_QUIRKS_REGISTRY, TuyaCoverDefinition, parse_enum @dataclass(frozen=True) diff --git a/homeassistant/components/tuya/models.py b/homeassistant/components/tuya/models.py index c14e79d0a07..f5ad4825dc7 100644 --- a/homeassistant/components/tuya/models.py +++ b/homeassistant/components/tuya/models.py @@ -3,24 +3,18 @@ from __future__ import annotations import base64 -from collections.abc import Callable from dataclasses import dataclass import json import struct -from typing import Any, Self - -from tuya_sharing import CustomerDevice +from typing import Self from .const import DPCode +from .tuya_device_handlers.conversion import TuyaIntegerDefinition from .util import remap_value -type StateConversionFunction = Callable[ - [CustomerDevice, EnumTypeData | IntegerTypeData | None, Any], Any -] - @dataclass -class IntegerTypeData: +class IntegerTypeData(TuyaIntegerDefinition): """Integer Type Data.""" dpcode: DPCode diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index 781ca0ac22c..1f5f7df0c3f 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -13,7 +13,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import TuyaConfigEntry from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType from .entity import TuyaEntity -from .xternal_tuya_quirks import TUYA_QUIRKS_REGISTRY, TuyaSelectDefinition, parse_enum +from .tuya_device_handlers import TUYA_QUIRKS_REGISTRY, TuyaSelectDefinition, parse_enum # All descriptions can be found here. Mostly the Enum data types in the # default instructions set of each category end up being a select. diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index f5b6e448316..0380ed7e2b1 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -44,8 +44,8 @@ from .const import ( ) from .entity import TuyaEntity from .models import ComplexValue, ElectricityValue, EnumTypeData, IntegerTypeData +from .tuya_device_handlers import TUYA_QUIRKS_REGISTRY, TuyaSensorDefinition, parse_enum from .util import get_dptype -from .xternal_tuya_quirks import TUYA_QUIRKS_REGISTRY, TuyaSensorDefinition, parse_enum _WIND_DIRECTIONS = { "north": 0.0, diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 2ebb3b65c3d..abfe7e1e3d3 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -27,7 +27,7 @@ from homeassistant.helpers.issue_registry import ( from . import TuyaConfigEntry from .const import DOMAIN, TUYA_DISCOVERY_NEW, DeviceCategory, DPCode from .entity import TuyaEntity -from .xternal_tuya_quirks import TUYA_QUIRKS_REGISTRY, TuyaSwitchDefinition, parse_enum +from .tuya_device_handlers import TUYA_QUIRKS_REGISTRY, TuyaSwitchDefinition, parse_enum @dataclass(frozen=True, kw_only=True) diff --git a/homeassistant/components/tuya/xternal_tuya_device_quirks/__init__.py b/homeassistant/components/tuya/tuya_device_handlers/__init__.py similarity index 63% rename from homeassistant/components/tuya/xternal_tuya_device_quirks/__init__.py rename to homeassistant/components/tuya/tuya_device_handlers/__init__.py index e54126a9657..3630321f3aa 100644 --- a/homeassistant/components/tuya/xternal_tuya_device_quirks/__init__.py +++ b/homeassistant/components/tuya/tuya_device_handlers/__init__.py @@ -1,4 +1,4 @@ -"""Quirks for Tuya.""" +"""Tuya device handler.""" from __future__ import annotations @@ -8,28 +8,53 @@ import pathlib import pkgutil import sys -from ..xternal_tuya_quirks import TUYA_QUIRKS_REGISTRY +from .base_quirk import ( + TuyaClimateDefinition, + TuyaCoverDefinition, + TuyaDeviceQuirk, + TuyaSelectDefinition, + TuyaSensorDefinition, + TuyaSwitchDefinition, +) +from .registry import QuirksRegistry +from .utils import parse_enum _LOGGER = logging.getLogger(__name__) +__all__ = [ + "TUYA_QUIRKS_REGISTRY", + "QuirksRegistry", + "TuyaClimateDefinition", + "TuyaCoverDefinition", + "TuyaDeviceQuirk", + "TuyaSelectDefinition", + "TuyaSensorDefinition", + "TuyaSwitchDefinition", + "parse_enum", +] + +TUYA_QUIRKS_REGISTRY = QuirksRegistry() + def register_tuya_quirks(custom_quirks_path: str | None = None) -> None: - """Register all quirks with xternal_tuya_quirks. + """Register all available quirks. - remove custom quirks from `custom_quirks_path` - - add quirks from `xternal_tuya_device_quirks` + - add quirks from `devices` subfolder - add custom quirks from `custom_quirks_path` """ if custom_quirks_path is not None: TUYA_QUIRKS_REGISTRY.purge_custom_quirks(custom_quirks_path) - # Import all quirks in the `xternal_tuya_device_quirks` package first + # Import all quirks in the `tuya_device_handlers` package first + from . import devices # noqa: PLC0415 + for _importer, modname, _ispkg in pkgutil.walk_packages( - path=__path__, - prefix=__name__ + ".", + path=devices.__path__, + prefix=devices.__name__ + ".", ): - _LOGGER.debug("Loading quirks module %r", modname) + _LOGGER.warning("Loading quirks module %r", modname) importlib.import_module(modname) if custom_quirks_path is None: diff --git a/homeassistant/components/tuya/xternal_tuya_quirks/device_quirk.py b/homeassistant/components/tuya/tuya_device_handlers/base_quirk.py similarity index 85% rename from homeassistant/components/tuya/xternal_tuya_quirks/device_quirk.py rename to homeassistant/components/tuya/tuya_device_handlers/base_quirk.py index d0e357916d8..d8394522b69 100644 --- a/homeassistant/components/tuya/xternal_tuya_quirks/device_quirk.py +++ b/homeassistant/components/tuya/tuya_device_handlers/base_quirk.py @@ -1,4 +1,4 @@ -"""Quirks registry.""" +"""Base quirk definition.""" from __future__ import annotations @@ -7,8 +7,8 @@ import inspect import pathlib from typing import TYPE_CHECKING, Self -from ..const import DPCode -from ..models import StateConversionFunction +from .const import TuyaDeviceCategory, TuyaDPCode +from .conversion import TuyaIntegerConversionFunction from .homeassistant import ( TuyaClimateHVACMode, TuyaCoverDeviceClass, @@ -38,9 +38,9 @@ class TuyaClimateDefinition(BaseTuyaDefinition): switch_only_hvac_mode: TuyaClimateHVACMode - current_temperature_state_conversion: StateConversionFunction | None = None - target_temperature_state_conversion: StateConversionFunction | None = None - target_temperature_command_conversion: StateConversionFunction | None = None + current_temperature_state_conversion: TuyaIntegerConversionFunction | None = None + target_temperature_state_conversion: TuyaIntegerConversionFunction | None = None + target_temperature_command_conversion: TuyaIntegerConversionFunction | None = None @dataclass(kw_only=True) @@ -77,8 +77,8 @@ class TuyaDeviceQuirk: def __init__(self) -> None: """Initialize the quirk.""" self._applies_to: list[tuple[str, str]] = [] - self.cover_definitions: list[TuyaCoverDefinition] = [] self.climate_definitions: list[TuyaClimateDefinition] = [] + self.cover_definitions: list[TuyaCoverDefinition] = [] self.select_definitions: list[TuyaSelectDefinition] = [] self.sensor_definitions: list[TuyaSensorDefinition] = [] self.switch_definitions: list[TuyaSwitchDefinition] = [] @@ -92,7 +92,7 @@ class TuyaDeviceQuirk: self.quirk_file = pathlib.Path(caller.f_code.co_filename) self.quirk_file_line = caller.f_lineno - def applies_to(self, *, category: str, product_id: str) -> Self: + def applies_to(self, *, category: TuyaDeviceCategory, product_id: str) -> Self: """Set the device type the quirk applies to.""" self._applies_to.append((category, product_id)) return self @@ -108,9 +108,12 @@ class TuyaDeviceQuirk: key: str, # Climate specific switch_only_hvac_mode: TuyaClimateHVACMode, - current_temperature_state_conversion: StateConversionFunction | None = None, - target_temperature_state_conversion: StateConversionFunction | None = None, - target_temperature_command_conversion: StateConversionFunction | None = None, + current_temperature_state_conversion: TuyaIntegerConversionFunction + | None = None, + target_temperature_state_conversion: TuyaIntegerConversionFunction + | None = None, + target_temperature_command_conversion: TuyaIntegerConversionFunction + | None = None, ) -> Self: """Add climate definition.""" self.climate_definitions.append( @@ -127,14 +130,14 @@ class TuyaDeviceQuirk: def add_cover( self, *, - key: DPCode, + key: TuyaDPCode, translation_key: str, translation_string: str, device_class: TuyaCoverDeviceClass | None = None, # Cover specific - current_state_dp_code: str | None = None, - current_position_dp_code: str | None = None, - set_position_dp_code: str | None = None, + current_state_dp_code: TuyaDPCode | None = None, + current_position_dp_code: TuyaDPCode | None = None, + set_position_dp_code: TuyaDPCode | None = None, ) -> Self: """Add cover definition.""" self.cover_definitions.append( @@ -153,7 +156,7 @@ class TuyaDeviceQuirk: def add_select( self, *, - key: DPCode, + key: TuyaDPCode, translation_key: str, translation_string: str, entity_category: TuyaEntityCategory | None = None, @@ -175,7 +178,7 @@ class TuyaDeviceQuirk: def add_sensor( self, *, - key: DPCode, + key: TuyaDPCode, translation_key: str, translation_string: str, device_class: TuyaSensorDeviceClass | None = None, @@ -197,7 +200,7 @@ class TuyaDeviceQuirk: def add_switch( self, *, - key: DPCode, + key: TuyaDPCode, translation_key: str, translation_string: str, entity_category: TuyaEntityCategory | None = None, diff --git a/homeassistant/components/tuya/tuya_device_handlers/const.py b/homeassistant/components/tuya/tuya_device_handlers/const.py new file mode 100644 index 00000000000..e8cdb779891 --- /dev/null +++ b/homeassistant/components/tuya/tuya_device_handlers/const.py @@ -0,0 +1,30 @@ +"""Constants for Tuya device handlers.""" + +from __future__ import annotations + +from enum import StrEnum + + +class TuyaDPCode(StrEnum): + """Tuya data-point codes.""" + + CHILD_LOCK = "child_lock" + CONTROL = "control" + CONTROL_BACK_MODE = "control_back_mode" + PERCENT_CONTROL = "percent_control" + TIME_TOTAL = "time_total" + + +class TuyaDeviceCategory(StrEnum): + """Tuya device categories.""" + + CL = "cl" + """Curtain + + https://developer.tuya.com/en/docs/iot/categorycl?id=Kaiuz1hnpo7df + """ + WK = "wk" + """Thermostat + + https://developer.tuya.com/en/docs/iot/f?id=K9gf45ld5l0t9 + """ diff --git a/homeassistant/components/tuya/tuya_device_handlers/conversion.py b/homeassistant/components/tuya/tuya_device_handlers/conversion.py new file mode 100644 index 00000000000..0c439f07923 --- /dev/null +++ b/homeassistant/components/tuya/tuya_device_handlers/conversion.py @@ -0,0 +1,49 @@ +"""Base quirk definition.""" + +from __future__ import annotations + +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Protocol + +from .utils import scale_value, scale_value_back + +if TYPE_CHECKING: + from tuya_sharing import CustomerDevice + + +class TuyaIntegerDefinition(Protocol): + """Definition of an integer type data.""" + + dpcode: str + min: int + max: int + scale: float + step: float + unit: str | None = None + type: str | None = None + + +type TuyaIntegerConversionFunction = Callable[ + [CustomerDevice, TuyaIntegerDefinition, Any], Any +] +"""Start conversion function: + + Args: + device: The Tuya device instance (CustomerDevice). + dptype: The DP type data (IntegerTypeData). + value: The value to convert. +""" + + +def scale_value_fixed_scale_1( + _: CustomerDevice, dptype: TuyaIntegerDefinition, value: Any +) -> float: + """Scale value, overriding scale to be 1.""" + return scale_value(value, dptype.step, 1) + + +def scale_value_back_fixed_scale_1( + _: CustomerDevice, dptype: TuyaIntegerDefinition, value: Any +) -> int: + """Unscale value, overriding scale to be 1.""" + return scale_value_back(value, dptype.step, 1) diff --git a/homeassistant/components/tuya/tuya_device_handlers/devices/__init__.py b/homeassistant/components/tuya/tuya_device_handlers/devices/__init__.py new file mode 100644 index 00000000000..7cd7ea00b35 --- /dev/null +++ b/homeassistant/components/tuya/tuya_device_handlers/devices/__init__.py @@ -0,0 +1,3 @@ +"""Quirks for Tuya.""" + +from __future__ import annotations diff --git a/homeassistant/components/tuya/tuya_device_handlers/devices/cl/__init__.py b/homeassistant/components/tuya/tuya_device_handlers/devices/cl/__init__.py new file mode 100644 index 00000000000..5f39d25e6b9 --- /dev/null +++ b/homeassistant/components/tuya/tuya_device_handlers/devices/cl/__init__.py @@ -0,0 +1,4 @@ +"""Quirks for Tuya CL category (curtain). + +https://developer.tuya.com/en/docs/iot/categorycl?id=Kaiuz1hnpo7df +""" diff --git a/homeassistant/components/tuya/tuya_device_handlers/devices/cl/cl_g1cp07dsqnbdbbki.py b/homeassistant/components/tuya/tuya_device_handlers/devices/cl/cl_g1cp07dsqnbdbbki.py new file mode 100644 index 00000000000..aa172351930 --- /dev/null +++ b/homeassistant/components/tuya/tuya_device_handlers/devices/cl/cl_g1cp07dsqnbdbbki.py @@ -0,0 +1,31 @@ +"""Quirks for Tuya.""" + +from __future__ import annotations + +from ... import TUYA_QUIRKS_REGISTRY, TuyaDeviceQuirk +from ...const import TuyaDeviceCategory, TuyaDPCode +from ...homeassistant import TuyaCoverDeviceClass, TuyaEntityCategory + +( + # This model has percent_state and percent_control but percent_state never + # gets updated - force percent_control instead + TuyaDeviceQuirk() + .applies_to(category=TuyaDeviceCategory.CL, product_id="g1cp07dsqnbdbbki") + .add_cover( + key=TuyaDPCode.CONTROL, + translation_key="curtain", + translation_string="[%key:component::cover::entity_component::curtain::name%]", + current_state_dp_code=TuyaDPCode.CONTROL, + current_position_dp_code=TuyaDPCode.PERCENT_CONTROL, + set_position_dp_code=TuyaDPCode.PERCENT_CONTROL, + device_class=TuyaCoverDeviceClass.CURTAIN, + ) + .add_select( + key=TuyaDPCode.CONTROL_BACK_MODE, + translation_key="curtain_motor_mode", + translation_string="Motor mode", + entity_category=TuyaEntityCategory.CONFIG, + state_translations={"forward": "Forward", "back": "Back"}, + ) + .register(TUYA_QUIRKS_REGISTRY) +) diff --git a/homeassistant/components/tuya/tuya_device_handlers/devices/cl/cl_lfkr93x0ukp5gaia.py b/homeassistant/components/tuya/tuya_device_handlers/devices/cl/cl_lfkr93x0ukp5gaia.py new file mode 100644 index 00000000000..aa69787a46c --- /dev/null +++ b/homeassistant/components/tuya/tuya_device_handlers/devices/cl/cl_lfkr93x0ukp5gaia.py @@ -0,0 +1,35 @@ +"""Quirks for Tuya.""" + +from __future__ import annotations + +from ... import TUYA_QUIRKS_REGISTRY, TuyaDeviceQuirk +from ...const import TuyaDeviceCategory, TuyaDPCode +from ...homeassistant import TuyaCoverDeviceClass, TuyaEntityCategory + +( + # This model has percent_control / percent_state / situation_set + # but they never get updated - use control instead to get the state + TuyaDeviceQuirk() + .applies_to(category=TuyaDeviceCategory.CL, product_id="lfkr93x0ukp5gaia") + .add_cover( + key=TuyaDPCode.CONTROL, + translation_key="curtain", + translation_string="[%key:component::cover::entity_component::curtain::name%]", + current_state_dp_code=TuyaDPCode.CONTROL, + device_class=TuyaCoverDeviceClass.CURTAIN, + ) + .add_select( + key=TuyaDPCode.CONTROL_BACK_MODE, + translation_key="curtain_motor_mode", + translation_string="Motor mode", + entity_category=TuyaEntityCategory.CONFIG, + state_translations={"forward": "Forward", "back": "Back"}, + ) + .add_sensor( + key=TuyaDPCode.TIME_TOTAL, + translation_key="last_operation_duration", + translation_string="Last operation duration", + entity_category=TuyaEntityCategory.DIAGNOSTIC, + ) + .register(TUYA_QUIRKS_REGISTRY) +) diff --git a/homeassistant/components/tuya/tuya_device_handlers/devices/wk/__init__.py b/homeassistant/components/tuya/tuya_device_handlers/devices/wk/__init__.py new file mode 100644 index 00000000000..b0a1a55ea88 --- /dev/null +++ b/homeassistant/components/tuya/tuya_device_handlers/devices/wk/__init__.py @@ -0,0 +1,4 @@ +"""Quirks for Tuya WK category (thermostat). + +https://developer.tuya.com/en/docs/iot/f?id=K9gf45ld5l0t9 +""" diff --git a/homeassistant/components/tuya/tuya_device_handlers/devices/wk/wk_IAYz2WK1th0cMLmL.py b/homeassistant/components/tuya/tuya_device_handlers/devices/wk/wk_IAYz2WK1th0cMLmL.py new file mode 100644 index 00000000000..5b0b0070812 --- /dev/null +++ b/homeassistant/components/tuya/tuya_device_handlers/devices/wk/wk_IAYz2WK1th0cMLmL.py @@ -0,0 +1,28 @@ +"""Quirks for Tuya.""" + +from __future__ import annotations + +from ... import TUYA_QUIRKS_REGISTRY, TuyaDeviceQuirk +from ...const import TuyaDeviceCategory, TuyaDPCode +from ...conversion import scale_value_back_fixed_scale_1, scale_value_fixed_scale_1 +from ...homeassistant import TuyaClimateHVACMode, TuyaEntityCategory + +( + # This model has invalid scale 0 for temperature dps - force scale 1 + TuyaDeviceQuirk() + .applies_to(category=TuyaDeviceCategory.WK, product_id="IAYz2WK1th0cMLmL") + .add_climate( + key="wk", + switch_only_hvac_mode=TuyaClimateHVACMode.HEAT_COOL, + current_temperature_state_conversion=scale_value_fixed_scale_1, + target_temperature_state_conversion=scale_value_fixed_scale_1, + target_temperature_command_conversion=scale_value_back_fixed_scale_1, + ) + .add_switch( + key=TuyaDPCode.CHILD_LOCK, + translation_key="child_lock", + translation_string="Child lock", + entity_category=TuyaEntityCategory.CONFIG, + ) + .register(TUYA_QUIRKS_REGISTRY) +) diff --git a/homeassistant/components/tuya/xternal_tuya_quirks/homeassistant.py b/homeassistant/components/tuya/tuya_device_handlers/homeassistant.py similarity index 97% rename from homeassistant/components/tuya/xternal_tuya_quirks/homeassistant.py rename to homeassistant/components/tuya/tuya_device_handlers/homeassistant.py index 8079b1a776c..607bc89735f 100644 --- a/homeassistant/components/tuya/xternal_tuya_quirks/homeassistant.py +++ b/homeassistant/components/tuya/tuya_device_handlers/homeassistant.py @@ -1,20 +1,10 @@ -"""Quirks registry.""" +"""Copy of Home Assistant constants.""" from __future__ import annotations from enum import StrEnum -def parse_enum[T: StrEnum](enum_class: type[T], value: str | None) -> T | None: - """Parse a string to an enum member, or return None if value is None.""" - if value is None: - return None - try: - return enum_class(value) - except ValueError: - return None - - class TuyaEntityCategory(StrEnum): """Category of an entity. diff --git a/homeassistant/components/tuya/xternal_tuya_quirks/registry.py b/homeassistant/components/tuya/tuya_device_handlers/registry.py similarity index 97% rename from homeassistant/components/tuya/xternal_tuya_quirks/registry.py rename to homeassistant/components/tuya/tuya_device_handlers/registry.py index 9a552614d84..de41267adf4 100644 --- a/homeassistant/components/tuya/xternal_tuya_quirks/registry.py +++ b/homeassistant/components/tuya/tuya_device_handlers/registry.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Self from tuya_sharing import CustomerDevice if TYPE_CHECKING: - from .device_quirk import TuyaDeviceQuirk + from .base_quirk import TuyaDeviceQuirk _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tuya/xternal_tuya_quirks/utils.py b/homeassistant/components/tuya/tuya_device_handlers/utils.py similarity index 54% rename from homeassistant/components/tuya/xternal_tuya_quirks/utils.py rename to homeassistant/components/tuya/tuya_device_handlers/utils.py index 7a883ecc3c8..0f2dc9976b4 100644 --- a/homeassistant/components/tuya/xternal_tuya_quirks/utils.py +++ b/homeassistant/components/tuya/tuya_device_handlers/utils.py @@ -1,5 +1,9 @@ """Common utility functions for Tuya quirks.""" +from __future__ import annotations + +from enum import StrEnum + def scale_value(value: int, step: float, scale: float) -> float: """Official scaling function from Tuya. @@ -16,3 +20,17 @@ def scale_value_back(value: float, step: float, scale: float) -> int: """ return int(value * (10**scale) / step) + + +def parse_enum[T: StrEnum](enum_class: type[T], value: str | None) -> T | None: + """Parse a string to an enum member. + + Return None if value is None or if the value does not correspond to any + enum member. + """ + if value is None: + return None + try: + return enum_class(value) + except ValueError: + return None diff --git a/homeassistant/components/tuya/xternal_tuya_device_quirks/cl.py b/homeassistant/components/tuya/xternal_tuya_device_quirks/cl.py deleted file mode 100644 index 5424b4b3de2..00000000000 --- a/homeassistant/components/tuya/xternal_tuya_device_quirks/cl.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Quirks for Tuya.""" - -from __future__ import annotations - -from ..const import DPCode -from ..xternal_tuya_quirks import TUYA_QUIRKS_REGISTRY, TuyaDeviceQuirk -from ..xternal_tuya_quirks.homeassistant import TuyaCoverDeviceClass, TuyaEntityCategory - -( - # This model has percent_state and percent_control but percent_state never - # gets updated - force percent_control instead - TuyaDeviceQuirk() - .applies_to(category="cl", product_id="g1cp07dsqnbdbbki") - .add_cover( - key=DPCode.CONTROL, - translation_key="curtain", - translation_string="[%key:component::cover::entity_component::curtain::name%]", - current_state_dp_code=DPCode.CONTROL, - current_position_dp_code=DPCode.PERCENT_CONTROL, - set_position_dp_code=DPCode.PERCENT_CONTROL, - device_class=TuyaCoverDeviceClass.CURTAIN, - ) - .add_select( - key=DPCode.CONTROL_BACK_MODE, - translation_key="curtain_motor_mode", - translation_string="Motor mode", - entity_category=TuyaEntityCategory.CONFIG, - state_translations={"forward": "Forward", "back": "Back"}, - ) - .register(TUYA_QUIRKS_REGISTRY) -) -( - # This model has percent_control / percent_state / situation_set - # but they never get updated - use control instead to get the state - TuyaDeviceQuirk() - .applies_to(category="cl", product_id="lfkr93x0ukp5gaia") - .add_cover( - key=DPCode.CONTROL, - translation_key="curtain", - translation_string="[%key:component::cover::entity_component::curtain::name%]", - current_state_dp_code=DPCode.CONTROL, - device_class=TuyaCoverDeviceClass.CURTAIN, - ) - .add_select( - key=DPCode.CONTROL_BACK_MODE, - translation_key="curtain_motor_mode", - translation_string="Motor mode", - entity_category=TuyaEntityCategory.CONFIG, - state_translations={"forward": "Forward", "back": "Back"}, - ) - .add_sensor( - key=DPCode.TIME_TOTAL, - translation_key="last_operation_duration", - translation_string="Last operation duration", - entity_category=TuyaEntityCategory.DIAGNOSTIC, - ) - .register(TUYA_QUIRKS_REGISTRY) -) diff --git a/homeassistant/components/tuya/xternal_tuya_device_quirks/wk.py b/homeassistant/components/tuya/xternal_tuya_device_quirks/wk.py deleted file mode 100644 index 4efcb62989c..00000000000 --- a/homeassistant/components/tuya/xternal_tuya_device_quirks/wk.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Quirks for Tuya.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -from tuya_sharing import CustomerDevice - -from ..const import DPCode -from ..models import EnumTypeData, IntegerTypeData -from ..xternal_tuya_quirks import TUYA_QUIRKS_REGISTRY, TuyaDeviceQuirk -from ..xternal_tuya_quirks.homeassistant import TuyaClimateHVACMode, TuyaEntityCategory -from ..xternal_tuya_quirks.utils import scale_value, scale_value_back - - -def _scale_value_force_scale_1( - _device: CustomerDevice, dptype: EnumTypeData | IntegerTypeData | None, value: Any -) -> float: - """Scale value to scale 1.""" - if TYPE_CHECKING: - assert isinstance(dptype, IntegerTypeData) - assert isinstance(value, int) - return scale_value(value, dptype.step, 1) - - -def _scale_value_back_force_scale_1( - _device: CustomerDevice, dptype: EnumTypeData | IntegerTypeData | None, value: Any -) -> int: - """Unscale value to scale 1.""" - if TYPE_CHECKING: - assert isinstance(dptype, IntegerTypeData) - assert isinstance(value, float) - return scale_value_back(value, dptype.step, 1) - - -( - # This model has invalid scale 0 for temperature dps - force scale 1 - TuyaDeviceQuirk() - .applies_to(category="wk", product_id="IAYz2WK1th0cMLmL") - .add_climate( - key="wk", - switch_only_hvac_mode=TuyaClimateHVACMode.HEAT_COOL, - current_temperature_state_conversion=_scale_value_force_scale_1, - target_temperature_state_conversion=_scale_value_force_scale_1, - target_temperature_command_conversion=_scale_value_back_force_scale_1, - ) - .add_switch( - key=DPCode.CHILD_LOCK, - translation_key="child_lock", - translation_string="Child lock", - entity_category=TuyaEntityCategory.CONFIG, - ) - .register(TUYA_QUIRKS_REGISTRY) -) diff --git a/homeassistant/components/tuya/xternal_tuya_quirks/__init__.py b/homeassistant/components/tuya/xternal_tuya_quirks/__init__.py deleted file mode 100644 index 55342a7ea69..00000000000 --- a/homeassistant/components/tuya/xternal_tuya_quirks/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Quirks for Tuya.""" - -from __future__ import annotations - -from .device_quirk import ( - TuyaClimateDefinition, - TuyaCoverDefinition, - TuyaDeviceQuirk, - TuyaSelectDefinition, - TuyaSensorDefinition, - TuyaSwitchDefinition, -) -from .homeassistant import parse_enum -from .registry import QuirksRegistry - -__all__ = [ - "TUYA_QUIRKS_REGISTRY", - "QuirksRegistry", - "TuyaClimateDefinition", - "TuyaCoverDefinition", - "TuyaDeviceQuirk", - "TuyaSelectDefinition", - "TuyaSensorDefinition", - "TuyaSwitchDefinition", - "parse_enum", -] - -TUYA_QUIRKS_REGISTRY = QuirksRegistry() diff --git a/tests/components/tuya/test_quirks.py b/tests/components/tuya/test_quirks.py index 5b4ae9860ab..8e805e0f3e9 100644 --- a/tests/components/tuya/test_quirks.py +++ b/tests/components/tuya/test_quirks.py @@ -8,15 +8,13 @@ import pytest from homeassistant.components.cover import CoverDeviceClass from homeassistant.components.sensor import SensorDeviceClass -from homeassistant.components.tuya.xternal_tuya_device_quirks import ( - register_tuya_quirks, -) -from homeassistant.components.tuya.xternal_tuya_quirks import ( +from homeassistant.components.tuya.tuya_device_handlers import ( TUYA_QUIRKS_REGISTRY, QuirksRegistry, TuyaDeviceQuirk, + register_tuya_quirks, ) -from homeassistant.components.tuya.xternal_tuya_quirks.device_quirk import ( +from homeassistant.components.tuya.tuya_device_handlers.base_quirk import ( BaseTuyaDefinition, ) from homeassistant.const import EntityCategory, Platform