File: dpt_9_float_test.py

package info (click to toggle)
python-xknx 3.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,012 kB
  • sloc: python: 39,710; javascript: 8,556; makefile: 27; sh: 12
file content (215 lines) | stat: -rw-r--r-- 8,803 bytes parent folder | download
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"""Unit test for KNX 2 byte float objects."""

import pytest

from xknx.dpt import (
    DPT2ByteFloat,
    DPTAirFlow,
    DPTArray,
    DPTEnthalpy,
    DPTHumidity,
    DPTLux,
    DPTPartsPerMillion,
    DPTTemperature,
    DPTVoltage,
)
from xknx.exceptions import ConversionError, CouldNotParseTelegram


class TestDPTFloat:
    """Test class for KNX 2 byte/octet float object."""

    # ####################################################################
    # DPT2ByteFloat
    #
    def test_value_from_documentation(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat -30.00. Example from the internet[tm]."""
        assert DPT2ByteFloat.to_knx(-30.00) == DPTArray((0x8A, 0x24))
        assert DPT2ByteFloat.from_knx(DPTArray((0x8A, 0x24))) == -30.00

    def test_value_taken_from_live_thermostat(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat 19.96."""
        assert DPT2ByteFloat.to_knx(16.96) == DPTArray((0x06, 0xA0))
        assert DPT2ByteFloat.from_knx(DPTArray((0x06, 0xA0))) == 16.96

    def test_zero_value(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat zero value."""
        assert DPT2ByteFloat.to_knx(0.00) == DPTArray((0x00, 0x00))
        assert DPT2ByteFloat.from_knx(DPTArray((0x00, 0x00))) == 0.00

    def test_near_zero_value(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat near zero value."""
        assert DPT2ByteFloat.to_knx(0.0002) == DPTArray((0x00, 0x00))
        assert DPT2ByteFloat.to_knx(0.005) == DPTArray((0x00, 0x00))
        assert DPT2ByteFloat.to_knx(0.00501) == DPTArray((0x00, 0x01))

        assert DPT2ByteFloat.to_knx(-0.0) == DPTArray((0x00, 0x00))
        # ETS would convert values < 0 and >= -0.005 to 0x8000
        # which is equivalent to -20.48 so we handle this differently
        assert DPT2ByteFloat.to_knx(-0.0002) == DPTArray((0x00, 0x00))
        assert DPT2ByteFloat.to_knx(-0.005) == DPTArray((0x00, 0x00))
        assert DPT2ByteFloat.to_knx(-0.00501) == DPTArray(
            (0x87, 0xFF)
        )  # this is ETS-conform again

        assert DPT2ByteFloat.from_knx(DPTArray((0x00, 0x01))) == 0.01
        assert DPT2ByteFloat.from_knx(DPTArray((0x87, 0xFF))) == -0.01

    def test_room_temperature(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat 21.00. Room temperature."""
        assert DPT2ByteFloat.to_knx(21.00) == DPTArray((0x0C, 0x1A))
        assert DPT2ByteFloat.from_knx(DPTArray((0x0C, 0x1A))) == 21.00

    def test_high_temperature(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat 500.00, 499.84, 500.16. Testing rounding issues."""
        assert DPT2ByteFloat.to_knx(500.00) == DPTArray((0x2E, 0x1A))
        assert (
            round(abs(DPT2ByteFloat.from_knx(DPTArray((0x2E, 0x1A))) - 499.84), 7) == 0
        )
        assert (
            round(abs(DPT2ByteFloat.from_knx(DPTArray((0x2E, 0x1B))) - 500.16), 7) == 0
        )
        assert DPT2ByteFloat.to_knx(499.84) == DPTArray((0x2E, 0x1A))
        assert DPT2ByteFloat.to_knx(500.16) == DPTArray((0x2E, 0x1B))

    def test_minor_negative_temperature(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat -10.00. Testing negative values."""
        assert DPT2ByteFloat.to_knx(-10.00) == DPTArray((0x84, 0x18))
        assert DPT2ByteFloat.from_knx(DPTArray((0x84, 0x18))) == -10.00

    def test_very_cold_temperature(self) -> None:
        """
        Test parsing and streaming of DPT2ByteFloat -1000.00,-999.68, -1000.32.

        Testing rounding issues of negative values.
        """
        assert DPT2ByteFloat.to_knx(-1000.00) == DPTArray((0xB1, 0xE6))
        assert DPT2ByteFloat.from_knx(DPTArray((0xB1, 0xE6))) == -999.68
        assert DPT2ByteFloat.from_knx(DPTArray((0xB1, 0xE5))) == -1000.32
        assert DPT2ByteFloat.to_knx(-999.68) == DPTArray((0xB1, 0xE6))
        assert DPT2ByteFloat.to_knx(-1000.32) == DPTArray((0xB1, 0xE5))

    def test_max(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat with maximum value."""
        assert DPT2ByteFloat.to_knx(DPT2ByteFloat.value_max) == DPTArray((0x7F, 0xFF))
        assert DPT2ByteFloat.from_knx(DPTArray((0x7F, 0xFF))) == DPT2ByteFloat.value_max

    def test_close_to_limit(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat with numeric limit."""
        assert DPT2ByteFloat.to_knx(20.48) == DPTArray((0x0C, 0x00))
        assert DPT2ByteFloat.from_knx(DPTArray((0x0C, 0x00))) == 20.48
        assert DPT2ByteFloat.to_knx(-20.48) == DPTArray((0x80, 0x00))
        assert DPT2ByteFloat.from_knx(DPTArray((0x80, 0x00))) == -20.48

    def test_min(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat with minimum value."""
        assert DPT2ByteFloat.to_knx(DPT2ByteFloat.value_min) == DPTArray((0xF8, 0x00))
        assert DPT2ByteFloat.from_knx(DPTArray((0xF8, 0x00))) == DPT2ByteFloat.value_min

    def test_close_to_max(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat with maximum value -1."""
        assert DPT2ByteFloat.to_knx(670433.28) == DPTArray((0x7F, 0xFE))
        assert DPT2ByteFloat.from_knx(DPTArray((0x7F, 0xFE))) == 670433.28

    def test_close_to_min(self) -> None:
        """Test parsing and streaming of DPT2ByteFloat with minimum value +1."""
        assert DPT2ByteFloat.to_knx(-670760.96) == DPTArray((0xF8, 0x01))
        assert DPT2ByteFloat.from_knx(DPTArray((0xF8, 0x01))) == -670760.96

    def test_to_knx_min_exceeded(self) -> None:
        """Test parsing of DPT2ByteFloat with wrong value (underflow)."""
        with pytest.raises(ConversionError):
            DPT2ByteFloat.to_knx(DPT2ByteFloat.value_min - 1)

    def test_to_knx_max_exceeded(self) -> None:
        """Test parsing of DPT2ByteFloat with wrong value (overflow)."""
        with pytest.raises(ConversionError):
            DPT2ByteFloat.to_knx(DPT2ByteFloat.value_max + 1)

    def test_to_knx_wrong_parameter(self) -> None:
        """Test parsing of DPT2ByteFloat with wrong value (string)."""
        with pytest.raises(ConversionError):
            DPT2ByteFloat.to_knx("fnord")

    def test_from_knx_wrong_parameter(self) -> None:
        """Test parsing of DPT2ByteFloat with wrong value (wrong number of bytes)."""
        with pytest.raises(CouldNotParseTelegram):
            DPT2ByteFloat.from_knx(DPTArray((0xF8, 0x01, 0x23)))

    #
    # DPTTemperature
    #
    def test_temperature_settings(self) -> None:
        """Test attributes of DPTTemperature."""
        assert DPTTemperature.value_min == -273
        assert DPTTemperature.value_max == 670760
        assert DPTTemperature.unit == "°C"
        assert DPTTemperature.resolution == 0.01

    def test_temperature_assert_min_exceeded(self) -> None:
        """Testing parsing of DPTTemperature with wrong value."""
        with pytest.raises(ConversionError):
            DPTTemperature.to_knx(-274)

    def test_temperature_assert_min_exceeded_from_knx(self) -> None:
        """Testing parsing of DPTTemperature with wrong value."""
        with pytest.raises(ConversionError):
            DPTTemperature.from_knx(DPTArray((0xB1, 0xE6)))  # -1000

    #
    # DPTLux
    #
    def test_lux_settings(self) -> None:
        """Test attributes of DPTLux."""
        assert DPTLux.value_min == 0
        assert DPTLux.value_max == 670760
        assert DPTLux.unit == "lx"
        assert DPTLux.resolution == 0.01

    def test_lux_assert_min_exceeded(self) -> None:
        """Test parsing of DPTLux with wrong value."""
        with pytest.raises(ConversionError):
            DPTLux.to_knx(-1)

    #
    # DPTHumidity
    #
    def test_humidity_settings(self) -> None:
        """Test attributes of DPTHumidity."""
        assert DPTHumidity.value_min == 0
        assert DPTHumidity.value_max == 670760
        assert DPTHumidity.unit == "%"
        assert DPTHumidity.resolution == 0.01

    def test_humidity_assert_min_exceeded(self) -> None:
        """Test parsing of DPTHumidity with wrong value."""
        with pytest.raises(ConversionError):
            DPTHumidity.to_knx(-1)

    #
    # DPTEnthalpy
    #
    def test_enthalpy_settings(self) -> None:
        """Test attributes of DPTEnthalpy."""
        assert DPTEnthalpy.unit == "H"

    #
    # DPTPartsPerMillion
    #
    def test_partspermillion_settings(self) -> None:
        """Test attributes of DPTPartsPerMillion."""
        assert DPTPartsPerMillion.unit == "ppm"

    #
    # DPTAirFlow
    #
    def test_airflow_settings(self) -> None:
        """Test attributes of DPTAirFlow."""
        assert DPTAirFlow.unit == "m³/h"

    #
    # DPTVoltage
    #
    def test_voltage_settings(self) -> None:
        """Test attributes of DPTVoltage."""
        assert DPTVoltage.unit == "mV"