This commit is contained in:
epenet
2025-11-03 12:00:10 +00:00
parent 1f2af23dff
commit 9fa0974c8c
24 changed files with 276 additions and 203 deletions

View File

@@ -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)

View File

@@ -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(

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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,

View File

@@ -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)

View File

@@ -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:

View File

@@ -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,

View File

@@ -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
"""

View File

@@ -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)

View File

@@ -0,0 +1,3 @@
"""Quirks for Tuya."""
from __future__ import annotations

View File

@@ -0,0 +1,4 @@
"""Quirks for Tuya CL category (curtain).
https://developer.tuya.com/en/docs/iot/categorycl?id=Kaiuz1hnpo7df
"""

View File

@@ -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)
)

View File

@@ -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)
)

View File

@@ -0,0 +1,4 @@
"""Quirks for Tuya WK category (thermostat).
https://developer.tuya.com/en/docs/iot/f?id=K9gf45ld5l0t9
"""

View File

@@ -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)
)

View File

@@ -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.

View File

@@ -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__)

View File

@@ -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

View File

@@ -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)
)

View File

@@ -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)
)

View File

@@ -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()

View File

@@ -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