Skip to content

Commit 6be31ec

Browse files
authored
Merge pull request #71 from karrenberg/dev
Add various new settables incl some getters/setters and enums.
2 parents 1f06771 + ffb968b commit 6be31ec

File tree

5 files changed

+509
-9
lines changed

5 files changed

+509
-9
lines changed

tests/data/test_data_model.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
from vallox_websocket_api.data.model import DataModel
66

77
js_folder = Path(__file__).parent / "js"
8-
js_files = [js_file.name for js_file in js_folder.glob("*.js")]
8+
js_files = [
9+
js_file.name
10+
for js_file in js_folder.glob("*.js")
11+
if not js_file.name.startswith("_")
12+
]
913

1014
json_folder = Path(__file__).parent.parent.parent / "vallox_websocket_api" / "data"
1115
json_files = [json_file.name for json_file in json_folder.glob("*.json")]

tests/test_vallox_sensors.py

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
"""Tests for Vallox WebSocket API sensor control functionality."""
2+
3+
from unittest import mock
4+
5+
import pytest
6+
7+
from vallox_websocket_api import (
8+
DefrostMode,
9+
Profile,
10+
SupplyHeatingAdjustMode,
11+
ValloxInvalidInputException,
12+
)
13+
14+
15+
@pytest.mark.parametrize(
16+
"profile, enabled",
17+
[
18+
(Profile.HOME, True),
19+
(Profile.HOME, False),
20+
(Profile.AWAY, True),
21+
(Profile.AWAY, False),
22+
(Profile.BOOST, True),
23+
(Profile.BOOST, False),
24+
],
25+
)
26+
@pytest.mark.asyncio
27+
async def test_set_rh_sensor_control(vallox, profile, enabled):
28+
"""Test setting RH sensor control."""
29+
vallox.set_values = mock.AsyncMock()
30+
await vallox.set_rh_sensor_control(profile, enabled)
31+
vallox.set_values.assert_called_once_with(
32+
{f"A_CYC_{profile.name}_RH_CTRL_ENABLED": enabled}
33+
)
34+
35+
36+
@pytest.mark.asyncio
37+
async def test_set_rh_sensor_control_for_invalid_profile(vallox):
38+
"""Test setting RH sensor control with invalid profile."""
39+
with pytest.raises(ValloxInvalidInputException):
40+
await vallox.set_rh_sensor_control(Profile.FIREPLACE, True)
41+
with pytest.raises(ValloxInvalidInputException):
42+
await vallox.set_rh_sensor_control(Profile.EXTRA, True)
43+
44+
45+
@pytest.mark.parametrize(
46+
"profile, enabled",
47+
[
48+
(Profile.HOME, True),
49+
(Profile.HOME, False),
50+
(Profile.AWAY, True),
51+
(Profile.AWAY, False),
52+
(Profile.BOOST, True),
53+
(Profile.BOOST, False),
54+
],
55+
)
56+
@pytest.mark.asyncio
57+
async def test_set_co2_sensor_control(vallox, profile, enabled):
58+
"""Test setting CO2 sensor control."""
59+
vallox.set_values = mock.AsyncMock()
60+
await vallox.set_co2_sensor_control(profile, enabled)
61+
vallox.set_values.assert_called_once_with(
62+
{f"A_CYC_{profile.name}_CO2_CTRL_ENABLED": enabled}
63+
)
64+
65+
66+
@pytest.mark.asyncio
67+
async def test_set_co2_sensor_control_with_invalid_profile(vallox):
68+
"""Test setting CO2 sensor control with invalid profile."""
69+
with pytest.raises(ValloxInvalidInputException):
70+
await vallox.set_co2_sensor_control(Profile.FIREPLACE, True)
71+
with pytest.raises(ValloxInvalidInputException):
72+
await vallox.set_co2_sensor_control(Profile.EXTRA, True)
73+
74+
75+
@pytest.mark.parametrize(
76+
"mode, expected",
77+
[
78+
(True, 1),
79+
(False, 0),
80+
(1, 1),
81+
(0, 0),
82+
],
83+
)
84+
@pytest.mark.asyncio
85+
async def test_set_sensor_control_mode(vallox, mode, expected):
86+
"""Test setting sensor control modes and limits."""
87+
vallox.set_values = mock.AsyncMock()
88+
89+
await vallox.set_rh_sensor_manual_control_mode(mode) # Manual mode
90+
vallox.set_values.assert_called_once_with({"A_CYC_RH_LEVEL_MODE": expected})
91+
92+
# Test invalid RH sensor mode
93+
with pytest.raises(ValloxInvalidInputException):
94+
await vallox.set_rh_sensor_manual_control_mode(2)
95+
96+
97+
@pytest.mark.asyncio
98+
async def test_set_sensor_control_limits(vallox):
99+
"""Test setting sensor control modes and limits."""
100+
vallox.set_values = mock.AsyncMock()
101+
102+
# Test setting RH sensor limit
103+
await vallox.set_rh_sensor_limit(65)
104+
vallox.set_values.assert_called_once_with({"A_CYC_RH_BASIC_LEVEL": 65})
105+
vallox.set_values.reset_mock()
106+
107+
# Test setting CO2 sensor limit
108+
await vallox.set_co2_sensor_limit(1000)
109+
vallox.set_values.assert_called_once_with({"A_CYC_CO2_THRESHOLD": 1000})
110+
vallox.set_values.reset_mock()
111+
112+
# Test invalid RH sensor limit
113+
with pytest.raises(ValloxInvalidInputException):
114+
await vallox.set_rh_sensor_limit(101)
115+
116+
# Test invalid CO2 sensor limits
117+
with pytest.raises(ValloxInvalidInputException):
118+
await vallox.set_co2_sensor_limit(499) # Below minimum
119+
with pytest.raises(ValloxInvalidInputException):
120+
await vallox.set_co2_sensor_limit(2001) # Above maximum
121+
122+
123+
@pytest.mark.parametrize(
124+
"adjust_mode",
125+
[
126+
SupplyHeatingAdjustMode.SUPPLY,
127+
SupplyHeatingAdjustMode.EXTRACT,
128+
SupplyHeatingAdjustMode.COOLING,
129+
],
130+
)
131+
@pytest.mark.asyncio
132+
async def test_set_supply_heating_adjust_mode(vallox, adjust_mode):
133+
"""Test setting supply heating adjust mode."""
134+
vallox.set_values = mock.AsyncMock()
135+
136+
await vallox.set_supply_heating_adjust_mode(adjust_mode)
137+
vallox.set_values.assert_called_once_with(
138+
{"A_CYC_SUPPLY_HEATING_ADJUST_MODE": adjust_mode.value}
139+
)
140+
141+
142+
@pytest.mark.asyncio
143+
async def test_set_supply_heating_adjust_mode_with_invalid_mode(vallox):
144+
"""Test setting supply heating adjust mode with invalid mode."""
145+
with pytest.raises(ValloxInvalidInputException):
146+
await vallox.set_supply_heating_adjust_mode(5)
147+
148+
149+
@pytest.mark.parametrize(
150+
"defrost_mode",
151+
[
152+
DefrostMode.BYPASS,
153+
DefrostMode.FAN_STOP,
154+
],
155+
)
156+
@pytest.mark.asyncio
157+
async def test_set_defrost_mode(vallox, defrost_mode):
158+
"""Test setting defrost mode."""
159+
vallox.set_values = mock.AsyncMock()
160+
161+
await vallox.set_defrost_mode(defrost_mode)
162+
vallox.set_values.assert_called_once_with(
163+
{"A_CYC_DEFROST_MODE": defrost_mode.value}
164+
)
165+
166+
167+
@pytest.mark.asyncio
168+
async def test_set_defrost_mode_with_invalid_mode(vallox):
169+
"""Test setting defrost mode with invalid mode."""
170+
with pytest.raises(ValloxInvalidInputException):
171+
await vallox.set_defrost_mode(5)
172+
173+
174+
@pytest.mark.parametrize(
175+
"metrics_response",
176+
[
177+
{
178+
"A_CYC_HOME_RH_CTRL_ENABLED": 1,
179+
"A_CYC_AWAY_RH_CTRL_ENABLED": 1,
180+
"A_CYC_BOOST_RH_CTRL_ENABLED": 1,
181+
"A_CYC_HOME_CO2_CTRL_ENABLED": 1,
182+
"A_CYC_AWAY_CO2_CTRL_ENABLED": 1,
183+
"A_CYC_BOOST_CO2_CTRL_ENABLED": 1,
184+
"A_CYC_RH_LEVEL_MODE": 0,
185+
"A_CYC_RH_BASIC_LEVEL": 58,
186+
"A_CYC_CO2_THRESHOLD": 800,
187+
"A_CYC_SUPPLY_HEATING_ADJUST_MODE": 0,
188+
"A_CYC_DEFROST_MODE": 0,
189+
},
190+
{
191+
"A_CYC_HOME_RH_CTRL_ENABLED": 0,
192+
"A_CYC_AWAY_RH_CTRL_ENABLED": 0,
193+
"A_CYC_BOOST_RH_CTRL_ENABLED": 0,
194+
"A_CYC_HOME_CO2_CTRL_ENABLED": 0,
195+
"A_CYC_AWAY_CO2_CTRL_ENABLED": 0,
196+
"A_CYC_BOOST_CO2_CTRL_ENABLED": 0,
197+
"A_CYC_RH_LEVEL_MODE": 1,
198+
"A_CYC_RH_BASIC_LEVEL": 50,
199+
"A_CYC_CO2_THRESHOLD": 750,
200+
"A_CYC_SUPPLY_HEATING_ADJUST_MODE": 1,
201+
"A_CYC_DEFROST_MODE": 1,
202+
},
203+
{
204+
"A_CYC_HOME_RH_CTRL_ENABLED": 0,
205+
"A_CYC_AWAY_RH_CTRL_ENABLED": 1,
206+
"A_CYC_BOOST_RH_CTRL_ENABLED": 0,
207+
"A_CYC_HOME_CO2_CTRL_ENABLED": 1,
208+
"A_CYC_AWAY_CO2_CTRL_ENABLED": 0,
209+
"A_CYC_BOOST_CO2_CTRL_ENABLED": 1,
210+
"A_CYC_RH_LEVEL_MODE": 0,
211+
"A_CYC_RH_BASIC_LEVEL": 60,
212+
"A_CYC_CO2_THRESHOLD": 850,
213+
"A_CYC_SUPPLY_HEATING_ADJUST_MODE": 2,
214+
"A_CYC_DEFROST_MODE": 1,
215+
},
216+
{
217+
"A_CYC_HOME_RH_CTRL_ENABLED": 1,
218+
"A_CYC_AWAY_RH_CTRL_ENABLED": 0,
219+
"A_CYC_BOOST_RH_CTRL_ENABLED": 1,
220+
"A_CYC_HOME_CO2_CTRL_ENABLED": 0,
221+
"A_CYC_AWAY_CO2_CTRL_ENABLED": 1,
222+
"A_CYC_BOOST_CO2_CTRL_ENABLED": 0,
223+
"A_CYC_RH_LEVEL_MODE": 1,
224+
"A_CYC_RH_BASIC_LEVEL": 55,
225+
"A_CYC_CO2_THRESHOLD": 900,
226+
"A_CYC_SUPPLY_HEATING_ADJUST_MODE": 0,
227+
"A_CYC_DEFROST_MODE": 0,
228+
},
229+
],
230+
)
231+
@pytest.mark.asyncio
232+
async def test_get_sensor_controls_and_modes(vallox, metrics_response):
233+
"""Test getting sensor controls and modes."""
234+
# Mock the metrics response
235+
vallox.fetch_metrics = mock.AsyncMock(return_value=metrics_response)
236+
237+
data = await vallox.fetch_metric_data()
238+
239+
# Test RH sensor control status
240+
assert isinstance(data.get_rh_sensor_control(Profile.HOME), bool)
241+
assert (
242+
data.get_rh_sensor_control(Profile.HOME)
243+
== metrics_response["A_CYC_HOME_RH_CTRL_ENABLED"]
244+
)
245+
assert (
246+
data.get_rh_sensor_control(Profile.AWAY)
247+
== metrics_response["A_CYC_AWAY_RH_CTRL_ENABLED"]
248+
)
249+
assert (
250+
data.get_rh_sensor_control(Profile.BOOST)
251+
== metrics_response["A_CYC_BOOST_RH_CTRL_ENABLED"]
252+
)
253+
254+
# Test CO2 sensor control status
255+
assert isinstance(data.get_co2_sensor_control(Profile.HOME), bool)
256+
assert (
257+
data.get_co2_sensor_control(Profile.HOME)
258+
== metrics_response["A_CYC_HOME_CO2_CTRL_ENABLED"]
259+
)
260+
assert (
261+
data.get_co2_sensor_control(Profile.AWAY)
262+
== metrics_response["A_CYC_AWAY_CO2_CTRL_ENABLED"]
263+
)
264+
assert (
265+
data.get_co2_sensor_control(Profile.BOOST)
266+
== metrics_response["A_CYC_BOOST_CO2_CTRL_ENABLED"]
267+
)
268+
269+
# Test sensor modes and limits
270+
assert isinstance(data.rh_sensor_manual_control_mode, bool)
271+
assert data.rh_sensor_manual_control_mode == bool(
272+
metrics_response["A_CYC_RH_LEVEL_MODE"]
273+
)
274+
assert data.rh_sensor_limit == metrics_response["A_CYC_RH_BASIC_LEVEL"]
275+
assert data.co2_sensor_limit == metrics_response["A_CYC_CO2_THRESHOLD"]
276+
277+
# Test supply heating adjust mode
278+
assert isinstance(data.supply_heating_adjust_mode, SupplyHeatingAdjustMode)
279+
assert data.supply_heating_adjust_mode == SupplyHeatingAdjustMode(
280+
metrics_response["A_CYC_SUPPLY_HEATING_ADJUST_MODE"]
281+
)
282+
assert (
283+
data.supply_heating_adjust_mode.name
284+
== SupplyHeatingAdjustMode(
285+
metrics_response["A_CYC_SUPPLY_HEATING_ADJUST_MODE"]
286+
).name
287+
)
288+
289+
# Test defrost mode
290+
assert isinstance(data.defrost_mode, DefrostMode)
291+
assert data.defrost_mode == DefrostMode(metrics_response["A_CYC_DEFROST_MODE"])
292+
assert (
293+
data.defrost_mode.name
294+
== DefrostMode(metrics_response["A_CYC_DEFROST_MODE"]).name
295+
)
296+
297+
# Test valid profiles without specific RH and CO2 sensor control metrics
298+
with pytest.raises(ValloxInvalidInputException):
299+
data.get_rh_sensor_control(Profile.FIREPLACE)
300+
with pytest.raises(ValloxInvalidInputException):
301+
data.get_rh_sensor_control(Profile.EXTRA)
302+
with pytest.raises(ValloxInvalidInputException):
303+
data.get_co2_sensor_control(Profile.FIREPLACE)
304+
with pytest.raises(ValloxInvalidInputException):
305+
data.get_co2_sensor_control(Profile.EXTRA)
306+
307+
vallox.fetch_metrics.assert_called_once()

