diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index 727587be6266..224778d4802d 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -5,6 +5,7 @@ from __future__ import annotations import logging from typing import Any, NamedTuple +from tuya_device_handlers.devices import register_tuya_quirks from tuya_sharing import ( CustomerDevice, Manager, @@ -31,7 +32,6 @@ from .const import ( TUYA_DISCOVERY_NEW, TUYA_HA_SIGNAL_UPDATE_ENTITY, ) -from .tuya_device_handlers.devices 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 97b6664aad40..8ab30b486ca8 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -5,6 +5,11 @@ from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING, Any +from tuya_device_handlers import TUYA_QUIRKS_REGISTRY +from tuya_device_handlers.builder import ( + TuyaClimateDefinition, + TuyaIntegerConversionFunction, +) from tuya_sharing import CustomerDevice, Manager from homeassistant.components.climate import ( @@ -27,11 +32,6 @@ from . import TuyaConfigEntry from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType from .entity import TuyaEntity from .models import IntegerTypeData -from .tuya_device_handlers import TUYA_QUIRKS_REGISTRY -from .tuya_device_handlers.builder import ( - TuyaClimateDefinition, - TuyaIntegerConversionFunction, -) from .util import get_dpcode TUYA_HVAC_TO_HA = { diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index 1b3b02eb17e8..dc8d73d8e4ff 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -5,6 +5,9 @@ from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING, Any +from tuya_device_handlers import TUYA_QUIRKS_REGISTRY +from tuya_device_handlers.builder import TuyaCoverDefinition +from tuya_device_handlers.helpers import parse_enum from tuya_sharing import CustomerDevice, Manager from homeassistant.components.cover import ( @@ -23,9 +26,6 @@ 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 -from .tuya_device_handlers.builder import TuyaCoverDefinition -from .tuya_device_handlers.helpers import parse_enum from .util import get_dpcode diff --git a/homeassistant/components/tuya/manifest.json b/homeassistant/components/tuya/manifest.json index cfc074a6a46e..54ef7ad3974a 100644 --- a/homeassistant/components/tuya/manifest.json +++ b/homeassistant/components/tuya/manifest.json @@ -43,5 +43,8 @@ "integration_type": "hub", "iot_class": "cloud_push", "loggers": ["tuya_sharing"], - "requirements": ["tuya-device-sharing-sdk==0.2.4"] + "requirements": [ + "tuya-device-handlers==0.0.1", + "tuya-device-sharing-sdk==0.2.4" + ] } diff --git a/homeassistant/components/tuya/models.py b/homeassistant/components/tuya/models.py index ae246238d51f..a5e12c22d7f0 100644 --- a/homeassistant/components/tuya/models.py +++ b/homeassistant/components/tuya/models.py @@ -8,8 +8,9 @@ import json import struct from typing import Self +from tuya_device_handlers.helpers import TuyaIntegerTypeDefinition + from .const import DPCode -from .tuya_device_handlers.helpers import TuyaIntegerTypeDefinition from .util import remap_value diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index 71eb664cb076..99f00dd138a2 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -2,6 +2,9 @@ from __future__ import annotations +from tuya_device_handlers import TUYA_QUIRKS_REGISTRY +from tuya_device_handlers.builder import TuyaSelectDefinition +from tuya_device_handlers.helpers import parse_enum from tuya_sharing import CustomerDevice, Manager from homeassistant.components.select import SelectEntity, SelectEntityDescription @@ -13,9 +16,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import TuyaConfigEntry from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType from .entity import TuyaEntity -from .tuya_device_handlers import TUYA_QUIRKS_REGISTRY -from .tuya_device_handlers.builder import TuyaSelectDefinition -from .tuya_device_handlers.helpers import 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 efdc174da67c..dc2cd03ea68f 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -6,6 +6,9 @@ from collections.abc import Callable from dataclasses import dataclass from typing import Any +from tuya_device_handlers import TUYA_QUIRKS_REGISTRY +from tuya_device_handlers.builder import TuyaSensorDefinition +from tuya_device_handlers.helpers import parse_enum from tuya_sharing import CustomerDevice, Manager from tuya_sharing.device import DeviceStatusRange @@ -44,9 +47,6 @@ from .const import ( ) from .entity import TuyaEntity from .models import ComplexValue, ElectricityValue, EnumTypeData, IntegerTypeData -from .tuya_device_handlers import TUYA_QUIRKS_REGISTRY -from .tuya_device_handlers.builder import TuyaSensorDefinition -from .tuya_device_handlers.helpers import parse_enum from .util import get_dptype _WIND_DIRECTIONS = { diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 2762d527064a..bdf89a68f5a6 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -5,6 +5,9 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any +from tuya_device_handlers import TUYA_QUIRKS_REGISTRY +from tuya_device_handlers.builder import TuyaSwitchDefinition +from tuya_device_handlers.helpers import parse_enum from tuya_sharing import CustomerDevice, Manager from homeassistant.components.switch import ( @@ -27,9 +30,6 @@ from homeassistant.helpers.issue_registry import ( from . import TuyaConfigEntry from .const import DOMAIN, TUYA_DISCOVERY_NEW, DeviceCategory, DPCode from .entity import TuyaEntity -from .tuya_device_handlers import TUYA_QUIRKS_REGISTRY -from .tuya_device_handlers.builder import TuyaSwitchDefinition -from .tuya_device_handlers.helpers import parse_enum @dataclass(frozen=True, kw_only=True) diff --git a/homeassistant/components/tuya/tuya_device_handlers/__init__.py b/homeassistant/components/tuya/tuya_device_handlers/__init__.py deleted file mode 100644 index 8a1a8e1a9722..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Tuya device handler.""" - -from __future__ import annotations - -from .registry import QuirksRegistry - -__all__ = [ - "TUYA_QUIRKS_REGISTRY", -] - -TUYA_QUIRKS_REGISTRY = QuirksRegistry() diff --git a/homeassistant/components/tuya/tuya_device_handlers/builder/__init__.py b/homeassistant/components/tuya/tuya_device_handlers/builder/__init__.py deleted file mode 100644 index cc410286a95c..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/builder/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Tuya device handler.""" - -from __future__ import annotations - -from .base_quirk import ( - TuyaClimateDefinition, - TuyaCoverDefinition, - TuyaDeviceQuirk, - TuyaIntegerConversionFunction, - TuyaSelectDefinition, - TuyaSensorDefinition, - TuyaSwitchDefinition, -) - -__all__ = [ - "TuyaClimateDefinition", - "TuyaCoverDefinition", - "TuyaDeviceQuirk", - "TuyaIntegerConversionFunction", - "TuyaSelectDefinition", - "TuyaSensorDefinition", - "TuyaSwitchDefinition", -] diff --git a/homeassistant/components/tuya/tuya_device_handlers/builder/base_quirk.py b/homeassistant/components/tuya/tuya_device_handlers/builder/base_quirk.py deleted file mode 100644 index ef60e3aa0b58..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/builder/base_quirk.py +++ /dev/null @@ -1,240 +0,0 @@ -"""Base quirk definition.""" - -from __future__ import annotations - -from collections.abc import Callable -from dataclasses import dataclass -import inspect -import pathlib -from typing import TYPE_CHECKING, Any, Self - -from tuya_device_handlers.helpers import ( - TuyaClimateHVACMode, - TuyaCoverDeviceClass, - TuyaDeviceCategory, - TuyaDPCode, - TuyaEntityCategory, - TuyaSensorDeviceClass, -) - -if TYPE_CHECKING: - from tuya_sharing import CustomerDevice - - from ..helpers import TuyaIntegerTypeDefinition - from ..registry import QuirksRegistry - - -type TuyaIntegerConversionFunction = Callable[ - [CustomerDevice, TuyaIntegerTypeDefinition, Any], Any -] -"""Start conversion function: - - Args: - device: The Tuya device instance (CustomerDevice). - dptype: The DP type definition (TuyaIntegerTypeDefinition). - value: The value to convert. -""" - - -@dataclass -class BaseTuyaDefinition: - """Definition for a Tuya entity.""" - - key: str - translation_key: str | None = None - translation_string: str | None = None - state_translations: dict[str, str] | None = None - device_class: str | None = None - entity_category: str | None = None - - -@dataclass(kw_only=True) -class TuyaClimateDefinition(BaseTuyaDefinition): - """Definition for a climate entity.""" - - switch_only_hvac_mode: TuyaClimateHVACMode - - current_temperature_dp_code: TuyaDPCode | None = None - current_temperature_state_conversion: TuyaIntegerConversionFunction | None = None - target_temperature_dp_code: TuyaDPCode | None = None - target_temperature_state_conversion: TuyaIntegerConversionFunction | None = None - target_temperature_command_conversion: TuyaIntegerConversionFunction | None = None - - -@dataclass(kw_only=True) -class TuyaCoverDefinition(BaseTuyaDefinition): - """Definition for a cover entity.""" - - device_class: TuyaCoverDeviceClass | None = None - - current_state_dp_code: str | None = None - current_position_dp_code: str | None = None - set_position_dp_code: str | None = None - - -@dataclass(kw_only=True) -class TuyaSelectDefinition(BaseTuyaDefinition): - """Definition for a select entity.""" - - -@dataclass(kw_only=True) -class TuyaSensorDefinition(BaseTuyaDefinition): - """Definition for a sensor entity.""" - - device_class: TuyaSensorDeviceClass | None = None - - -@dataclass(kw_only=True) -class TuyaSwitchDefinition(BaseTuyaDefinition): - """Definition for a switch entity.""" - - -class TuyaDeviceQuirk: - """Quirk for Tuya device.""" - - def __init__(self) -> None: - """Initialize the quirk.""" - self._applies_to: list[tuple[TuyaDeviceCategory, str]] = [] - 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] = [] - - current_frame = inspect.currentframe() - if TYPE_CHECKING: - assert current_frame is not None - caller = current_frame.f_back - if TYPE_CHECKING: - assert caller is not None - self.quirk_file = pathlib.Path(caller.f_code.co_filename) - self.quirk_file_line = caller.f_lineno - - 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 - - def register(self, registry: QuirksRegistry) -> None: - """Register the quirk in the registry.""" - for category, product_id in self._applies_to: - registry.register(category, product_id, self) - - def add_climate( - self, - *, - key: str, - # Climate specific - switch_only_hvac_mode: TuyaClimateHVACMode, - current_temperature_dp_code: TuyaDPCode | None = None, - current_temperature_state_conversion: TuyaIntegerConversionFunction - | None = None, - target_temperature_dp_code: TuyaDPCode | None = None, - target_temperature_state_conversion: TuyaIntegerConversionFunction - | None = None, - target_temperature_command_conversion: TuyaIntegerConversionFunction - | None = None, - ) -> Self: - """Add climate definition.""" - self.climate_definitions.append( - TuyaClimateDefinition( - key=key, - switch_only_hvac_mode=switch_only_hvac_mode, - current_temperature_dp_code=current_temperature_dp_code, - current_temperature_state_conversion=current_temperature_state_conversion, - target_temperature_dp_code=target_temperature_dp_code, - target_temperature_state_conversion=target_temperature_state_conversion, - target_temperature_command_conversion=target_temperature_command_conversion, - ) - ) - return self - - def add_cover( - self, - *, - key: TuyaDPCode, - translation_key: str, - translation_string: str, - device_class: TuyaCoverDeviceClass | None = None, - # Cover specific - 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( - TuyaCoverDefinition( - key=key, - translation_key=translation_key, - translation_string=translation_string, - device_class=device_class, - current_state_dp_code=current_state_dp_code, - current_position_dp_code=current_position_dp_code, - set_position_dp_code=set_position_dp_code, - ) - ) - return self - - def add_select( - self, - *, - key: TuyaDPCode, - translation_key: str, - translation_string: str, - entity_category: TuyaEntityCategory | None = None, - # Select specific - state_translations: dict[str, str] | None = None, - ) -> Self: - """Add select definition.""" - self.select_definitions.append( - TuyaSelectDefinition( - key=key, - translation_key=translation_key, - translation_string=translation_string, - entity_category=entity_category, - state_translations=state_translations, - ) - ) - return self - - def add_sensor( - self, - *, - key: TuyaDPCode, - translation_key: str, - translation_string: str, - device_class: TuyaSensorDeviceClass | None = None, - entity_category: TuyaEntityCategory | None = None, - # Sensor specific - ) -> Self: - """Add sensor definition.""" - self.sensor_definitions.append( - TuyaSensorDefinition( - key=key, - translation_key=translation_key, - translation_string=translation_string, - device_class=device_class, - entity_category=entity_category, - ) - ) - return self - - def add_switch( - self, - *, - key: TuyaDPCode, - translation_key: str, - translation_string: str, - entity_category: TuyaEntityCategory | None = None, - # Switch specific - ) -> Self: - """Add switch definition.""" - self.switch_definitions.append( - TuyaSwitchDefinition( - key=key, - translation_key=translation_key, - translation_string=translation_string, - entity_category=entity_category, - ) - ) - return self diff --git a/homeassistant/components/tuya/tuya_device_handlers/devices/__init__.py b/homeassistant/components/tuya/tuya_device_handlers/devices/__init__.py deleted file mode 100644 index e9904a605ca1..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/devices/__init__.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Quirks for Tuya.""" - -from __future__ import annotations - -import importlib -import logging -import pathlib -import pkgutil -import sys - -from .. import TUYA_QUIRKS_REGISTRY - -_LOGGER = logging.getLogger(__name__) - - -def register_tuya_quirks(custom_quirks_path: str | None = None) -> None: - """Register all available quirks. - - - remove custom quirks from `custom_quirks_path` - - 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 `tuya_device_handlers` package first - for _importer, modname, _ispkg in pkgutil.walk_packages( - path=__path__, - prefix=__name__ + ".", - ): - _LOGGER.debug("Loading quirks module %r", modname) - importlib.import_module(modname) - - if custom_quirks_path is None: - return - - path = pathlib.Path(custom_quirks_path) - _LOGGER.debug("Loading custom quirks from %r", path) - - loaded = False - - # Treat the custom quirk path (e.g. `/config/tuya_quirks/`) itself as a module - for importer, modname, _ispkg in pkgutil.walk_packages(path=[str(path)]): - _LOGGER.debug("Loading custom quirk module %r", modname) - - try: - spec = importer.find_spec(modname) # type: ignore[call-arg] - module = importlib.util.module_from_spec(spec) # type: ignore[arg-type] - sys.modules[modname] = module - spec.loader.exec_module(module) # type: ignore[union-attr] - except Exception: - _LOGGER.exception("Unexpected exception importing custom quirk %r", modname) - else: - loaded = True - - if loaded: - _LOGGER.warning( - "Loaded custom quirks. Please contribute them to https://github.com/TBD" - ) diff --git a/homeassistant/components/tuya/tuya_device_handlers/devices/cl/__init__.py b/homeassistant/components/tuya/tuya_device_handlers/devices/cl/__init__.py deleted file mode 100644 index 5f39d25e6b9a..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/devices/cl/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""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 deleted file mode 100644 index 1dea2beb7b2d..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/devices/cl/cl_g1cp07dsqnbdbbki.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Quirks for Tuya.""" - -from __future__ import annotations - -from ... import TUYA_QUIRKS_REGISTRY -from ...builder import TuyaDeviceQuirk -from ...helpers import ( - TuyaCoverDeviceClass, - TuyaDeviceCategory, - TuyaDPCode, - 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 deleted file mode 100644 index 28ea9b43d050..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/devices/cl/cl_lfkr93x0ukp5gaia.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Quirks for Tuya.""" - -from __future__ import annotations - -from ... import TUYA_QUIRKS_REGISTRY -from ...builder import TuyaDeviceQuirk -from ...helpers import ( - TuyaCoverDeviceClass, - TuyaDeviceCategory, - TuyaDPCode, - 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 deleted file mode 100644 index b0a1a55ea88a..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/devices/wk/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""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 deleted file mode 100644 index aea9d7e241ce..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/devices/wk/wk_IAYz2WK1th0cMLmL.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Quirks for Tuya.""" - -from __future__ import annotations - -from ... import TUYA_QUIRKS_REGISTRY -from ...builder import TuyaDeviceQuirk -from ...helpers import ( - TuyaClimateHVACMode, - TuyaDeviceCategory, - TuyaDPCode, - TuyaEntityCategory, -) - -( - TuyaDeviceQuirk() - .applies_to(category=TuyaDeviceCategory.WK, product_id="IAYz2WK1th0cMLmL") - .add_climate( - key="wk", - switch_only_hvac_mode=TuyaClimateHVACMode.HEAT_COOL, - current_temperature_dp_code=TuyaDPCode.UPPER_TEMP, - # UPPER_TEMP uses incorrect scale 1 / step 5 - convert to proper temperature - current_temperature_state_conversion=lambda _device, _def, value: value / 2, - # TEMP_SET uses incorrect scale 0 / step 5 - convert to proper temperature - target_temperature_dp_code=TuyaDPCode.TEMP_SET, - target_temperature_state_conversion=lambda _device, _def, value: value / 2, - target_temperature_command_conversion=lambda _device, _def, value: value * 2, - ) - .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/tuya_device_handlers/helpers/__init__.py b/homeassistant/components/tuya/tuya_device_handlers/helpers/__init__.py deleted file mode 100644 index 6c3f5cbc3776..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/helpers/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Tuya models.""" - -from __future__ import annotations - -from .const import TuyaDeviceCategory, TuyaDPCode -from .homeassistant import ( - TuyaClimateHVACMode, - TuyaCoverDeviceClass, - TuyaEntityCategory, - TuyaSensorDeviceClass, -) -from .models import TuyaIntegerTypeDefinition -from .utils import parse_enum - -__all__ = [ - "TuyaClimateHVACMode", - "TuyaCoverDeviceClass", - "TuyaDPCode", - "TuyaDeviceCategory", - "TuyaEntityCategory", - "TuyaIntegerTypeDefinition", - "TuyaSensorDeviceClass", - "parse_enum", -] diff --git a/homeassistant/components/tuya/tuya_device_handlers/helpers/const.py b/homeassistant/components/tuya/tuya_device_handlers/helpers/const.py deleted file mode 100644 index 8b5b99b3b4ab..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/helpers/const.py +++ /dev/null @@ -1,32 +0,0 @@ -"""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" - TEMP_SET = "temp_set" - TIME_TOTAL = "time_total" - UPPER_TEMP = "upper_temp" - - -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/helpers/homeassistant.py b/homeassistant/components/tuya/tuya_device_handlers/helpers/homeassistant.py deleted file mode 100644 index 607bc89735fb..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/helpers/homeassistant.py +++ /dev/null @@ -1,480 +0,0 @@ -"""Copy of Home Assistant constants.""" - -from __future__ import annotations - -from enum import StrEnum - - -class TuyaEntityCategory(StrEnum): - """Category of an entity. - - An entity with a category will: - - Not be exposed to cloud, Alexa, or Google Assistant components - - Not be included in indirect service calls to devices or areas - """ - - # Config: An entity which allows changing the configuration of a device. - CONFIG = "config" - - # Diagnostic: An entity exposing some configuration parameter, - # or diagnostics of a device. - DIAGNOSTIC = "diagnostic" - - -class TuyaClimateHVACMode(StrEnum): - """HVAC mode for climate devices.""" - - # All activity disabled / Device is off/standby - OFF = "off" - - # Heating - HEAT = "heat" - - # Cooling - COOL = "cool" - - # The device supports heating/cooling to a range - HEAT_COOL = "heat_cool" - - # The temperature is set based on a schedule, learned behavior, AI or some - # other related mechanism. User is not able to adjust the temperature - AUTO = "auto" - - # Device is in Dry/Humidity mode - DRY = "dry" - - # Only the fan is on, not fan and another mode like cool - FAN_ONLY = "fan_only" - - -class TuyaCoverDeviceClass(StrEnum): - """Device class for cover.""" - - # Keep synchronized with homeassistant - AWNING = "awning" - BLIND = "blind" - CURTAIN = "curtain" - DAMPER = "damper" - DOOR = "door" - GARAGE = "garage" - GATE = "gate" - SHADE = "shade" - SHUTTER = "shutter" - WINDOW = "window" - - -class TuyaSensorDeviceClass(StrEnum): - """Device class for sensors.""" - - # Non-numerical device classes - DATE = "date" - """Date. - - Unit of measurement: `None` - - ISO8601 format: https://en.wikipedia.org/wiki/ISO_8601 - """ - - ENUM = "enum" - """Enumeration. - - Provides a fixed list of options the state of the sensor can be in. - - Unit of measurement: `None` - """ - - TIMESTAMP = "timestamp" - """Timestamp. - - Unit of measurement: `None` - - ISO8601 format: https://en.wikipedia.org/wiki/ISO_8601 - """ - - # Numerical device classes, these should be aligned with NumberDeviceClass - ABSOLUTE_HUMIDITY = "absolute_humidity" - """Absolute humidity. - - Unit of measurement: `g/m³`, `mg/m³` - """ - - APPARENT_POWER = "apparent_power" - """Apparent power. - - Unit of measurement: `mVA`, `VA`, `kVA` - """ - - AQI = "aqi" - """Air Quality Index. - - Unit of measurement: `None` - """ - - AREA = "area" - """Area - - Unit of measurement: `UnitOfArea` units - """ - - ATMOSPHERIC_PRESSURE = "atmospheric_pressure" - """Atmospheric pressure. - - Unit of measurement: `UnitOfPressure` units - """ - - BATTERY = "battery" - """Percentage of battery that is left. - - Unit of measurement: `%` - """ - - BLOOD_GLUCOSE_CONCENTRATION = "blood_glucose_concentration" - """Blood glucose concentration. - - Unit of measurement: `mg/dL`, `mmol/L` - """ - - CO = "carbon_monoxide" - """Carbon Monoxide gas concentration. - - Unit of measurement: `ppm` (parts per million) - """ - - CO2 = "carbon_dioxide" - """Carbon Dioxide gas concentration. - - Unit of measurement: `ppm` (parts per million) - """ - - CONDUCTIVITY = "conductivity" - """Conductivity. - - Unit of measurement: `S/cm`, `mS/cm`, `μS/cm` - """ - - CURRENT = "current" - """Current. - - Unit of measurement: `A`, `mA` - """ - - DATA_RATE = "data_rate" - """Data rate. - - Unit of measurement: UnitOfDataRate - """ - - DATA_SIZE = "data_size" - """Data size. - - Unit of measurement: UnitOfInformation - """ - - DISTANCE = "distance" - """Generic distance. - - Unit of measurement: `LENGTH_*` units - - SI /metric: `mm`, `cm`, `m`, `km` - - USCS / imperial: `in`, `ft`, `yd`, `mi` - """ - - DURATION = "duration" - """Fixed duration. - - Unit of measurement: `d`, `h`, `min`, `s`, `ms`, `μs` - """ - - ENERGY = "energy" - """Energy. - - Use this device class for sensors measuring energy consumption, for example - electric energy consumption. - Unit of measurement: `J`, `kJ`, `MJ`, `GJ`, `mWh`, `Wh`, `kWh`, `MWh`, `GWh`, `TWh`, `cal`, `kcal`, `Mcal`, `Gcal` - """ - - ENERGY_DISTANCE = "energy_distance" - """Energy distance. - - Use this device class for sensors measuring energy by distance, for example the amount - of electric energy consumed by an electric car. - - Unit of measurement: `kWh/100km`, `Wh/km`, `mi/kWh`, `km/kWh` - """ - - ENERGY_STORAGE = "energy_storage" - """Stored energy. - - Use this device class for sensors measuring stored energy, for example the amount - of electric energy currently stored in a battery or the capacity of a battery. - - Unit of measurement: `J`, `kJ`, `MJ`, `GJ`, `mWh`, `Wh`, `kWh`, `MWh`, `GWh`, `TWh`, `cal`, `kcal`, `Mcal`, `Gcal` - """ - - FREQUENCY = "frequency" - """Frequency. - - Unit of measurement: `Hz`, `kHz`, `MHz`, `GHz` - """ - - GAS = "gas" - """Gas. - - Unit of measurement: - - SI / metric: `L`, `m³` - - USCS / imperial: `ft³`, `CCF`, `MCF` - """ - - HUMIDITY = "humidity" - """Relative humidity. - - Unit of measurement: `%` - """ - - ILLUMINANCE = "illuminance" - """Illuminance. - - Unit of measurement: `lx` - """ - - IRRADIANCE = "irradiance" - """Irradiance. - - Unit of measurement: - - SI / metric: `W/m²` - - USCS / imperial: `BTU/(h⋅ft²)` - """ - - MOISTURE = "moisture" - """Moisture. - - Unit of measurement: `%` - """ - - MONETARY = "monetary" - """Amount of money. - - Unit of measurement: ISO4217 currency code - - See https://en.wikipedia.org/wiki/ISO_4217#Active_codes for active codes - """ - - NITROGEN_DIOXIDE = "nitrogen_dioxide" - """Amount of NO2. - - Unit of measurement: `μg/m³` - """ - - NITROGEN_MONOXIDE = "nitrogen_monoxide" - """Amount of NO. - - Unit of measurement: `μg/m³` - """ - - NITROUS_OXIDE = "nitrous_oxide" - """Amount of N2O. - - Unit of measurement: `μg/m³` - """ - - OZONE = "ozone" - """Amount of O3. - - Unit of measurement: `μg/m³` - """ - - PH = "ph" - """Potential hydrogen (acidity/alkalinity). - - Unit of measurement: Unitless - """ - - PM1 = "pm1" - """Particulate matter <= 1 μm. - - Unit of measurement: `μg/m³` - """ - - PM10 = "pm10" - """Particulate matter <= 10 μm. - - Unit of measurement: `μg/m³` - """ - - PM25 = "pm25" - """Particulate matter <= 2.5 μm. - - Unit of measurement: `μg/m³` - """ - - PM4 = "pm4" - """Particulate matter <= 4 μm. - - Unit of measurement: `μg/m³` - """ - - POWER_FACTOR = "power_factor" - """Power factor. - - Unit of measurement: `%`, `None` - """ - - POWER = "power" - """Power. - - Unit of measurement: `mW`, `W`, `kW`, `MW`, `GW`, `TW`, `BTU/h` - """ - - PRECIPITATION = "precipitation" - """Accumulated precipitation. - - Unit of measurement: UnitOfPrecipitationDepth - - SI / metric: `cm`, `mm` - - USCS / imperial: `in` - """ - - PRECIPITATION_INTENSITY = "precipitation_intensity" - """Precipitation intensity. - - Unit of measurement: UnitOfVolumetricFlux - - SI /metric: `mm/d`, `mm/h` - - USCS / imperial: `in/d`, `in/h` - """ - - PRESSURE = "pressure" - """Pressure. - - Unit of measurement: - - `mbar`, `cbar`, `bar` - - `Pa`, `hPa`, `kPa` - - `inHg` - - `psi` - - `inH₂O` - """ - - REACTIVE_ENERGY = "reactive_energy" - """Reactive energy. - - Unit of measurement: `varh`, `kvarh` - """ - - REACTIVE_POWER = "reactive_power" - """Reactive power. - - Unit of measurement: `mvar`, `var`, `kvar` - """ - - SIGNAL_STRENGTH = "signal_strength" - """Signal strength. - - Unit of measurement: `dB`, `dBm` - """ - - SOUND_PRESSURE = "sound_pressure" - """Sound pressure. - - Unit of measurement: `dB`, `dBA` - """ - - SPEED = "speed" - """Generic speed. - - Unit of measurement: `SPEED_*` units or `UnitOfVolumetricFlux` - - SI /metric: `mm/d`, `mm/h`, `m/s`, `km/h`, `mm/s` - - USCS / imperial: `in/d`, `in/h`, `in/s`, `ft/s`, `mph` - - Nautical: `kn` - - Beaufort: `Beaufort` - """ - - SULPHUR_DIOXIDE = "sulphur_dioxide" - """Amount of SO2. - - Unit of measurement: `μg/m³` - """ - - TEMPERATURE = "temperature" - """Temperature. - - Unit of measurement: `°C`, `°F`, `K` - """ - - VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" - """Amount of VOC. - - Unit of measurement: `μg/m³`, `mg/m³` - """ - - VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts" - """Ratio of VOC. - - Unit of measurement: `ppm`, `ppb` - """ - - VOLTAGE = "voltage" - """Voltage. - - Unit of measurement: `V`, `mV`, `μV`, `kV`, `MV` - """ - - VOLUME = "volume" - """Generic volume. - - Unit of measurement: `VOLUME_*` units - - SI / metric: `mL`, `L`, `m³` - - USCS / imperial: `ft³`, `CCF`, `MCF`, `fl. oz.`, `gal` (warning: volumes expressed in - USCS/imperial units are currently assumed to be US volumes) - """ - - VOLUME_STORAGE = "volume_storage" - """Generic stored volume. - - Use this device class for sensors measuring stored volume, for example the amount - of fuel in a fuel tank. - - Unit of measurement: `VOLUME_*` units - - SI / metric: `mL`, `L`, `m³` - - USCS / imperial: `ft³`, `CCF`, `MCF`, `fl. oz.`, `gal` (warning: volumes expressed in - USCS/imperial units are currently assumed to be US volumes) - """ - - VOLUME_FLOW_RATE = "volume_flow_rate" - """Generic flow rate - - Unit of measurement: UnitOfVolumeFlowRate - - SI / metric: `m³/h`, `m³/min`, `m³/s`, `L/h`, `L/min`, `L/s`, `mL/s` - - USCS / imperial: `ft³/min`, `gal/min` - """ - - WATER = "water" - """Water. - - Unit of measurement: - - SI / metric: `m³`, `L` - - USCS / imperial: `ft³`, `CCF`, `MCF`, `gal` (warning: volumes expressed in - USCS/imperial units are currently assumed to be US volumes) - """ - - WEIGHT = "weight" - """Generic weight, represents a measurement of an object's mass. - - Weight is used instead of mass to fit with every day language. - - Unit of measurement: `MASS_*` units - - SI / metric: `μg`, `mg`, `g`, `kg` - - USCS / imperial: `oz`, `lb` - """ - - WIND_DIRECTION = "wind_direction" - """Wind direction. - - Unit of measurement: `°` - """ - - WIND_SPEED = "wind_speed" - """Wind speed. - - Unit of measurement: `SPEED_*` units - - SI /metric: `m/s`, `km/h` - - USCS / imperial: `ft/s`, `mph` - - Nautical: `kn` - - Beaufort: `Beaufort` - """ diff --git a/homeassistant/components/tuya/tuya_device_handlers/helpers/models.py b/homeassistant/components/tuya/tuya_device_handlers/helpers/models.py deleted file mode 100644 index 0772b9bac9ed..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/helpers/models.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Tuya models.""" - -from __future__ import annotations - -from dataclasses import dataclass - - -@dataclass -class TuyaIntegerTypeDefinition: - """Definition of an integer type data.""" - - dpcode: str - min: int - max: int - scale: float - step: float - unit: str | None = None - type: str | None = None - - @property - def max_scaled(self) -> float: - """Return the max scaled.""" - return self.scale_value(self.max) - - @property - def min_scaled(self) -> float: - """Return the min scaled.""" - return self.scale_value(self.min) - - @property - def step_scaled(self) -> float: - """Return the step scaled.""" - return self.step / (10**self.scale) - - def scale_value(self, value: float) -> float: - """Scale a value.""" - return value / (10**self.scale) - - def scale_value_back(self, value: float) -> int: - """Return raw value for scaled.""" - return int(value * (10**self.scale)) diff --git a/homeassistant/components/tuya/tuya_device_handlers/helpers/utils.py b/homeassistant/components/tuya/tuya_device_handlers/helpers/utils.py deleted file mode 100644 index d57f4689c1a2..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/helpers/utils.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Common utility functions for Tuya quirks.""" - -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. - - 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/tuya_device_handlers/registry.py b/homeassistant/components/tuya/tuya_device_handlers/registry.py deleted file mode 100644 index f92b003e596b..000000000000 --- a/homeassistant/components/tuya/tuya_device_handlers/registry.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Quirks registry.""" - -from __future__ import annotations - -import logging -from typing import TYPE_CHECKING, Self - -if TYPE_CHECKING: - from tuya_sharing import CustomerDevice - - from .builder import TuyaDeviceQuirk - from .helpers import TuyaDeviceCategory - -_LOGGER = logging.getLogger(__name__) - - -class QuirksRegistry: - """Registry for Tuya quirks.""" - - instance: Self - - _quirks: dict[str, dict[str, TuyaDeviceQuirk]] - - def __new__(cls) -> Self: - """Create a new class.""" - if not hasattr(cls, "instance"): - cls.instance = super().__new__(cls) - return cls.instance - - def __init__(self) -> None: - """Initialize the registry.""" - self._quirks = {} - - def register( - self, - category: TuyaDeviceCategory, - product_id: str, - quirk: TuyaDeviceQuirk, - ) -> None: - """Register a quirk for a specific device type.""" - self._quirks.setdefault(category, {})[product_id] = quirk - - def get_quirk_for_device(self, device: CustomerDevice) -> TuyaDeviceQuirk | None: - """Get the quirk for a specific device.""" - return self._quirks.get(device.category, {}).get(device.product_id) - - def purge_custom_quirks(self, custom_quirks_root: str) -> None: - """Purge custom quirks from the registry.""" - for category_quirks in self._quirks.values(): - to_remove = [] - for product_id, quirk in category_quirks.items(): - if quirk.quirk_file.is_relative_to(custom_quirks_root): - to_remove.append(product_id) - - for product_id in to_remove: - _LOGGER.debug("Removing stale custom quirk: %s", product_id) - category_quirks.pop(product_id) diff --git a/requirements_all.txt b/requirements_all.txt index 8f8252c7041f..16d0c36384ef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3010,6 +3010,9 @@ ttls==1.8.3 # homeassistant.components.thethingsnetwork ttn_client==1.2.3 +# homeassistant.components.tuya +tuya-device-handlers==0.0.1 + # homeassistant.components.tuya tuya-device-sharing-sdk==0.2.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6decd52d9336..996992f6b234 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2483,6 +2483,9 @@ ttls==1.8.3 # homeassistant.components.thethingsnetwork ttn_client==1.2.3 +# homeassistant.components.tuya +tuya-device-handlers==0.0.1 + # homeassistant.components.tuya tuya-device-sharing-sdk==0.2.4 diff --git a/tests/components/tuya/test_quirks.py b/tests/components/tuya/test_quirks.py index 73c1fa8b205c..4c4b0a8492e7 100644 --- a/tests/components/tuya/test_quirks.py +++ b/tests/components/tuya/test_quirks.py @@ -5,20 +5,14 @@ from __future__ import annotations from enum import StrEnum import pytest +from tuya_device_handlers import TUYA_QUIRKS_REGISTRY +from tuya_device_handlers.builder import TuyaDeviceQuirk +from tuya_device_handlers.builder.base_quirk import BaseTuyaDefinition +from tuya_device_handlers.devices import register_tuya_quirks +from tuya_device_handlers.registry import QuirksRegistry from homeassistant.components.cover import CoverDeviceClass from homeassistant.components.sensor import SensorDeviceClass -from homeassistant.components.tuya.tuya_device_handlers import ( - TUYA_QUIRKS_REGISTRY, - QuirksRegistry, -) -from homeassistant.components.tuya.tuya_device_handlers.builder import TuyaDeviceQuirk -from homeassistant.components.tuya.tuya_device_handlers.builder.base_quirk import ( - BaseTuyaDefinition, -) -from homeassistant.components.tuya.tuya_device_handlers.devices import ( - register_tuya_quirks, -) from homeassistant.const import EntityCategory, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import translation