mirror of
https://github.com/home-assistant/core.git
synced 2026-02-07 15:46:19 +01:00
Refactor
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
@@ -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,
|
||||
30
homeassistant/components/tuya/tuya_device_handlers/const.py
Normal file
30
homeassistant/components/tuya/tuya_device_handlers/const.py
Normal 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
|
||||
"""
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
"""Quirks for Tuya."""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Quirks for Tuya CL category (curtain).
|
||||
|
||||
https://developer.tuya.com/en/docs/iot/categorycl?id=Kaiuz1hnpo7df
|
||||
"""
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Quirks for Tuya WK category (thermostat).
|
||||
|
||||
https://developer.tuya.com/en/docs/iot/f?id=K9gf45ld5l0t9
|
||||
"""
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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.
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user