Skip to content

Commit badebe0

Browse files
authored
Refactor Tuya event platform to use DeviceWrapper (home-assistant#160366)
1 parent 7817ec1 commit badebe0

2 files changed

Lines changed: 56 additions & 52 deletions

File tree

homeassistant/components/tuya/event.py

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,80 +21,66 @@
2121
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
2222
from .entity import TuyaEntity
2323
from .models import (
24+
DeviceWrapper,
2425
DPCodeEnumWrapper,
2526
DPCodeRawWrapper,
2627
DPCodeStringWrapper,
2728
DPCodeTypeInformationWrapper,
2829
)
2930

3031

31-
class _DPCodeEventWrapper(DPCodeTypeInformationWrapper):
32-
"""Base class for Tuya event wrappers."""
33-
34-
def __init__(self, dpcode: str, type_information: Any) -> None:
35-
"""Init _DPCodeEventWrapper."""
36-
super().__init__(dpcode, type_information)
37-
self.options = ["triggered"]
38-
39-
def get_event_type(
40-
self, device: CustomerDevice, updated_status_properties: list[str] | None
41-
) -> str | None:
42-
"""Return the event type."""
43-
if (
44-
updated_status_properties is None
45-
or self.dpcode not in updated_status_properties
46-
):
47-
return None
48-
return "triggered"
49-
50-
def get_event_attributes(self, device: CustomerDevice) -> dict[str, Any] | None:
51-
"""Return the event attributes."""
52-
return None
53-
54-
55-
class _EventEnumWrapper(DPCodeEnumWrapper, _DPCodeEventWrapper):
32+
class _EventEnumWrapper(DPCodeEnumWrapper):
5633
"""Wrapper for event enum DP codes."""
5734

58-
def get_event_type(
59-
self, device: CustomerDevice, updated_status_properties: list[str] | None
60-
) -> str | None:
61-
"""Return the triggered event type."""
62-
if (
63-
updated_status_properties is None
64-
or self.dpcode not in updated_status_properties
65-
):
35+
def read_device_status(self, device: CustomerDevice) -> tuple[str, None] | None:
36+
"""Return the event details."""
37+
if (raw_value := super().read_device_status(device)) is None:
6638
return None
67-
return self.read_device_status(device)
39+
return (raw_value, None)
6840

6941

70-
class _AlarmMessageWrapper(DPCodeStringWrapper, _DPCodeEventWrapper):
42+
class _AlarmMessageWrapper(DPCodeStringWrapper):
7143
"""Wrapper for a STRING message on DPCode.ALARM_MESSAGE."""
7244

73-
def get_event_attributes(self, device: CustomerDevice) -> dict[str, Any] | None:
45+
def __init__(self, dpcode: str, type_information: Any) -> None:
46+
"""Init _AlarmMessageWrapper."""
47+
super().__init__(dpcode, type_information)
48+
self.options = ["triggered"]
49+
50+
def read_device_status(
51+
self, device: CustomerDevice
52+
) -> tuple[str, dict[str, Any]] | None:
7453
"""Return the event attributes for the alarm message."""
75-
if (raw_value := device.status.get(self.dpcode)) is None:
54+
if (raw_value := super().read_device_status(device)) is None:
7655
return None
77-
return {"message": b64decode(raw_value).decode("utf-8")}
56+
return ("triggered", {"message": b64decode(raw_value).decode("utf-8")})
7857

7958

80-
class _DoorbellPicWrapper(DPCodeRawWrapper, _DPCodeEventWrapper):
59+
class _DoorbellPicWrapper(DPCodeRawWrapper):
8160
"""Wrapper for a RAW message on DPCode.DOORBELL_PIC.
8261
8362
It is expected that the RAW data is base64/utf8 encoded URL of the picture.
8463
"""
8564

86-
def get_event_attributes(self, device: CustomerDevice) -> dict[str, Any] | None:
65+
def __init__(self, dpcode: str, type_information: Any) -> None:
66+
"""Init _DoorbellPicWrapper."""
67+
super().__init__(dpcode, type_information)
68+
self.options = ["triggered"]
69+
70+
def read_device_status(
71+
self, device: CustomerDevice
72+
) -> tuple[str, dict[str, Any]] | None:
8773
"""Return the event attributes for the doorbell picture."""
8874
if (status := super().read_device_status(device)) is None:
8975
return None
90-
return {"message": status.decode("utf-8")}
76+
return ("triggered", {"message": status.decode("utf-8")})
9177

9278

9379
@dataclass(frozen=True)
9480
class TuyaEventEntityDescription(EventEntityDescription):
9581
"""Describe a Tuya Event entity."""
9682

97-
wrapper_class: type[_DPCodeEventWrapper] = _EventEnumWrapper
83+
wrapper_class: type[DPCodeTypeInformationWrapper] = _EventEnumWrapper
9884

9985

10086
# All descriptions can be found here. Mostly the Enum data types in the
@@ -220,7 +206,7 @@ def __init__(
220206
device: CustomerDevice,
221207
device_manager: Manager,
222208
description: EventEntityDescription,
223-
dpcode_wrapper: _DPCodeEventWrapper,
209+
dpcode_wrapper: DeviceWrapper[tuple[str, dict[str, Any] | None]],
224210
) -> None:
225211
"""Init Tuya event entity."""
226212
super().__init__(device, device_manager)
@@ -234,15 +220,11 @@ async def _handle_state_update(
234220
updated_status_properties: list[str] | None,
235221
dp_timestamps: dict | None = None,
236222
) -> None:
237-
if (
238-
event_type := self._dpcode_wrapper.get_event_type(
239-
self.device, updated_status_properties
240-
)
241-
) is None:
223+
if self._dpcode_wrapper.skip_update(
224+
self.device, updated_status_properties
225+
) or not (event_data := self._dpcode_wrapper.read_device_status(self.device)):
242226
return
243227

244-
self._trigger_event(
245-
event_type,
246-
self._dpcode_wrapper.get_event_attributes(self.device),
247-
)
228+
event_type, event_attributes = event_data
229+
self._trigger_event(event_type, event_attributes)
248230
self.async_write_ha_state()

homeassistant/components/tuya/models.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ class DeviceWrapper[T]:
3030

3131
options: list[str]
3232

33+
def skip_update(
34+
self, device: CustomerDevice, updated_status_properties: list[str] | None
35+
) -> bool:
36+
"""Determine if the wrapper should skip an update.
37+
38+
The default is to always skip, unless overridden in subclasses.
39+
"""
40+
return True
41+
3342
def read_device_status(self, device: CustomerDevice) -> T | None:
3443
"""Read device status and convert to a Home Assistant value."""
3544
raise NotImplementedError
@@ -52,6 +61,19 @@ def __init__(self, dpcode: str) -> None:
5261
"""Init DPCodeWrapper."""
5362
self.dpcode = dpcode
5463

64+
def skip_update(
65+
self, device: CustomerDevice, updated_status_properties: list[str] | None
66+
) -> bool:
67+
"""Determine if the wrapper should skip an update.
68+
69+
By default, skip if updated_status_properties is given and
70+
does not include this dpcode.
71+
"""
72+
return (
73+
updated_status_properties is None
74+
or self.dpcode not in updated_status_properties
75+
)
76+
5577
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
5678
"""Convert a Home Assistant value back to a raw device value.
5779

0 commit comments

Comments
 (0)