Skip to content

Commit c340353

Browse files
committed
convert stick_test_data to class
1 parent 9078821 commit c340353

File tree

2 files changed

+152
-146
lines changed

2 files changed

+152
-146
lines changed

tests/stick_test_data.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
"""Stick Test Program."""
22

3+
import asyncio
4+
from collections.abc import Callable
35
from datetime import UTC, datetime, timedelta
46
import importlib
7+
import logging
8+
import random
9+
10+
import crcmod
11+
12+
crc_fun = crcmod.mkCrcFun(0x11021, rev=False, initCrc=0x0000, xorOut=0x0000)
13+
14+
15+
_LOGGER = logging.getLogger(__name__)
16+
_LOGGER.setLevel(logging.DEBUG)
517

618
pw_constants = importlib.import_module("plugwise_usb.constants")
719

@@ -12,6 +24,124 @@
1224
# generate energy log timestamps with fixed hour timestamp used in tests
1325
hour_timestamp = utc_now.replace(minute=0, second=0, microsecond=0)
1426

27+
28+
def construct_message(data: bytes, seq_id: bytes = b"0000") -> bytes:
29+
"""Construct plugwise message."""
30+
body = data[:4] + seq_id + data[4:]
31+
return bytes(
32+
pw_constants.MESSAGE_HEADER
33+
+ body
34+
+ bytes(f"{crc_fun(body):04X}", pw_constants.UTF8)
35+
+ pw_constants.MESSAGE_FOOTER
36+
)
37+
38+
39+
class DummyTransport:
40+
"""Dummy transport class."""
41+
42+
protocol_data_received: Callable[[bytes], None]
43+
44+
def __init__(
45+
self,
46+
loop: asyncio.AbstractEventLoop,
47+
test_data: dict[bytes, tuple[str, bytes, bytes | None]] | None = None,
48+
) -> None:
49+
"""Initialize dummy transport class."""
50+
self._loop = loop
51+
self._msg = 0
52+
self._seq_id = b"1233"
53+
self._processed: list[bytes] = []
54+
self._first_response = test_data
55+
self._second_response = test_data
56+
if test_data is None:
57+
self._first_response = RESPONSE_MESSAGES
58+
self._second_response = SECOND_RESPONSE_MESSAGES
59+
self.random_extra_byte = 0
60+
self._closing = False
61+
62+
def inc_seq_id(self, seq_id: bytes | None) -> bytes:
63+
"""Increment sequence id."""
64+
if seq_id is None:
65+
return b"0000"
66+
temp_int = int(seq_id, 16) + 1
67+
if temp_int >= 65532:
68+
temp_int = 0
69+
temp_str = str(hex(temp_int)).lstrip("0x").upper()
70+
while len(temp_str) < 4:
71+
temp_str = "0" + temp_str
72+
return temp_str.encode()
73+
74+
def is_closing(self) -> bool:
75+
"""Close connection."""
76+
return self._closing
77+
78+
def write(self, data: bytes) -> None:
79+
"""Write data back to system."""
80+
log = None
81+
ack = None
82+
response = None
83+
if data in self._processed and self._second_response is not None:
84+
log, ack, response = self._second_response.get(data, (None, None, None))
85+
if log is None and self._first_response is not None:
86+
log, ack, response = self._first_response.get(data, (None, None, None))
87+
if log is None:
88+
resp = PARTLY_RESPONSE_MESSAGES.get(data[:24], (None, None, None))
89+
if resp is None:
90+
_LOGGER.debug("No msg response for %s", str(data))
91+
return
92+
log, ack, response = resp
93+
if ack is None:
94+
_LOGGER.debug("No ack response for %s", str(data))
95+
return
96+
97+
self._seq_id = self.inc_seq_id(self._seq_id)
98+
if response and self._msg == 0:
99+
self.message_response_at_once(ack, response, self._seq_id)
100+
self._processed.append(data)
101+
else:
102+
self.message_response(ack, self._seq_id)
103+
self._processed.append(data)
104+
if response is None or self._closing:
105+
return
106+
self._loop.create_task(self._delayed_response(response, self._seq_id))
107+
self._msg += 1
108+
109+
async def _delayed_response(self, data: bytes, seq_id: bytes) -> None:
110+
delay = random.uniform(0.005, 0.025)
111+
await asyncio.sleep(delay)
112+
self.message_response(data, seq_id)
113+
114+
def message_response(self, data: bytes, seq_id: bytes) -> None:
115+
"""Handle message response."""
116+
self.random_extra_byte += 1
117+
if self.random_extra_byte > 25:
118+
self.protocol_data_received(b"\x83")
119+
self.random_extra_byte = 0
120+
self.protocol_data_received(construct_message(data, seq_id) + b"\x83")
121+
else:
122+
self.protocol_data_received(construct_message(data, seq_id))
123+
124+
def message_response_at_once(self, ack: bytes, data: bytes, seq_id: bytes) -> None:
125+
"""Full message."""
126+
self.random_extra_byte += 1
127+
if self.random_extra_byte > 25:
128+
self.protocol_data_received(b"\x83")
129+
self.random_extra_byte = 0
130+
self.protocol_data_received(
131+
construct_message(ack, seq_id)
132+
+ construct_message(data, seq_id)
133+
+ b"\x83"
134+
)
135+
else:
136+
self.protocol_data_received(
137+
construct_message(ack, seq_id) + construct_message(data, seq_id)
138+
)
139+
140+
def close(self) -> None:
141+
"""Close connection."""
142+
self._closing = True
143+
144+
15145
LOG_TIMESTAMPS = {}
16146
_one_hour = timedelta(hours=1)
17147
for x in range(168):

0 commit comments

Comments
 (0)