-
-
Notifications
You must be signed in to change notification settings - Fork 122
Expand file tree
/
Copy pathflags_test.py
More file actions
172 lines (148 loc) · 5.77 KB
/
Copy pathflags_test.py
File metadata and controls
172 lines (148 loc) · 5.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""Tests for the cEMI L_Data control fields."""
import pytest
from xknx.cemi import (
CEMIAddressType,
CEMIFlags,
CEMIFrameFormat,
CEMIFrameType,
CEMIPriority,
)
from xknx.exceptions import ConversionError
def control_field(ctrl1: int, ctrl2: int) -> int:
"""Return Ctrl1 and Ctrl2 as one 16 bit control field."""
return ctrl1 << 8 | ctrl2
@pytest.mark.parametrize(
"ctrl1,ctrl2,flags",
[
(
# Ctrl1 0xBC: standard frame, do not repeat, broadcast, low priority
# Ctrl2 0xE0: group address, hop count 6, standard frame format
0xBC,
0xE0,
CEMIFlags(priority=CEMIPriority.LOW, hop_count=6),
),
(
# Ctrl1 0x3C: extended frame - kept, but not evaluated
0x3C,
0xE0,
CEMIFlags(
priority=CEMIPriority.LOW,
hop_count=6,
frame_type=CEMIFrameType.EXTENDED,
),
),
(
# Ctrl1 0xB0: system priority; Ctrl2 0x60: individual address
0xB0,
0x60,
CEMIFlags(priority=CEMIPriority.SYSTEM, hop_count=6),
),
(
# every optional flag set: repeat on error, system broadcast,
# acknowledge requested, confirm error, urgent priority, hop count 7
0b1000_1011,
0b0111_0000,
CEMIFlags(
priority=CEMIPriority.URGENT,
repeat_on_error=True,
system_broadcast=True,
acknowledge_request=True,
confirm_error=True,
hop_count=7,
),
),
],
)
def test_from_knx(ctrl1: int, ctrl2: int, flags: CEMIFlags) -> None:
"""Test parsing of the control field."""
assert CEMIFlags.from_knx(control_field(ctrl1, ctrl2)) == flags
@pytest.mark.parametrize(
"eff,frame_format",
[
(0b0000, CEMIFrameFormat.STANDARD),
# the whole 01xxb range is LTE-HEE; the 2 lsb are the LTE address type
(0b0100, CEMIFrameFormat.LTE_HEE),
(0b0101, CEMIFrameFormat.LTE_HEE),
(0b0110, CEMIFrameFormat.LTE_HEE),
(0b0111, CEMIFrameFormat.LTE_HEE),
],
)
def test_frame_format_from_knx(eff: int, frame_format: CEMIFrameFormat) -> None:
"""Test parsing of the Extended Frame Format field."""
flags = CEMIFlags.from_knx(control_field(0xBC, 0xE0 | eff))
assert flags.frame_format is frame_format
@pytest.mark.parametrize("eff", [0b0001, 0b0010, 0b0011, 0b1000, 0b1100, 0b1111])
def test_reserved_frame_format(eff: int) -> None:
"""Test parsing of a reserved Extended Frame Format."""
with pytest.raises(ConversionError, match=r".*Reserved Extended Frame Format.*"):
CEMIFlags.from_knx(control_field(0xBC, 0xE0 | eff))
@pytest.mark.parametrize(
"priority,ctrl1",
[
(CEMIPriority.SYSTEM, 0b0011_0000),
(CEMIPriority.NORMAL, 0b0011_0100),
(CEMIPriority.URGENT, 0b0011_1000),
(CEMIPriority.LOW, 0b0011_1100),
],
)
def test_priority_to_knx(priority: CEMIPriority, ctrl1: int) -> None:
"""Test priority encoding - 3/2/2 §2.2.2 Figure 28."""
# the Frame Type bit is not set by `CEMIFlags.to_knx()`
assert CEMIFlags(priority=priority).to_knx() >> 8 == ctrl1
def test_to_knx_leaves_derived_bits_clear() -> None:
"""Test Frame Type and Address Type are not set by `CEMIFlags.to_knx()`."""
raw = CEMIFlags().to_knx()
assert raw == control_field(0b0011_1100, 0b0110_0000)
assert CEMIFrameType.from_knx(raw) is CEMIFrameType.EXTENDED # ie. bit not set
assert CEMIAddressType.from_knx(raw) is CEMIAddressType.INDIVIDUAL
@pytest.mark.parametrize(
"frame_type,address_type,expected",
[
(CEMIFrameType.STANDARD, CEMIAddressType.GROUP, (0xBC, 0xE0)),
(CEMIFrameType.EXTENDED, CEMIAddressType.GROUP, (0x3C, 0xE0)),
(CEMIFrameType.STANDARD, CEMIAddressType.INDIVIDUAL, (0xBC, 0x60)),
(CEMIFrameType.EXTENDED, CEMIAddressType.INDIVIDUAL, (0x3C, 0x60)),
],
)
def test_composition(
frame_type: CEMIFrameType,
address_type: CEMIAddressType,
expected: tuple[int, int],
) -> None:
"""Test the frame ORs the derived bits into the flags."""
raw = CEMIFlags().to_knx() | frame_type.to_knx() | address_type.to_knx()
assert raw == control_field(*expected)
def test_to_knx_ignores_received_frame_type() -> None:
"""Test the Frame Type of a received frame is not used when serializing."""
flags = CEMIFlags.from_knx(control_field(0x3C, 0xE0))
assert flags.frame_type is CEMIFrameType.EXTENDED
raw = (
flags.to_knx()
| CEMIFrameType.STANDARD.to_knx()
| CEMIAddressType.GROUP.to_knx()
)
assert raw == control_field(0xBC, 0xE0)
def test_round_trip() -> None:
"""Test parsing and serializing yields the same control field."""
for ctrl1 in (0xBC, 0xB0, 0x9C, 0xBF, 0x3C):
for ctrl2 in (0xE0, 0x60, 0xF0, 0x00):
raw = control_field(ctrl1, ctrl2)
flags = CEMIFlags.from_knx(raw)
assert (
flags.to_knx()
| flags.frame_type.to_knx()
| CEMIAddressType.from_knx(raw).to_knx()
) == raw
@pytest.mark.parametrize("hop_count", [-1, 8, 255])
def test_invalid_hop_count(hop_count: int) -> None:
"""Test hop count out of range."""
with pytest.raises(ConversionError, match=r".*Hop count out of range.*"):
CEMIFlags(hop_count=hop_count).to_knx()
def test_str() -> None:
"""Test the compact string representation only lists flags that are set."""
assert str(CEMIFlags()) == "LOW STANDARD hop_count=6"
assert (
str(CEMIFlags.from_knx(control_field(0b1000_1011, 0b0111_0000)))
== "URGENT STANDARD hop_count=7 repeat_on_error system_broadcast "
"acknowledge_request confirm_error"
)