1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Move Tuya helpers to external library (#158791)

This commit is contained in:
epenet
2026-02-25 10:35:12 +01:00
committed by GitHub
parent 3219417a7d
commit dc133bf7cc
28 changed files with 254 additions and 1130 deletions
@@ -5,6 +5,12 @@ from __future__ import annotations
from base64 import b64decode
from typing import Any
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeEnumWrapper,
DPCodeRawWrapper,
)
from tuya_device_handlers.type_information import EnumTypeInformation
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.alarm_control_panel import (
@@ -20,8 +26,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DeviceWrapper, DPCodeEnumWrapper, DPCodeRawWrapper
from .type_information import EnumTypeInformation
ALARM: dict[DeviceCategory, tuple[AlarmControlPanelEntityDescription, ...]] = {
DeviceCategory.MAL: (
@@ -39,7 +43,7 @@ class _AlarmChangedByWrapper(DPCodeRawWrapper):
Decode base64 to utf-16be string, but only if alarm has been triggered.
"""
def read_device_status(self, device: CustomerDevice) -> str | None:
def read_device_status(self, device: CustomerDevice) -> str | None: # type: ignore[override]
"""Read the device status."""
if (
device.status.get(DPCode.MASTER_STATE) != "alarm"
@@ -4,6 +4,12 @@ from __future__ import annotations
from dataclasses import dataclass
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.binary_sensor import DPCodeBitmapBitWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeBooleanWrapper,
DPCodeWrapper,
)
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.binary_sensor import (
@@ -19,12 +25,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import (
DeviceWrapper,
DPCodeBitmapBitWrapper,
DPCodeBooleanWrapper,
DPCodeWrapper,
)
@dataclass(frozen=True)
+2 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import DPCodeBooleanWrapper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
@@ -13,7 +15,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DeviceWrapper, DPCodeBooleanWrapper
BUTTONS: dict[DeviceCategory, tuple[ButtonEntityDescription, ...]] = {
DeviceCategory.HXD: (
+2 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import DPCodeBooleanWrapper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components import ffmpeg
@@ -13,7 +15,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DeviceWrapper, DPCodeBooleanWrapper
CAMERAS: tuple[DeviceCategory, ...] = (
DeviceCategory.DGHSXJ,
+11 -9
View File
@@ -6,6 +6,13 @@ import collections
from dataclasses import dataclass
from typing import Any, Self
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
)
from tuya_device_handlers.type_information import EnumTypeInformation
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.climate import (
@@ -33,13 +40,6 @@ from .const import (
DPCode,
)
from .entity import TuyaEntity
from .models import (
DeviceWrapper,
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
)
from .type_information import EnumTypeInformation
TUYA_HVAC_TO_HA = {
"auto": HVACMode.HEAT_COOL,
@@ -177,8 +177,10 @@ class _HvacModeWrapper(DPCodeEnumWrapper):
return None
return TUYA_HVAC_TO_HA[raw]
def _convert_value_to_raw_value(
self, device: CustomerDevice, value: HVACMode
def _convert_value_to_raw_value( # type: ignore[override]
self,
device: CustomerDevice,
value: HVACMode,
) -> Any:
"""Convert value to raw value."""
return next(
-12
View File
@@ -82,18 +82,6 @@ class WorkMode(StrEnum):
WHITE = "white"
class DPType(StrEnum):
"""Data point types."""
BITMAP = "Bitmap"
BOOLEAN = "Boolean"
ENUM = "Enum"
INTEGER = "Integer"
JSON = "Json"
RAW = "Raw"
STRING = "String"
class DeviceCategory(StrEnum):
"""Tuya device categories.
+13 -10
View File
@@ -5,6 +5,17 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
)
from tuya_device_handlers.type_information import (
EnumTypeInformation,
IntegerTypeInformation,
)
from tuya_device_handlers.utils import RemapHelper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.cover import (
@@ -22,14 +33,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import (
DeviceWrapper,
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
)
from .type_information import EnumTypeInformation, IntegerTypeInformation
from .util import RemapHelper
class _DPCodePercentageMappingWrapper(DPCodeIntegerWrapper):
@@ -84,7 +87,7 @@ class _InstructionBooleanWrapper(DPCodeBooleanWrapper):
options = ["open", "close"]
_ACTION_MAPPINGS = {"open": True, "close": False}
def _convert_value_to_raw_value(self, device: CustomerDevice, value: str) -> bool:
def _convert_value_to_raw_value(self, device: CustomerDevice, value: str) -> bool: # type: ignore[override]
return self._ACTION_MAPPINGS[value]
@@ -130,7 +133,7 @@ class _IsClosedEnumWrapper(DPCodeEnumWrapper):
"fully_open": False,
}
def read_device_status(self, device: CustomerDevice) -> bool | None:
def read_device_status(self, device: CustomerDevice) -> bool | None: # type: ignore[override]
if (value := super().read_device_status(device)) is None:
return None
return self._MAPPINGS.get(value)
+1 -1
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
from typing import Any
from tuya_device_handlers.device_wrapper import DEVICE_WARNINGS
from tuya_sharing import CustomerDevice
from homeassistant.components.diagnostics import REDACTED
@@ -14,7 +15,6 @@ from homeassistant.util import dt as dt_util
from . import TuyaConfigEntry
from .const import DOMAIN, DPCode
from .type_information import DEVICE_WARNINGS
_REDACTED_DPCODES = {
DPCode.ALARM_MESSAGE,
+1 -1
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
from typing import Any
from tuya_device_handlers.device_wrapper import DeviceWrapper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.helpers.device_registry import DeviceInfo
@@ -11,7 +12,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from .const import DOMAIN, LOGGER, TUYA_HA_SIGNAL_UPDATE_ENTITY
from .models import DeviceWrapper
class TuyaEntity(Entity):
+11 -9
View File
@@ -6,6 +6,13 @@ from base64 import b64decode
from dataclasses import dataclass
from typing import Any
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeEnumWrapper,
DPCodeRawWrapper,
DPCodeStringWrapper,
DPCodeTypeInformationWrapper,
)
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.event import (
@@ -20,19 +27,14 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import (
DeviceWrapper,
DPCodeEnumWrapper,
DPCodeRawWrapper,
DPCodeStringWrapper,
DPCodeTypeInformationWrapper,
)
class _EventEnumWrapper(DPCodeEnumWrapper):
"""Wrapper for event enum DP codes."""
def read_device_status(self, device: CustomerDevice) -> tuple[str, None] | None:
def read_device_status( # type: ignore[override]
self, device: CustomerDevice
) -> tuple[str, None] | None:
"""Return the event details."""
if (raw_value := super().read_device_status(device)) is None:
return None
@@ -67,7 +69,7 @@ class _DoorbellPicWrapper(DPCodeRawWrapper):
super().__init__(dpcode, type_information)
self.options = ["triggered"]
def read_device_status(
def read_device_status( # type: ignore[override]
self, device: CustomerDevice
) -> tuple[str, dict[str, Any]] | None:
"""Return the event attributes for the doorbell picture."""
+10 -9
View File
@@ -4,6 +4,14 @@ from __future__ import annotations
from typing import Any
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
)
from tuya_device_handlers.type_information import IntegerTypeInformation
from tuya_device_handlers.utils import RemapHelper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.fan import (
@@ -23,14 +31,7 @@ from homeassistant.util.percentage import (
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import (
DeviceWrapper,
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
)
from .type_information import IntegerTypeInformation
from .util import RemapHelper, get_dpcode
from .util import get_dpcode
_DIRECTION_DPCODES = (DPCode.FAN_DIRECTION,)
_MODE_DPCODES = (DPCode.FAN_MODE, DPCode.MODE)
@@ -82,7 +83,7 @@ def _has_a_valid_dpcode(device: CustomerDevice) -> bool:
class _FanSpeedEnumWrapper(DPCodeEnumWrapper):
"""Wrapper for fan speed DP code (from an enum)."""
def read_device_status(self, device: CustomerDevice) -> int | None:
def read_device_status(self, device: CustomerDevice) -> int | None: # type: ignore[override]
"""Get the current speed as a percentage."""
if (value := super().read_device_status(device)) is None:
return None
+6 -6
View File
@@ -5,6 +5,12 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
)
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.humidifier import (
@@ -20,12 +26,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import (
DeviceWrapper,
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
)
from .util import ActionDPCodeNotFoundError, get_dpcode
+10 -10
View File
@@ -7,6 +7,15 @@ from enum import StrEnum
import json
from typing import Any, cast
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
DPCodeJsonWrapper,
)
from tuya_device_handlers.type_information import IntegerTypeInformation
from tuya_device_handlers.utils import RemapHelper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.light import (
@@ -30,15 +39,6 @@ from homeassistant.util.json import json_loads_object
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, WorkMode
from .entity import TuyaEntity
from .models import (
DeviceWrapper,
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
DPCodeJsonWrapper,
)
from .type_information import IntegerTypeInformation
from .util import RemapHelper
class _BrightnessWrapper(DPCodeIntegerWrapper):
@@ -174,7 +174,7 @@ class _ColorDataWrapper(DPCodeJsonWrapper):
s_type = DEFAULT_S_TYPE
v_type = DEFAULT_V_TYPE
def read_device_status(
def read_device_status( # type: ignore[override]
self, device: CustomerDevice
) -> tuple[float, float, float] | None:
"""Return a tuple (H, S, V) from this color data."""
+4 -1
View File
@@ -43,5 +43,8 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["tuya_sharing"],
"requirements": ["tuya-device-sharing-sdk==0.2.8"]
"requirements": [
"tuya-device-handlers==0.0.10",
"tuya-device-sharing-sdk==0.2.8"
]
}
-329
View File
@@ -1,329 +0,0 @@
"""Tuya Home Assistant Base Device Model."""
from __future__ import annotations
import logging
from typing import Any, Self
from tuya_sharing import CustomerDevice
from homeassistant.components.sensor import SensorStateClass
from .type_information import (
BitmapTypeInformation,
BooleanTypeInformation,
EnumTypeInformation,
IntegerTypeInformation,
JsonTypeInformation,
RawTypeInformation,
StringTypeInformation,
TypeInformation,
)
_LOGGER = logging.getLogger(__name__)
class DeviceWrapper[T]:
"""Base device wrapper."""
native_unit: str | None = None
suggested_unit: str | None = None
state_class: SensorStateClass | None = None
max_value: float
min_value: float
value_step: float
options: list[str]
def initialize(self, device: CustomerDevice) -> None:
"""Initialize the wrapper with device data.
Called when the entity is added to Home Assistant.
Override in subclasses to perform initialization logic.
"""
def skip_update(
self,
device: CustomerDevice,
updated_status_properties: list[str],
dp_timestamps: dict[str, int] | None,
) -> bool:
"""Determine if the wrapper should skip an update.
The default is to always skip if updated properties is given,
unless overridden in subclasses.
"""
# If updated_status_properties is None, we should not skip,
# as we don't have information on what was updated
# This happens for example on online/offline updates, where
# we still want to update the entity state
return updated_status_properties is not None
def read_device_status(self, device: CustomerDevice) -> T | None:
"""Read device status and convert to a Home Assistant value."""
raise NotImplementedError
def get_update_commands(
self, device: CustomerDevice, value: T
) -> list[dict[str, Any]]:
"""Generate update commands for a Home Assistant action."""
raise NotImplementedError
class DPCodeWrapper(DeviceWrapper):
"""Base device wrapper for a single DPCode.
Used as a common interface for referring to a DPCode, and
access read conversion routines.
"""
def __init__(self, dpcode: str) -> None:
"""Init DPCodeWrapper."""
self.dpcode = dpcode
def skip_update(
self,
device: CustomerDevice,
updated_status_properties: list[str],
dp_timestamps: dict[str, int] | None,
) -> bool:
"""Determine if the wrapper should skip an update.
By default, skip if updated_status_properties is given and
does not include this dpcode.
"""
# If updated_status_properties is None, we should not skip,
# as we don't have information on what was updated
# This happens for example on online/offline updates, where
# we still want to update the entity state
return (
updated_status_properties is not None
and self.dpcode not in updated_status_properties
)
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
"""Convert a Home Assistant value back to a raw device value.
This is called by `get_update_commands` to prepare the value for sending
back to the device, and should be implemented in concrete classes if needed.
"""
raise NotImplementedError
def get_update_commands(
self, device: CustomerDevice, value: Any
) -> list[dict[str, Any]]:
"""Get the update commands for the dpcode.
The Home Assistant value is converted back to a raw device value.
"""
return [
{
"code": self.dpcode,
"value": self._convert_value_to_raw_value(device, value),
}
]
class DPCodeTypeInformationWrapper[T: TypeInformation](DPCodeWrapper):
"""Base DPCode wrapper with Type Information."""
_DPTYPE: type[T]
type_information: T
def __init__(self, dpcode: str, type_information: T) -> None:
"""Init DPCodeWrapper."""
super().__init__(dpcode)
self.type_information = type_information
def read_device_status(self, device: CustomerDevice) -> Any | None:
"""Read the device value for the dpcode."""
return self.type_information.process_raw_value(
device.status.get(self.dpcode), device
)
@classmethod
def find_dpcode(
cls,
device: CustomerDevice,
dpcodes: str | tuple[str, ...] | None,
*,
prefer_function: bool = False,
) -> Self | None:
"""Find and return a DPCodeTypeInformationWrapper for the given DP codes."""
if type_information := cls._DPTYPE.find_dpcode(
device, dpcodes, prefer_function=prefer_function
):
return cls(
dpcode=type_information.dpcode, type_information=type_information
)
return None
class DPCodeBooleanWrapper(DPCodeTypeInformationWrapper[BooleanTypeInformation]):
"""Simple wrapper for boolean values.
Supports True/False only.
"""
_DPTYPE = BooleanTypeInformation
def _convert_value_to_raw_value(
self, device: CustomerDevice, value: Any
) -> Any | None:
"""Convert a Home Assistant value back to a raw device value."""
if value in (True, False):
return value
# Currently only called with boolean values
# Safety net in case of future changes
raise ValueError(f"Invalid boolean value `{value}`")
class DPCodeJsonWrapper(DPCodeTypeInformationWrapper[JsonTypeInformation]):
"""Wrapper to extract information from a JSON value."""
_DPTYPE = JsonTypeInformation
class DPCodeEnumWrapper(DPCodeTypeInformationWrapper[EnumTypeInformation]):
"""Simple wrapper for EnumTypeInformation values."""
_DPTYPE = EnumTypeInformation
def __init__(self, dpcode: str, type_information: EnumTypeInformation) -> None:
"""Init DPCodeEnumWrapper."""
super().__init__(dpcode, type_information)
self.options = type_information.range
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
"""Convert a Home Assistant value back to a raw device value."""
if value in self.type_information.range:
return value
# Guarded by select option validation
# Safety net in case of future changes
raise ValueError(
f"Enum value `{value}` out of range: {self.type_information.range}"
)
class DPCodeIntegerWrapper(DPCodeTypeInformationWrapper[IntegerTypeInformation]):
"""Simple wrapper for IntegerTypeInformation values."""
_DPTYPE = IntegerTypeInformation
def __init__(self, dpcode: str, type_information: IntegerTypeInformation) -> None:
"""Init DPCodeIntegerWrapper."""
super().__init__(dpcode, type_information)
self.native_unit = type_information.unit
self.min_value = self.type_information.scale_value(type_information.min)
self.max_value = self.type_information.scale_value(type_information.max)
self.value_step = self.type_information.scale_value(type_information.step)
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
"""Convert a Home Assistant value back to a raw device value."""
new_value = round(value * (10**self.type_information.scale))
if self.type_information.min <= new_value <= self.type_information.max:
return new_value
# Guarded by number validation
# Safety net in case of future changes
raise ValueError(
f"Value `{new_value}` (converted from `{value}`) out of range:"
f" ({self.type_information.min}-{self.type_information.max})"
)
class DPCodeDeltaIntegerWrapper(DPCodeIntegerWrapper):
"""Wrapper for integer values with delta report accumulation.
This wrapper handles sensors that report incremental (delta) values
instead of cumulative totals. It accumulates the delta values locally
to provide a running total.
"""
_accumulated_value: float = 0
_last_dp_timestamp: int | None = None
def __init__(self, dpcode: str, type_information: IntegerTypeInformation) -> None:
"""Init DPCodeDeltaIntegerWrapper."""
super().__init__(dpcode, type_information)
# Delta reports use TOTAL_INCREASING state class
self.state_class = SensorStateClass.TOTAL_INCREASING
def skip_update(
self,
device: CustomerDevice,
updated_status_properties: list[str],
dp_timestamps: dict[str, int] | None,
) -> bool:
"""Override skip_update to process delta updates.
Processes delta accumulation before determining if update should be skipped.
"""
if (
super().skip_update(device, updated_status_properties, dp_timestamps)
or dp_timestamps is None
or (current_timestamp := dp_timestamps.get(self.dpcode)) is None
or current_timestamp == self._last_dp_timestamp
or (raw_value := super().read_device_status(device)) is None
):
return True
delta = float(raw_value)
self._accumulated_value += delta
_LOGGER.debug(
"Delta update for %s: +%s, total: %s",
self.dpcode,
delta,
self._accumulated_value,
)
self._last_dp_timestamp = current_timestamp
return False
def read_device_status(self, device: CustomerDevice) -> float | None:
"""Read device status, returning accumulated value for delta reports."""
return self._accumulated_value
class DPCodeRawWrapper(DPCodeTypeInformationWrapper[RawTypeInformation]):
"""Wrapper to extract information from a RAW/binary value."""
_DPTYPE = RawTypeInformation
class DPCodeStringWrapper(DPCodeTypeInformationWrapper[StringTypeInformation]):
"""Wrapper to extract information from a STRING value."""
_DPTYPE = StringTypeInformation
class DPCodeBitmapBitWrapper(DPCodeWrapper):
"""Simple wrapper for a specific bit in bitmap values."""
def __init__(self, dpcode: str, mask: int) -> None:
"""Init DPCodeBitmapWrapper."""
super().__init__(dpcode)
self._mask = mask
def read_device_status(self, device: CustomerDevice) -> bool | None:
"""Read the device value for the dpcode."""
if (raw_value := device.status.get(self.dpcode)) is None:
return None
return (raw_value & (1 << self._mask)) != 0
@classmethod
def find_dpcode(
cls,
device: CustomerDevice,
dpcodes: str | tuple[str, ...],
*,
bitmap_key: str,
) -> Self | None:
"""Find and return a DPCodeBitmapBitWrapper for the given DP codes."""
if (
type_information := BitmapTypeInformation.find_dpcode(device, dpcodes)
) and bitmap_key in type_information.label:
return cls(
type_information.dpcode, type_information.label.index(bitmap_key)
)
return None
+2 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import DPCodeIntegerWrapper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.number import (
@@ -25,7 +27,6 @@ from .const import (
DPCode,
)
from .entity import TuyaEntity
from .models import DeviceWrapper, DPCodeIntegerWrapper
NUMBERS: dict[DeviceCategory, tuple[NumberEntityDescription, ...]] = {
DeviceCategory.BH: (
@@ -1,60 +0,0 @@
"""Parsers for RAW (base64-encoded bytes) values."""
from dataclasses import dataclass
import struct
from typing import Self
@dataclass(kw_only=True)
class ElectricityData:
"""Electricity RAW value."""
current: float
power: float
voltage: float
@classmethod
def from_bytes(cls, raw: bytes) -> Self | None:
"""Parse bytes and return an ElectricityValue object."""
# Format:
# - legacy: 8 bytes
# - v01: [ver=0x01][len=0x0F][data(15 bytes)]
# - v02: [ver=0x02][len=0x0F][data(15 bytes)][sign_bitmap(1 byte)]
# Data layout (big-endian):
# - voltage: 2B, unit 0.1 V
# - current: 3B, unit 0.001 A (i.e., mA)
# - active power: 3B, unit 0.001 kW (i.e., W)
# - reactive power: 3B, unit 0.001 kVar
# - apparent power: 3B, unit 0.001 kVA
# - power factor: 1B, unit 0.01
# Sign bitmap (v02 only, 1 bit means negative):
# - bit0 current
# - bit1 active power
# - bit2 reactive
# - bit3 power factor
is_v1 = len(raw) == 17 and raw[0:2] == b"\x01\x0f"
is_v2 = len(raw) == 18 and raw[0:2] == b"\x02\x0f"
if is_v1 or is_v2:
data = raw[2:17]
voltage = struct.unpack(">H", data[0:2])[0] / 10.0
current = struct.unpack(">L", b"\x00" + data[2:5])[0]
power = struct.unpack(">L", b"\x00" + data[5:8])[0]
if is_v2:
sign_bitmap = raw[17]
if sign_bitmap & 0x01:
current = -current
if sign_bitmap & 0x02:
power = -power
return cls(current=current, power=power, voltage=voltage)
if len(raw) >= 8:
voltage = struct.unpack(">H", raw[0:2])[0] / 10.0
current = struct.unpack(">L", b"\x00" + raw[2:5])[0]
power = struct.unpack(">L", b"\x00" + raw[5:8])[0]
return cls(current=current, power=power, voltage=voltage)
return None
+2 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import DPCodeEnumWrapper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.select import SelectEntity, SelectEntityDescription
@@ -13,7 +15,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DeviceWrapper, DPCodeEnumWrapper
# All descriptions can be found here. Mostly the Enum data types in the
# default instructions set of each category end up being a select.
+30 -135
View File
@@ -4,6 +4,24 @@ from __future__ import annotations
from dataclasses import dataclass
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
DPCodeTypeInformationWrapper,
DPCodeWrapper,
)
from tuya_device_handlers.device_wrapper.sensor import (
DeltaIntegerWrapper,
ElectricityCurrentJsonWrapper,
ElectricityCurrentRawWrapper,
ElectricityPowerJsonWrapper,
ElectricityPowerRawWrapper,
ElectricityVoltageJsonWrapper,
ElectricityVoltageRawWrapper,
WindDirectionEnumWrapper,
)
from tuya_device_handlers.type_information import IntegerTypeInformation
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.sensor import (
@@ -38,138 +56,10 @@ from .const import (
DPCode,
)
from .entity import TuyaEntity
from .models import (
DeviceWrapper,
DPCodeDeltaIntegerWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
DPCodeJsonWrapper,
DPCodeRawWrapper,
DPCodeTypeInformationWrapper,
DPCodeWrapper,
)
from .raw_data_models import ElectricityData
from .type_information import EnumTypeInformation, IntegerTypeInformation
class _WindDirectionWrapper(DPCodeTypeInformationWrapper[EnumTypeInformation]):
"""Custom DPCode Wrapper for converting enum to wind direction."""
_DPTYPE = EnumTypeInformation
_WIND_DIRECTIONS = {
"north": 0.0,
"north_north_east": 22.5,
"north_east": 45.0,
"east_north_east": 67.5,
"east": 90.0,
"east_south_east": 112.5,
"south_east": 135.0,
"south_south_east": 157.5,
"south": 180.0,
"south_south_west": 202.5,
"south_west": 225.0,
"west_south_west": 247.5,
"west": 270.0,
"west_north_west": 292.5,
"north_west": 315.0,
"north_north_west": 337.5,
}
def read_device_status(self, device: CustomerDevice) -> float | None:
"""Read the device value for the dpcode."""
if (raw_value := device.status.get(self.dpcode)) in self.type_information.range:
return self._WIND_DIRECTIONS.get(raw_value)
return None
class _JsonElectricityCurrentWrapper(DPCodeJsonWrapper):
"""Custom DPCode Wrapper for extracting electricity current from JSON."""
native_unit = UnitOfElectricCurrent.AMPERE
def read_device_status(self, device: CustomerDevice) -> float | None:
"""Read the device value for the dpcode."""
if (status := super().read_device_status(device)) is None:
return None
return status.get("electricCurrent")
class _JsonElectricityPowerWrapper(DPCodeJsonWrapper):
"""Custom DPCode Wrapper for extracting electricity power from JSON."""
native_unit = UnitOfPower.KILO_WATT
def read_device_status(self, device: CustomerDevice) -> float | None:
"""Read the device value for the dpcode."""
if (status := super().read_device_status(device)) is None:
return None
return status.get("power")
class _JsonElectricityVoltageWrapper(DPCodeJsonWrapper):
"""Custom DPCode Wrapper for extracting electricity voltage from JSON."""
native_unit = UnitOfElectricPotential.VOLT
def read_device_status(self, device: CustomerDevice) -> float | None:
"""Read the device value for the dpcode."""
if (status := super().read_device_status(device)) is None:
return None
return status.get("voltage")
class _RawElectricityDataWrapper(DPCodeRawWrapper):
"""Custom DPCode Wrapper for extracting ElectricityData from base64."""
def _convert(self, value: ElectricityData) -> float:
"""Extract specific value from T."""
raise NotImplementedError
def read_device_status(self, device: CustomerDevice) -> float | None:
"""Read the device value for the dpcode."""
if (raw_value := super().read_device_status(device)) is None or (
value := ElectricityData.from_bytes(raw_value)
) is None:
return None
return self._convert(value)
class _RawElectricityCurrentWrapper(_RawElectricityDataWrapper):
"""Custom DPCode Wrapper for extracting electricity current from base64."""
native_unit = UnitOfElectricCurrent.MILLIAMPERE
suggested_unit = UnitOfElectricCurrent.AMPERE
def _convert(self, value: ElectricityData) -> float:
"""Extract specific value from ElectricityData."""
return value.current
class _RawElectricityPowerWrapper(_RawElectricityDataWrapper):
"""Custom DPCode Wrapper for extracting electricity power from base64."""
native_unit = UnitOfPower.WATT
suggested_unit = UnitOfPower.KILO_WATT
def _convert(self, value: ElectricityData) -> float:
"""Extract specific value from ElectricityData."""
return value.power
class _RawElectricityVoltageWrapper(_RawElectricityDataWrapper):
"""Custom DPCode Wrapper for extracting electricity voltage from base64."""
native_unit = UnitOfElectricPotential.VOLT
def _convert(self, value: ElectricityData) -> float:
"""Extract specific value from ElectricityData."""
return value.voltage
CURRENT_WRAPPER = (_RawElectricityCurrentWrapper, _JsonElectricityCurrentWrapper)
POWER_WRAPPER = (_RawElectricityPowerWrapper, _JsonElectricityPowerWrapper)
VOLTAGE_WRAPPER = (_RawElectricityVoltageWrapper, _JsonElectricityVoltageWrapper)
CURRENT_WRAPPER = (ElectricityCurrentRawWrapper, ElectricityCurrentJsonWrapper)
POWER_WRAPPER = (ElectricityPowerRawWrapper, ElectricityPowerJsonWrapper)
VOLTAGE_WRAPPER = (ElectricityVoltageRawWrapper, ElectricityVoltageJsonWrapper)
@dataclass(frozen=True)
@@ -1070,7 +960,7 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
translation_key="wind_direction",
device_class=SensorDeviceClass.WIND_DIRECTION,
state_class=SensorStateClass.MEASUREMENT,
wrapper_class=(_WindDirectionWrapper,),
wrapper_class=(WindDirectionEnumWrapper,),
),
TuyaSensorEntityDescription(
key=DPCode.DEW_POINT_TEMP,
@@ -1744,7 +1634,7 @@ def _get_dpcode_wrapper(
# Check for integer type first, using delta wrapper only for sum report_type
if type_information := IntegerTypeInformation.find_dpcode(device, dpcode):
if type_information.report_type == "sum":
return DPCodeDeltaIntegerWrapper(type_information.dpcode, type_information)
return DeltaIntegerWrapper(type_information.dpcode, type_information)
return DPCodeIntegerWrapper(type_information.dpcode, type_information)
return DPCodeEnumWrapper.find_dpcode(device, dpcode)
@@ -1802,8 +1692,13 @@ class TuyaSensorEntity(TuyaEntity, SensorEntity):
self._attr_native_unit_of_measurement = dpcode_wrapper.native_unit
if description.suggested_unit_of_measurement is None:
self._attr_suggested_unit_of_measurement = dpcode_wrapper.suggested_unit
if description.state_class is None:
self._attr_state_class = dpcode_wrapper.state_class
if (
description.state_class is None
# For integer type DPs with "sum" report type, we can assume it's a total
# increasing sensor
and isinstance(dpcode_wrapper, DeltaIntegerWrapper)
):
self._attr_state_class = SensorStateClass.TOTAL_INCREASING
self._validate_device_class_unit()
+2 -1
View File
@@ -4,6 +4,8 @@ from __future__ import annotations
from typing import Any
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import DPCodeBooleanWrapper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.siren import (
@@ -19,7 +21,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DeviceWrapper, DPCodeBooleanWrapper
SIRENS: dict[DeviceCategory, tuple[SirenEntityDescription, ...]] = {
DeviceCategory.CO2BJ: (
+2 -1
View File
@@ -5,6 +5,8 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import DPCodeBooleanWrapper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.switch import (
@@ -27,7 +29,6 @@ from homeassistant.helpers.issue_registry import (
from . import TuyaConfigEntry
from .const import DOMAIN, TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DeviceWrapper, DPCodeBooleanWrapper
@dataclass(frozen=True, kw_only=True)
@@ -1,302 +0,0 @@
"""Type information classes for the Tuya integration."""
from __future__ import annotations
import base64
from dataclasses import dataclass
from typing import Any, ClassVar, Self, cast
from tuya_sharing import CustomerDevice
from homeassistant.util.json import json_loads_object
from .const import LOGGER, DPType
from .util import parse_dptype
# Dictionary to track logged warnings to avoid spamming logs
# Keyed by device ID
DEVICE_WARNINGS: dict[str, set[str]] = {}
def _should_log_warning(device_id: str, warning_key: str) -> bool:
"""Check if a warning has already been logged for a device and add it if not.
Returns: True if the warning should be logged, False if it was already logged.
"""
if (device_warnings := DEVICE_WARNINGS.get(device_id)) is None:
device_warnings = set()
DEVICE_WARNINGS[device_id] = device_warnings
if warning_key in device_warnings:
return False
DEVICE_WARNINGS[device_id].add(warning_key)
return True
@dataclass(kw_only=True)
class TypeInformation[T]:
"""Type information.
As provided by the SDK, from `device.function` / `device.status_range`.
"""
_DPTYPE: ClassVar[DPType]
dpcode: str
type_data: str
def process_raw_value(
self, raw_value: Any | None, device: CustomerDevice
) -> T | None:
"""Read and process raw value against this type information.
Base implementation does no validation, subclasses may override to provide
specific validation.
"""
return raw_value
@classmethod
def _from_json(
cls, dpcode: str, type_data: str, *, report_type: str | None
) -> Self | None:
"""Load JSON string and return a TypeInformation object."""
return cls(dpcode=dpcode, type_data=type_data)
@classmethod
def find_dpcode(
cls,
device: CustomerDevice,
dpcodes: str | tuple[str, ...] | None,
*,
prefer_function: bool = False,
) -> Self | None:
"""Find type information for a matching DP code available for this device."""
if dpcodes is None:
return None
if not isinstance(dpcodes, tuple):
dpcodes = (dpcodes,)
lookup_tuple = (
(device.function, device.status_range)
if prefer_function
else (device.status_range, device.function)
)
for dpcode in dpcodes:
report_type = (
sr.report_type if (sr := device.status_range.get(dpcode)) else None
)
for device_specs in lookup_tuple:
if (
(current_definition := device_specs.get(dpcode))
and parse_dptype(current_definition.type) is cls._DPTYPE
and (
type_information := cls._from_json(
dpcode=dpcode,
type_data=current_definition.values,
report_type=report_type,
)
)
):
return type_information
return None
@dataclass(kw_only=True)
class BitmapTypeInformation(TypeInformation[int]):
"""Bitmap type information."""
_DPTYPE = DPType.BITMAP
label: list[str]
@classmethod
def _from_json(
cls, dpcode: str, type_data: str, *, report_type: str | None
) -> Self | None:
"""Load JSON string and return a BitmapTypeInformation object."""
if not (parsed := cast(dict[str, Any] | None, json_loads_object(type_data))):
return None
return cls(
dpcode=dpcode,
type_data=type_data,
label=parsed["label"],
)
@dataclass(kw_only=True)
class BooleanTypeInformation(TypeInformation[bool]):
"""Boolean type information."""
_DPTYPE = DPType.BOOLEAN
def process_raw_value(
self, raw_value: Any | None, device: CustomerDevice
) -> bool | None:
"""Read and process raw value against this type information."""
if raw_value is None:
return None
# Validate input against defined range
if raw_value not in (True, False):
if _should_log_warning(
device.id, f"boolean_out_range|{self.dpcode}|{raw_value}"
):
LOGGER.warning(
"Found invalid boolean value `%s` for datapoint `%s` in product "
"id `%s`, expected one of `%s`; please report this defect to "
"Tuya support",
raw_value,
self.dpcode,
device.product_id,
(True, False),
)
return None
return raw_value
@dataclass(kw_only=True)
class EnumTypeInformation(TypeInformation[str]):
"""Enum type information."""
_DPTYPE = DPType.ENUM
range: list[str]
def process_raw_value(
self, raw_value: Any | None, device: CustomerDevice
) -> str | None:
"""Read and process raw value against this type information."""
if raw_value is None:
return None
# Validate input against defined range
if raw_value not in self.range:
if _should_log_warning(
device.id, f"enum_out_range|{self.dpcode}|{raw_value}"
):
LOGGER.warning(
"Found invalid enum value `%s` for datapoint `%s` in product "
"id `%s`, expected one of `%s`; please report this defect to "
"Tuya support",
raw_value,
self.dpcode,
device.product_id,
self.range,
)
return None
return raw_value
@classmethod
def _from_json(
cls, dpcode: str, type_data: str, *, report_type: str | None
) -> Self | None:
"""Load JSON string and return an EnumTypeInformation object."""
if not (parsed := json_loads_object(type_data)):
return None
return cls(
dpcode=dpcode,
type_data=type_data,
**cast(dict[str, list[str]], parsed),
)
@dataclass(kw_only=True)
class IntegerTypeInformation(TypeInformation[float]):
"""Integer type information."""
_DPTYPE = DPType.INTEGER
min: int
max: int
scale: int
step: int
unit: str | None = None
report_type: str | None
def scale_value(self, value: int) -> float:
"""Scale a value."""
return value / (10**self.scale)
def scale_value_back(self, value: float) -> int:
"""Return raw value for scaled."""
return round(value * (10**self.scale))
def process_raw_value(
self, raw_value: Any | None, device: CustomerDevice
) -> float | None:
"""Read and process raw value against this type information."""
if raw_value is None:
return None
# Validate input against defined range
if not isinstance(raw_value, int) or not (self.min <= raw_value <= self.max):
if _should_log_warning(
device.id, f"integer_out_range|{self.dpcode}|{raw_value}"
):
LOGGER.warning(
"Found invalid integer value `%s` for datapoint `%s` in product "
"id `%s`, expected integer value between %s and %s; please report "
"this defect to Tuya support",
raw_value,
self.dpcode,
device.product_id,
self.min,
self.max,
)
return None
return raw_value / (10**self.scale)
@classmethod
def _from_json(
cls, dpcode: str, type_data: str, *, report_type: str | None
) -> Self | None:
"""Load JSON string and return an IntegerTypeInformation object."""
if not (parsed := cast(dict[str, Any] | None, json_loads_object(type_data))):
return None
return cls(
dpcode=dpcode,
type_data=type_data,
min=int(parsed["min"]),
max=int(parsed["max"]),
scale=int(parsed["scale"]),
step=int(parsed["step"]),
unit=parsed.get("unit"),
report_type=report_type,
)
@dataclass(kw_only=True)
class JsonTypeInformation(TypeInformation[dict[str, Any]]):
"""Json type information."""
_DPTYPE = DPType.JSON
def process_raw_value(
self, raw_value: Any | None, device: CustomerDevice
) -> dict[str, Any] | None:
"""Read and process raw value against this type information."""
if raw_value is None:
return None
return json_loads_object(raw_value)
@dataclass(kw_only=True)
class RawTypeInformation(TypeInformation[bytes]):
"""Raw type information."""
_DPTYPE = DPType.RAW
def process_raw_value(
self, raw_value: Any | None, device: CustomerDevice
) -> bytes | None:
"""Read and process raw value against this type information."""
if raw_value is None:
return None
return base64.b64decode(raw_value)
@dataclass(kw_only=True)
class StringTypeInformation(TypeInformation[str]):
"""String type information."""
_DPTYPE = DPType.STRING
+1 -101
View File
@@ -2,27 +2,11 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
from tuya_sharing import CustomerDevice
from homeassistant.exceptions import ServiceValidationError
from .const import DOMAIN, DPCode, DPType
if TYPE_CHECKING:
from .type_information import IntegerTypeInformation
_DPTYPE_MAPPING: dict[str, DPType] = {
"bitmap": DPType.BITMAP,
"bool": DPType.BOOLEAN,
"enum": DPType.ENUM,
"json": DPType.JSON,
"raw": DPType.RAW,
"string": DPType.STRING,
"value": DPType.INTEGER,
}
from .const import DOMAIN, DPCode
def get_dpcode(
@@ -46,90 +30,6 @@ def get_dpcode(
return None
def parse_dptype(dptype: str) -> DPType | None:
"""Parse DPType from device DPCode information."""
try:
return DPType(dptype)
except ValueError:
# Sometimes, we get ill-formed DPTypes from the cloud,
# this fixes them and maps them to the correct DPType.
return _DPTYPE_MAPPING.get(dptype)
@dataclass(kw_only=True)
class RemapHelper:
"""Helper class for remapping values."""
source_min: int
source_max: int
target_min: int
target_max: int
@classmethod
def from_type_information(
cls,
type_information: IntegerTypeInformation,
target_min: int,
target_max: int,
) -> RemapHelper:
"""Create RemapHelper from IntegerTypeInformation."""
return cls(
source_min=type_information.min,
source_max=type_information.max,
target_min=target_min,
target_max=target_max,
)
@classmethod
def from_function_data(
cls, function_data: dict[str, Any], target_min: int, target_max: int
) -> RemapHelper:
"""Create RemapHelper from function_data."""
return cls(
source_min=function_data["min"],
source_max=function_data["max"],
target_min=target_min,
target_max=target_max,
)
def remap_value_to(self, value: float, *, reverse: bool = False) -> float:
"""Remap a value from this range to a new range."""
return self.remap_value(
value,
self.source_min,
self.source_max,
self.target_min,
self.target_max,
reverse=reverse,
)
def remap_value_from(self, value: float, *, reverse: bool = False) -> float:
"""Remap a value from its current range to this range."""
return self.remap_value(
value,
self.target_min,
self.target_max,
self.source_min,
self.source_max,
reverse=reverse,
)
@staticmethod
def remap_value(
value: float,
from_min: float,
from_max: float,
to_min: float,
to_max: float,
*,
reverse: bool = False,
) -> float:
"""Remap a value from its current range, to a new range."""
if reverse:
value = from_max - value + from_min
return ((value - from_min) / (from_max - from_min)) * (to_max - to_min) + to_min
class ActionDPCodeNotFoundError(ServiceValidationError):
"""Custom exception for action DP code not found errors."""
+5 -1
View File
@@ -4,6 +4,11 @@ from __future__ import annotations
from typing import Any, Self
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import (
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
)
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.vacuum import (
@@ -18,7 +23,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DeviceWrapper, DPCodeBooleanWrapper, DPCodeEnumWrapper
class _VacuumActivityWrapper(DeviceWrapper):
+2 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from tuya_device_handlers.device_wrapper.base import DeviceWrapper
from tuya_device_handlers.device_wrapper.common import DPCodeBooleanWrapper
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.valve import (
@@ -17,7 +19,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DeviceWrapper, DPCodeBooleanWrapper
VALVES: dict[DeviceCategory, tuple[ValveEntityDescription, ...]] = {
DeviceCategory.SFKZQ: (
+3
View File
@@ -3120,6 +3120,9 @@ ttls==1.8.3
# homeassistant.components.thethingsnetwork
ttn_client==1.2.3
# homeassistant.components.tuya
tuya-device-handlers==0.0.10
# homeassistant.components.tuya
tuya-device-sharing-sdk==0.2.8
+3
View File
@@ -2620,6 +2620,9 @@ ttls==1.8.3
# homeassistant.components.thethingsnetwork
ttn_client==1.2.3
# homeassistant.components.tuya
tuya-device-handlers==0.0.10
# homeassistant.components.tuya
tuya-device-sharing-sdk==0.2.8
+118 -118
View File
@@ -5758,7 +5758,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -5770,7 +5770,7 @@
'supported_features': 0,
'translation_key': 'phase_a_current',
'unique_id': 'tuya.qi94v9dmdx4fkpncqldphase_aelectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_phase_a_current-state]
@@ -5779,7 +5779,7 @@
'device_class': 'current',
'friendly_name': '断路器HA Phase A current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.duan_lu_qi_ha_phase_a_current',
@@ -5818,7 +5818,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -5830,7 +5830,7 @@
'supported_features': 0,
'translation_key': 'phase_a_power',
'unique_id': 'tuya.qi94v9dmdx4fkpncqldphase_apower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_phase_a_power-state]
@@ -5839,7 +5839,7 @@
'device_class': 'power',
'friendly_name': '断路器HA Phase A power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.duan_lu_qi_ha_phase_a_power',
@@ -5887,7 +5887,7 @@
'supported_features': 0,
'translation_key': 'phase_a_voltage',
'unique_id': 'tuya.qi94v9dmdx4fkpncqldphase_avoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.duan_lu_qi_ha_phase_a_voltage-state]
@@ -5896,7 +5896,7 @@
'device_class': 'voltage',
'friendly_name': '断路器HA Phase A voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.duan_lu_qi_ha_phase_a_voltage',
@@ -6279,7 +6279,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -6291,7 +6291,7 @@
'supported_features': 0,
'translation_key': 'phase_a_current',
'unique_id': 'tuya.vcrfgwvbuybgnj3zqldphase_aelectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.edesanya_energy_phase_a_current-state]
@@ -6300,7 +6300,7 @@
'device_class': 'current',
'friendly_name': 'Edesanya Energy Phase A current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.edesanya_energy_phase_a_current',
@@ -6339,7 +6339,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -6351,7 +6351,7 @@
'supported_features': 0,
'translation_key': 'phase_a_power',
'unique_id': 'tuya.vcrfgwvbuybgnj3zqldphase_apower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.edesanya_energy_phase_a_power-state]
@@ -6360,7 +6360,7 @@
'device_class': 'power',
'friendly_name': 'Edesanya Energy Phase A power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.edesanya_energy_phase_a_power',
@@ -6408,7 +6408,7 @@
'supported_features': 0,
'translation_key': 'phase_a_voltage',
'unique_id': 'tuya.vcrfgwvbuybgnj3zqldphase_avoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.edesanya_energy_phase_a_voltage-state]
@@ -6417,7 +6417,7 @@
'device_class': 'voltage',
'friendly_name': 'Edesanya Energy Phase A voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.edesanya_energy_phase_a_voltage',
@@ -6456,7 +6456,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -6468,7 +6468,7 @@
'supported_features': 0,
'translation_key': 'phase_b_current',
'unique_id': 'tuya.vcrfgwvbuybgnj3zqldphase_belectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.edesanya_energy_phase_b_current-state]
@@ -6477,7 +6477,7 @@
'device_class': 'current',
'friendly_name': 'Edesanya Energy Phase B current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.edesanya_energy_phase_b_current',
@@ -6516,7 +6516,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -6528,7 +6528,7 @@
'supported_features': 0,
'translation_key': 'phase_b_power',
'unique_id': 'tuya.vcrfgwvbuybgnj3zqldphase_bpower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.edesanya_energy_phase_b_power-state]
@@ -6537,7 +6537,7 @@
'device_class': 'power',
'friendly_name': 'Edesanya Energy Phase B power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.edesanya_energy_phase_b_power',
@@ -6585,7 +6585,7 @@
'supported_features': 0,
'translation_key': 'phase_b_voltage',
'unique_id': 'tuya.vcrfgwvbuybgnj3zqldphase_bvoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.edesanya_energy_phase_b_voltage-state]
@@ -6594,7 +6594,7 @@
'device_class': 'voltage',
'friendly_name': 'Edesanya Energy Phase B voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.edesanya_energy_phase_b_voltage',
@@ -6633,7 +6633,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -6645,7 +6645,7 @@
'supported_features': 0,
'translation_key': 'phase_c_current',
'unique_id': 'tuya.vcrfgwvbuybgnj3zqldphase_celectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.edesanya_energy_phase_c_current-state]
@@ -6654,7 +6654,7 @@
'device_class': 'current',
'friendly_name': 'Edesanya Energy Phase C current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.edesanya_energy_phase_c_current',
@@ -6693,7 +6693,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -6705,7 +6705,7 @@
'supported_features': 0,
'translation_key': 'phase_c_power',
'unique_id': 'tuya.vcrfgwvbuybgnj3zqldphase_cpower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.edesanya_energy_phase_c_power-state]
@@ -6714,7 +6714,7 @@
'device_class': 'power',
'friendly_name': 'Edesanya Energy Phase C power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.edesanya_energy_phase_c_power',
@@ -6762,7 +6762,7 @@
'supported_features': 0,
'translation_key': 'phase_c_voltage',
'unique_id': 'tuya.vcrfgwvbuybgnj3zqldphase_cvoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.edesanya_energy_phase_c_voltage-state]
@@ -6771,7 +6771,7 @@
'device_class': 'voltage',
'friendly_name': 'Edesanya Energy Phase C voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.edesanya_energy_phase_c_voltage',
@@ -12497,7 +12497,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -12509,7 +12509,7 @@
'supported_features': 0,
'translation_key': 'phase_a_current',
'unique_id': 'tuya.6pd3bkidqldphase_aelectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_a_current-state]
@@ -12518,7 +12518,7 @@
'device_class': 'current',
'friendly_name': 'Medidor de Energia Phase A current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.medidor_de_energia_phase_a_current',
@@ -12557,7 +12557,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -12569,7 +12569,7 @@
'supported_features': 0,
'translation_key': 'phase_a_power',
'unique_id': 'tuya.6pd3bkidqldphase_apower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_a_power-state]
@@ -12578,7 +12578,7 @@
'device_class': 'power',
'friendly_name': 'Medidor de Energia Phase A power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.medidor_de_energia_phase_a_power',
@@ -12626,7 +12626,7 @@
'supported_features': 0,
'translation_key': 'phase_a_voltage',
'unique_id': 'tuya.6pd3bkidqldphase_avoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_a_voltage-state]
@@ -12635,7 +12635,7 @@
'device_class': 'voltage',
'friendly_name': 'Medidor de Energia Phase A voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.medidor_de_energia_phase_a_voltage',
@@ -12674,7 +12674,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -12686,7 +12686,7 @@
'supported_features': 0,
'translation_key': 'phase_b_current',
'unique_id': 'tuya.6pd3bkidqldphase_belectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_b_current-state]
@@ -12695,7 +12695,7 @@
'device_class': 'current',
'friendly_name': 'Medidor de Energia Phase B current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.medidor_de_energia_phase_b_current',
@@ -12734,7 +12734,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -12746,7 +12746,7 @@
'supported_features': 0,
'translation_key': 'phase_b_power',
'unique_id': 'tuya.6pd3bkidqldphase_bpower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_b_power-state]
@@ -12755,7 +12755,7 @@
'device_class': 'power',
'friendly_name': 'Medidor de Energia Phase B power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.medidor_de_energia_phase_b_power',
@@ -12803,7 +12803,7 @@
'supported_features': 0,
'translation_key': 'phase_b_voltage',
'unique_id': 'tuya.6pd3bkidqldphase_bvoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_b_voltage-state]
@@ -12812,7 +12812,7 @@
'device_class': 'voltage',
'friendly_name': 'Medidor de Energia Phase B voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.medidor_de_energia_phase_b_voltage',
@@ -12851,7 +12851,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -12863,7 +12863,7 @@
'supported_features': 0,
'translation_key': 'phase_c_current',
'unique_id': 'tuya.6pd3bkidqldphase_celectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_c_current-state]
@@ -12872,7 +12872,7 @@
'device_class': 'current',
'friendly_name': 'Medidor de Energia Phase C current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.medidor_de_energia_phase_c_current',
@@ -12911,7 +12911,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -12923,7 +12923,7 @@
'supported_features': 0,
'translation_key': 'phase_c_power',
'unique_id': 'tuya.6pd3bkidqldphase_cpower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_c_power-state]
@@ -12932,7 +12932,7 @@
'device_class': 'power',
'friendly_name': 'Medidor de Energia Phase C power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.medidor_de_energia_phase_c_power',
@@ -12980,7 +12980,7 @@
'supported_features': 0,
'translation_key': 'phase_c_voltage',
'unique_id': 'tuya.6pd3bkidqldphase_cvoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.medidor_de_energia_phase_c_voltage-state]
@@ -12989,7 +12989,7 @@
'device_class': 'voltage',
'friendly_name': 'Medidor de Energia Phase C voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.medidor_de_energia_phase_c_voltage',
@@ -13199,7 +13199,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -13211,7 +13211,7 @@
'supported_features': 0,
'translation_key': 'phase_a_current',
'unique_id': 'tuya.nnqlg0rxryraf8ezbdnzphase_aelectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.meter_phase_a_current-state]
@@ -13220,7 +13220,7 @@
'device_class': 'current',
'friendly_name': 'Meter Phase A current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.meter_phase_a_current',
@@ -13259,7 +13259,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -13271,7 +13271,7 @@
'supported_features': 0,
'translation_key': 'phase_a_power',
'unique_id': 'tuya.nnqlg0rxryraf8ezbdnzphase_apower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.meter_phase_a_power-state]
@@ -13280,7 +13280,7 @@
'device_class': 'power',
'friendly_name': 'Meter Phase A power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.meter_phase_a_power',
@@ -13328,7 +13328,7 @@
'supported_features': 0,
'translation_key': 'phase_a_voltage',
'unique_id': 'tuya.nnqlg0rxryraf8ezbdnzphase_avoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.meter_phase_a_voltage-state]
@@ -13337,7 +13337,7 @@
'device_class': 'voltage',
'friendly_name': 'Meter Phase A voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.meter_phase_a_voltage',
@@ -13490,7 +13490,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -13502,7 +13502,7 @@
'supported_features': 0,
'translation_key': 'phase_a_current',
'unique_id': 'tuya.obb7p55c0us6rdxkqldphase_aelectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_phase_a_current-state]
@@ -13511,7 +13511,7 @@
'device_class': 'current',
'friendly_name': 'Metering_3PN_WiFi_stable Phase A current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.metering_3pn_wifi_stable_phase_a_current',
@@ -13550,7 +13550,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -13562,7 +13562,7 @@
'supported_features': 0,
'translation_key': 'phase_a_power',
'unique_id': 'tuya.obb7p55c0us6rdxkqldphase_apower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_phase_a_power-state]
@@ -13571,7 +13571,7 @@
'device_class': 'power',
'friendly_name': 'Metering_3PN_WiFi_stable Phase A power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.metering_3pn_wifi_stable_phase_a_power',
@@ -13619,7 +13619,7 @@
'supported_features': 0,
'translation_key': 'phase_a_voltage',
'unique_id': 'tuya.obb7p55c0us6rdxkqldphase_avoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_phase_a_voltage-state]
@@ -13628,7 +13628,7 @@
'device_class': 'voltage',
'friendly_name': 'Metering_3PN_WiFi_stable Phase A voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.metering_3pn_wifi_stable_phase_a_voltage',
@@ -13667,7 +13667,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -13679,7 +13679,7 @@
'supported_features': 0,
'translation_key': 'phase_b_current',
'unique_id': 'tuya.obb7p55c0us6rdxkqldphase_belectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_phase_b_current-state]
@@ -13688,7 +13688,7 @@
'device_class': 'current',
'friendly_name': 'Metering_3PN_WiFi_stable Phase B current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.metering_3pn_wifi_stable_phase_b_current',
@@ -13727,7 +13727,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -13739,7 +13739,7 @@
'supported_features': 0,
'translation_key': 'phase_b_power',
'unique_id': 'tuya.obb7p55c0us6rdxkqldphase_bpower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_phase_b_power-state]
@@ -13748,7 +13748,7 @@
'device_class': 'power',
'friendly_name': 'Metering_3PN_WiFi_stable Phase B power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.metering_3pn_wifi_stable_phase_b_power',
@@ -13796,7 +13796,7 @@
'supported_features': 0,
'translation_key': 'phase_b_voltage',
'unique_id': 'tuya.obb7p55c0us6rdxkqldphase_bvoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_phase_b_voltage-state]
@@ -13805,7 +13805,7 @@
'device_class': 'voltage',
'friendly_name': 'Metering_3PN_WiFi_stable Phase B voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.metering_3pn_wifi_stable_phase_b_voltage',
@@ -13844,7 +13844,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -13856,7 +13856,7 @@
'supported_features': 0,
'translation_key': 'phase_c_current',
'unique_id': 'tuya.obb7p55c0us6rdxkqldphase_celectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_phase_c_current-state]
@@ -13865,7 +13865,7 @@
'device_class': 'current',
'friendly_name': 'Metering_3PN_WiFi_stable Phase C current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.metering_3pn_wifi_stable_phase_c_current',
@@ -13904,7 +13904,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -13916,7 +13916,7 @@
'supported_features': 0,
'translation_key': 'phase_c_power',
'unique_id': 'tuya.obb7p55c0us6rdxkqldphase_cpower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_phase_c_power-state]
@@ -13925,7 +13925,7 @@
'device_class': 'power',
'friendly_name': 'Metering_3PN_WiFi_stable Phase C power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.metering_3pn_wifi_stable_phase_c_power',
@@ -13973,7 +13973,7 @@
'supported_features': 0,
'translation_key': 'phase_c_voltage',
'unique_id': 'tuya.obb7p55c0us6rdxkqldphase_cvoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.metering_3pn_wifi_stable_phase_c_voltage-state]
@@ -13982,7 +13982,7 @@
'device_class': 'voltage',
'friendly_name': 'Metering_3PN_WiFi_stable Phase C voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.metering_3pn_wifi_stable_phase_c_voltage',
@@ -15292,7 +15292,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -15304,7 +15304,7 @@
'supported_features': 0,
'translation_key': 'phase_a_current',
'unique_id': 'tuya.bcyciyhhu1g2gk9rqldphase_aelectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.p1_energia_elettrica_phase_a_current-state]
@@ -15313,7 +15313,7 @@
'device_class': 'current',
'friendly_name': 'P1 Energia Elettrica Phase A current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.p1_energia_elettrica_phase_a_current',
@@ -15352,7 +15352,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -15364,7 +15364,7 @@
'supported_features': 0,
'translation_key': 'phase_a_power',
'unique_id': 'tuya.bcyciyhhu1g2gk9rqldphase_apower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.p1_energia_elettrica_phase_a_power-state]
@@ -15373,7 +15373,7 @@
'device_class': 'power',
'friendly_name': 'P1 Energia Elettrica Phase A power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.p1_energia_elettrica_phase_a_power',
@@ -15421,7 +15421,7 @@
'supported_features': 0,
'translation_key': 'phase_a_voltage',
'unique_id': 'tuya.bcyciyhhu1g2gk9rqldphase_avoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.p1_energia_elettrica_phase_a_voltage-state]
@@ -15430,7 +15430,7 @@
'device_class': 'voltage',
'friendly_name': 'P1 Energia Elettrica Phase A voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.p1_energia_elettrica_phase_a_voltage',
@@ -15469,7 +15469,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -15481,7 +15481,7 @@
'supported_features': 0,
'translation_key': 'phase_b_current',
'unique_id': 'tuya.bcyciyhhu1g2gk9rqldphase_belectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.p1_energia_elettrica_phase_b_current-state]
@@ -15490,7 +15490,7 @@
'device_class': 'current',
'friendly_name': 'P1 Energia Elettrica Phase B current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.p1_energia_elettrica_phase_b_current',
@@ -15529,7 +15529,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -15541,7 +15541,7 @@
'supported_features': 0,
'translation_key': 'phase_b_power',
'unique_id': 'tuya.bcyciyhhu1g2gk9rqldphase_bpower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.p1_energia_elettrica_phase_b_power-state]
@@ -15550,7 +15550,7 @@
'device_class': 'power',
'friendly_name': 'P1 Energia Elettrica Phase B power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.p1_energia_elettrica_phase_b_power',
@@ -15598,7 +15598,7 @@
'supported_features': 0,
'translation_key': 'phase_b_voltage',
'unique_id': 'tuya.bcyciyhhu1g2gk9rqldphase_bvoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.p1_energia_elettrica_phase_b_voltage-state]
@@ -15607,7 +15607,7 @@
'device_class': 'voltage',
'friendly_name': 'P1 Energia Elettrica Phase B voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.p1_energia_elettrica_phase_b_voltage',
@@ -15646,7 +15646,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'suggested_unit_of_measurement': 'A',
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
@@ -15658,7 +15658,7 @@
'supported_features': 0,
'translation_key': 'phase_c_current',
'unique_id': 'tuya.bcyciyhhu1g2gk9rqldphase_celectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.p1_energia_elettrica_phase_c_current-state]
@@ -15667,7 +15667,7 @@
'device_class': 'current',
'friendly_name': 'P1 Energia Elettrica Phase C current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.p1_energia_elettrica_phase_c_current',
@@ -15706,7 +15706,7 @@
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'suggested_unit_of_measurement': 'kW',
}),
}),
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
@@ -15718,7 +15718,7 @@
'supported_features': 0,
'translation_key': 'phase_c_power',
'unique_id': 'tuya.bcyciyhhu1g2gk9rqldphase_cpower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.p1_energia_elettrica_phase_c_power-state]
@@ -15727,7 +15727,7 @@
'device_class': 'power',
'friendly_name': 'P1 Energia Elettrica Phase C power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.p1_energia_elettrica_phase_c_power',
@@ -15775,7 +15775,7 @@
'supported_features': 0,
'translation_key': 'phase_c_voltage',
'unique_id': 'tuya.bcyciyhhu1g2gk9rqldphase_cvoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.p1_energia_elettrica_phase_c_voltage-state]
@@ -15784,7 +15784,7 @@
'device_class': 'voltage',
'friendly_name': 'P1 Energia Elettrica Phase C voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.p1_energia_elettrica_phase_c_voltage',
@@ -24209,7 +24209,7 @@
'supported_features': 0,
'translation_key': 'phase_a_current',
'unique_id': 'tuya.9oh1h1uyalfykgg4bdnzphase_aelectriccurrent',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
})
# ---
# name: test_platform_setup_and_discovery[sensor.xoca_dac212xc_v2_s1_phase_a_current-state]
@@ -24218,7 +24218,7 @@
'device_class': 'current',
'friendly_name': 'XOCA-DAC212XC V2-S1 Phase A current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
'unit_of_measurement': 'A',
}),
'context': <ANY>,
'entity_id': 'sensor.xoca_dac212xc_v2_s1_phase_a_current',
@@ -24266,7 +24266,7 @@
'supported_features': 0,
'translation_key': 'phase_a_power',
'unique_id': 'tuya.9oh1h1uyalfykgg4bdnzphase_apower',
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
})
# ---
# name: test_platform_setup_and_discovery[sensor.xoca_dac212xc_v2_s1_phase_a_power-state]
@@ -24275,7 +24275,7 @@
'device_class': 'power',
'friendly_name': 'XOCA-DAC212XC V2-S1 Phase A power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
'unit_of_measurement': 'kW',
}),
'context': <ANY>,
'entity_id': 'sensor.xoca_dac212xc_v2_s1_phase_a_power',
@@ -24323,7 +24323,7 @@
'supported_features': 0,
'translation_key': 'phase_a_voltage',
'unique_id': 'tuya.9oh1h1uyalfykgg4bdnzphase_avoltage',
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
})
# ---
# name: test_platform_setup_and_discovery[sensor.xoca_dac212xc_v2_s1_phase_a_voltage-state]
@@ -24332,7 +24332,7 @@
'device_class': 'voltage',
'friendly_name': 'XOCA-DAC212XC V2-S1 Phase A voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
'unit_of_measurement': 'V',
}),
'context': <ANY>,
'entity_id': 'sensor.xoca_dac212xc_v2_s1_phase_a_voltage',