Skip to content

Commit 2cf60fc

Browse files
authored
Add wrappers for event (#44)
* Add wrappers for event * Improve
1 parent 7463e3d commit 2cf60fc

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""Tuya device wrapper."""
2+
3+
from __future__ import annotations
4+
5+
import base64
6+
from typing import TYPE_CHECKING, Any
7+
8+
from .common import DPCodeEnumWrapper, DPCodeRawWrapper, DPCodeStringWrapper
9+
10+
if TYPE_CHECKING:
11+
from tuya_sharing import CustomerDevice # type: ignore[import-untyped]
12+
13+
14+
class SimpleEventEnumWrapper(DPCodeEnumWrapper[tuple[str, None]]):
15+
"""Wrapper for event enum DP codes.
16+
17+
Does not provide attributes.
18+
"""
19+
20+
def read_device_status(
21+
self, device: CustomerDevice
22+
) -> tuple[str, None] | None:
23+
"""Return the event details."""
24+
if (raw_value := self._read_dpcode_value(device)) is None:
25+
return None
26+
return (raw_value, None)
27+
28+
29+
class Base64Utf8StringWrapper(DPCodeStringWrapper[tuple[str, dict[str, Any]]]):
30+
"""Wrapper for a string message received in a base64/UTF-8 STRING DPCode.
31+
32+
Raises 'triggered' event, with the message in the event attributes.
33+
"""
34+
35+
def __init__(self, dpcode: str, type_information: Any) -> None:
36+
"""Init Base64Utf8StringWrapper."""
37+
super().__init__(dpcode, type_information)
38+
self.options = ["triggered"]
39+
40+
def read_device_status(
41+
self, device: CustomerDevice
42+
) -> tuple[str, dict[str, Any]] | None:
43+
"""Return the event attributes for the alarm message."""
44+
if (raw_value := self._read_dpcode_value(device)) is None:
45+
return None
46+
return (
47+
"triggered",
48+
{"message": base64.b64decode(raw_value).decode("utf-8")},
49+
)
50+
51+
52+
class Base64Utf8RawWrapper(DPCodeRawWrapper[tuple[str, dict[str, Any]]]):
53+
"""Wrapper for a string message received in a base64/UTF-8 RAW DPCode.
54+
55+
Raises 'triggered' event, with the message in the event attributes.
56+
"""
57+
58+
def __init__(self, dpcode: str, type_information: Any) -> None:
59+
"""Init _DoorbellPicWrapper."""
60+
super().__init__(dpcode, type_information)
61+
self.options = ["triggered"]
62+
63+
def read_device_status(
64+
self, device: CustomerDevice
65+
) -> tuple[str, dict[str, Any]] | None:
66+
"""Return the event attributes for the doorbell picture."""
67+
if (status := self._read_dpcode_value(device)) is None:
68+
return None
69+
return ("triggered", {"message": status.decode("utf-8")})

tests/device_wrapper/test_event.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""Test DeviceWrapper classes"""
2+
3+
from typing import Any
4+
5+
import pytest
6+
from tuya_sharing import CustomerDevice # type: ignore[import-untyped]
7+
8+
from tuya_device_handlers.device_wrapper.common import (
9+
DPCodeTypeInformationWrapper,
10+
)
11+
from tuya_device_handlers.device_wrapper.event import (
12+
Base64Utf8RawWrapper,
13+
Base64Utf8StringWrapper,
14+
SimpleEventEnumWrapper,
15+
)
16+
17+
try:
18+
from typeguard import suppress_type_checks # type: ignore[import-not-found]
19+
except ImportError:
20+
from contextlib import nullcontext
21+
22+
suppress_type_checks = nullcontext
23+
24+
25+
@pytest.mark.parametrize(
26+
("wrapper_type", "dpcode", "status", "expected_device_status"),
27+
[
28+
(
29+
Base64Utf8RawWrapper,
30+
"demo_raw",
31+
"aHR0cHM6Ly9zb21lLXBpY3R1cmUtdXJsLmNvbS9pbWFnZS5qcGc=",
32+
(
33+
"triggered",
34+
{"message": "https://some-picture-url.com/image.jpg"},
35+
),
36+
),
37+
(
38+
Base64Utf8StringWrapper,
39+
"demo_string",
40+
"TXkgZG9nIGF0ZSBteSBkaW5uZXI=",
41+
(
42+
"triggered",
43+
{"message": "My dog ate my dinner"},
44+
),
45+
),
46+
(
47+
SimpleEventEnumWrapper,
48+
"demo_enum",
49+
"scene",
50+
("scene", None),
51+
),
52+
],
53+
)
54+
def test_read_device_status(
55+
dpcode: str,
56+
wrapper_type: type[DPCodeTypeInformationWrapper[Any, Any]],
57+
status: Any,
58+
expected_device_status: Any,
59+
mock_device: CustomerDevice,
60+
) -> None:
61+
"""Test read_device_status."""
62+
mock_device.status[dpcode] = status
63+
wrapper = wrapper_type.find_dpcode(mock_device, dpcode)
64+
65+
assert wrapper
66+
assert wrapper.read_device_status(mock_device) == expected_device_status
67+
68+
# All wrappers return None if status is None
69+
mock_device.status[dpcode] = None
70+
assert wrapper.read_device_status(mock_device) is None
71+
72+
# All wrappers return None if status is missing
73+
mock_device.status.pop(dpcode)
74+
assert wrapper.read_device_status(mock_device) is None

0 commit comments

Comments
 (0)