mirror of
https://github.com/home-assistant/core.git
synced 2026-05-08 17:49:37 +01:00
Add async dpcode update wrapper to Tuya (#156230)
This commit is contained in:
@@ -11,6 +11,7 @@ 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 DPCodeWrapper
|
||||
|
||||
|
||||
class TuyaEntity(Entity):
|
||||
@@ -64,3 +65,12 @@ class TuyaEntity(Entity):
|
||||
"""Send command to the device."""
|
||||
LOGGER.debug("Sending commands for device %s: %s", self.device.id, commands)
|
||||
self.device_manager.send_commands(self.device.id, commands)
|
||||
|
||||
async def _async_send_dpcode_update(
|
||||
self, dpcode_wrapper: DPCodeWrapper, value: Any
|
||||
) -> None:
|
||||
"""Send command to the device."""
|
||||
await self.hass.async_add_executor_job(
|
||||
self._send_command,
|
||||
[dpcode_wrapper.get_update_command(self.device, value)],
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
import base64
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
@@ -120,7 +121,7 @@ _TYPE_INFORMATION_MAPPINGS: dict[DPType, type[TypeInformation]] = {
|
||||
}
|
||||
|
||||
|
||||
class DPCodeWrapper:
|
||||
class DPCodeWrapper(ABC):
|
||||
"""Base DPCode wrapper.
|
||||
|
||||
Used as a common interface for referring to a DPCode, and
|
||||
@@ -138,9 +139,30 @@ class DPCodeWrapper:
|
||||
"""
|
||||
return device.status.get(self.dpcode)
|
||||
|
||||
@abstractmethod
|
||||
def read_device_status(self, device: CustomerDevice) -> Any | None:
|
||||
"""Read the device value for the dpcode."""
|
||||
raise NotImplementedError("read_device_status must be implemented")
|
||||
"""Read the device value for the dpcode.
|
||||
|
||||
The raw device status is converted to a Home Assistant value.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
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_command` to prepare the value for sending
|
||||
back to the device, and should be implemented in concrete classes.
|
||||
"""
|
||||
|
||||
def get_update_command(self, device: CustomerDevice, value: Any) -> dict[str, Any]:
|
||||
"""Get the update command 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 DPCodeBooleanWrapper(DPCodeWrapper):
|
||||
@@ -155,6 +177,16 @@ class DPCodeBooleanWrapper(DPCodeWrapper):
|
||||
return raw_value
|
||||
return None
|
||||
|
||||
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 DPCodeTypeInformationWrapper[T: TypeInformation](DPCodeWrapper):
|
||||
"""Base DPCode wrapper with Type Information."""
|
||||
@@ -202,6 +234,16 @@ class DPCodeEnumWrapper(DPCodeTypeInformationWrapper[EnumTypeData]):
|
||||
return raw_value
|
||||
return None
|
||||
|
||||
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[IntegerTypeData]):
|
||||
"""Simple wrapper for IntegerTypeData values."""
|
||||
@@ -217,6 +259,18 @@ class DPCodeIntegerWrapper(DPCodeTypeInformationWrapper[IntegerTypeData]):
|
||||
return None
|
||||
return raw_value / (10**self.type_information.scale)
|
||||
|
||||
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})"
|
||||
)
|
||||
|
||||
|
||||
@overload
|
||||
def find_dpcode(
|
||||
|
||||
@@ -540,15 +540,6 @@ class TuyaNumberEntity(TuyaEntity, NumberEntity):
|
||||
"""Return the entity value to represent the entity state."""
|
||||
return self._dpcode_wrapper.read_device_status(self.device)
|
||||
|
||||
def set_native_value(self, value: float) -> None:
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Set new value."""
|
||||
self._send_command(
|
||||
[
|
||||
{
|
||||
"code": self._dpcode_wrapper.dpcode,
|
||||
"value": (
|
||||
self._dpcode_wrapper.type_information.scale_value_back(value)
|
||||
),
|
||||
}
|
||||
]
|
||||
)
|
||||
await self._async_send_dpcode_update(self._dpcode_wrapper, value)
|
||||
|
||||
@@ -402,6 +402,6 @@ class TuyaSelectEntity(TuyaEntity, SelectEntity):
|
||||
"""Return the selected entity option to represent the entity state."""
|
||||
return self._dpcode_wrapper.read_device_status(self.device)
|
||||
|
||||
def select_option(self, option: str) -> None:
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
self._send_command([{"code": self._dpcode_wrapper.dpcode, "value": option}])
|
||||
await self._async_send_dpcode_update(self._dpcode_wrapper, option)
|
||||
|
||||
@@ -1041,10 +1041,10 @@ class TuyaSwitchEntity(TuyaEntity, SwitchEntity):
|
||||
"""Return true if switch is on."""
|
||||
return self._dpcode_wrapper.read_device_status(self.device)
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
self._send_command([{"code": self._dpcode_wrapper.dpcode, "value": True}])
|
||||
await self._async_send_dpcode_update(self._dpcode_wrapper, True)
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
self._send_command([{"code": self._dpcode_wrapper.dpcode, "value": False}])
|
||||
await self._async_send_dpcode_update(self._dpcode_wrapper, False)
|
||||
|
||||
@@ -140,12 +140,8 @@ class TuyaValveEntity(TuyaEntity, ValveEntity):
|
||||
|
||||
async def async_open_valve(self) -> None:
|
||||
"""Open the valve."""
|
||||
await self.hass.async_add_executor_job(
|
||||
self._send_command, [{"code": self._dpcode_wrapper.dpcode, "value": True}]
|
||||
)
|
||||
await self._async_send_dpcode_update(self._dpcode_wrapper, True)
|
||||
|
||||
async def async_close_valve(self) -> None:
|
||||
"""Close the valve."""
|
||||
await self.hass.async_add_executor_job(
|
||||
self._send_command, [{"code": self._dpcode_wrapper.dpcode, "value": False}]
|
||||
)
|
||||
await self._async_send_dpcode_update(self._dpcode_wrapper, False)
|
||||
|
||||
Reference in New Issue
Block a user