vallox_websocket_api/__init__.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@
55
ValloxInvalidInputException,
66
ValloxWebsocketException,
77
)
8-
from .vallox import Alarm, MetricData, Profile, Vallox
8+
from .vallox import (
9+
Alarm,
10+
CellState,
11+
DefrostMode,
12+
MetricData,
13+
Profile,
14+
SupplyHeatingAdjustMode,
15+
Vallox,
16+
)
917

1018
__all__ = [
1119
"Alarm",
@@ -18,6 +26,9 @@
1826
"ValloxInvalidInputException",
1927
"ValloxApiException",
2028
"ValloxWebsocketException",
29+
"CellState",
30+
"SupplyHeatingAdjustMode",
31+
"DefrostMode",
2132
]
2233

23-
__version__ = "5.3.0"
34+
__version__ = "5.3.1"

vallox_websocket_api/client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ class Client:
106106
re.compile(r"^A_CYC_FAULT_ACTIVITY(?:_\d{1,2})?$"),
107107
re.compile(r"^A_CYC_BYPASS_LOCKED$"),
108108
re.compile(r"^A_CYC_.*_SETPOINT$"),
109+
re.compile(r"^A_CYC_(?:HOME|AWAY|BOOST)_RH_CTRL_ENABLED$"),
110+
re.compile(r"^A_CYC_(?:HOME|AWAY|BOOST)_CO2_CTRL_ENABLED$"),
111+
re.compile(r"^A_CYC_CO2_THRESHOLD$"),
112+
re.compile(r"^A_CYC_RH_LEVEL_MODE$"),
113+
re.compile(r"^A_CYC_RH_BASIC_LEVEL$"),
114+
re.compile(r"^A_CYC_PARTIAL_BYPASS$"),
115+
re.compile(r"^A_CYC_SUPPLY_HEATING_ADJUST_MODE$"),
116+
re.compile(r"^A_CYC_POST_HEATER_WINTER_SETPOINT$"),
117+
re.compile(r"^A_CYC_COOLRECOVERY_DISABLED$"),
118+
re.compile(r"^A_CYC_DEFROST_MODE$"),
119+
re.compile(r"^A_CYC_SUPPLY_AIR_DEFROST_TEMP$"),
109120
}
110121

111122
_settable_addresses: Dict[int, type]

0 commit comments

Comments
 (0)