mirror of
https://github.com/home-assistant/core.git
synced 2026-09-24 15:31:52 -05:00
Improve
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
"""Tuya device handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .registry import QuirksRegistry
|
||||
|
||||
__all__ = [
|
||||
"TUYA_QUIRKS_REGISTRY",
|
||||
]
|
||||
|
||||
TUYA_QUIRKS_REGISTRY = QuirksRegistry()
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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"
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
"""Quirks for Tuya CL category (curtain).
|
||||
|
||||
https://developer.tuya.com/en/docs/iot/categorycl?id=Kaiuz1hnpo7df
|
||||
"""
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
"""Quirks for Tuya WK category (thermostat).
|
||||
|
||||
https://developer.tuya.com/en/docs/iot/f?id=K9gf45ld5l0t9
|
||||
"""
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
"""
|
||||
@@ -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`
|
||||
"""
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Generated
+3
@@ -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
|
||||
|
||||
|
||||
Generated
+3
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user