Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion tests/data/test_data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
from vallox_websocket_api.data.model import DataModel

js_folder = Path(__file__).parent / "js"
js_files = [js_file.name for js_file in js_folder.glob("*.js")]
js_files = [
js_file.name
for js_file in js_folder.glob("*.js")
if not js_file.name.startswith("_")
]

json_folder = Path(__file__).parent.parent.parent / "vallox_websocket_api" / "data"
json_files = [json_file.name for json_file in json_folder.glob("*.json")]
Expand Down
307 changes: 307 additions & 0 deletions tests/test_vallox_sensors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
"""Tests for Vallox WebSocket API sensor control functionality."""

from unittest import mock

import pytest

from vallox_websocket_api import (
DefrostMode,
Profile,
SupplyHeatingAdjustMode,
ValloxInvalidInputException,
)


@pytest.mark.parametrize(
"profile, enabled",
[
(Profile.HOME, True),
(Profile.HOME, False),
(Profile.AWAY, True),
(Profile.AWAY, False),
(Profile.BOOST, True),
(Profile.BOOST, False),
],
)
@pytest.mark.asyncio
async def test_set_rh_sensor_control(vallox, profile, enabled):
"""Test setting RH sensor control."""
vallox.set_values = mock.AsyncMock()
await vallox.set_rh_sensor_control(profile, enabled)
vallox.set_values.assert_called_once_with(
{f"A_CYC_{profile.name}_RH_CTRL_ENABLED": enabled}
)


@pytest.mark.asyncio
async def test_set_rh_sensor_control_for_invalid_profile(vallox):
"""Test setting RH sensor control with invalid profile."""
with pytest.raises(ValloxInvalidInputException):
await vallox.set_rh_sensor_control(Profile.FIREPLACE, True)
with pytest.raises(ValloxInvalidInputException):
await vallox.set_rh_sensor_control(Profile.EXTRA, True)


@pytest.mark.parametrize(
"profile, enabled",
[
(Profile.HOME, True),
(Profile.HOME, False),
(Profile.AWAY, True),
(Profile.AWAY, False),
(Profile.BOOST, True),
(Profile.BOOST, False),
],
)
@pytest.mark.asyncio
async def test_set_co2_sensor_control(vallox, profile, enabled):
"""Test setting CO2 sensor control."""
vallox.set_values = mock.AsyncMock()
await vallox.set_co2_sensor_control(profile, enabled)
vallox.set_values.assert_called_once_with(
{f"A_CYC_{profile.name}_CO2_CTRL_ENABLED": enabled}
)


@pytest.mark.asyncio
async def test_set_co2_sensor_control_with_invalid_profile(vallox):
"""Test setting CO2 sensor control with invalid profile."""
with pytest.raises(ValloxInvalidInputException):
await vallox.set_co2_sensor_control(Profile.FIREPLACE, True)
with pytest.raises(ValloxInvalidInputException):
await vallox.set_co2_sensor_control(Profile.EXTRA, True)


@pytest.mark.parametrize(
"mode, expected",
[
(True, 1),
(False, 0),
(1, 1),
(0, 0),
],
)
@pytest.mark.asyncio
async def test_set_sensor_control_mode(vallox, mode, expected):
"""Test setting sensor control modes and limits."""
vallox.set_values = mock.AsyncMock()

await vallox.set_rh_sensor_manual_control_mode(mode) # Manual mode
vallox.set_values.assert_called_once_with({"A_CYC_RH_LEVEL_MODE": expected})

# Test invalid RH sensor mode
with pytest.raises(ValloxInvalidInputException):
await vallox.set_rh_sensor_manual_control_mode(2)


@pytest.mark.asyncio
async def test_set_sensor_control_limits(vallox):
"""Test setting sensor control modes and limits."""
vallox.set_values = mock.AsyncMock()

# Test setting RH sensor limit
await vallox.set_rh_sensor_limit(65)
vallox.set_values.assert_called_once_with({"A_CYC_RH_BASIC_LEVEL": 65})
vallox.set_values.reset_mock()

# Test setting CO2 sensor limit
await vallox.set_co2_sensor_limit(1000)
vallox.set_values.assert_called_once_with({"A_CYC_CO2_THRESHOLD": 1000})
vallox.set_values.reset_mock()

# Test invalid RH sensor limit
with pytest.raises(ValloxInvalidInputException):
await vallox.set_rh_sensor_limit(101)

# Test invalid CO2 sensor limits
with pytest.raises(ValloxInvalidInputException):
await vallox.set_co2_sensor_limit(499) # Below minimum
with pytest.raises(ValloxInvalidInputException):
await vallox.set_co2_sensor_limit(2001) # Above maximum


@pytest.mark.parametrize(
"adjust_mode",
[
SupplyHeatingAdjustMode.SUPPLY,
SupplyHeatingAdjustMode.EXTRACT,
SupplyHeatingAdjustMode.COOLING,
],
)
@pytest.mark.asyncio
async def test_set_supply_heating_adjust_mode(vallox, adjust_mode):
"""Test setting supply heating adjust mode."""
vallox.set_values = mock.AsyncMock()

await vallox.set_supply_heating_adjust_mode(adjust_mode)
vallox.set_values.assert_called_once_with(
{"A_CYC_SUPPLY_HEATING_ADJUST_MODE": adjust_mode.value}
)


@pytest.mark.asyncio
async def test_set_supply_heating_adjust_mode_with_invalid_mode(vallox):
"""Test setting supply heating adjust mode with invalid mode."""
with pytest.raises(ValloxInvalidInputException):
await vallox.set_supply_heating_adjust_mode(5)


@pytest.mark.parametrize(
"defrost_mode",
[
DefrostMode.BYPASS,
DefrostMode.FAN_STOP,
],
)
@pytest.mark.asyncio
async def test_set_defrost_mode(vallox, defrost_mode):
"""Test setting defrost mode."""
vallox.set_values = mock.AsyncMock()

await vallox.set_defrost_mode(defrost_mode)
vallox.set_values.assert_called_once_with(
{"A_CYC_DEFROST_MODE": defrost_mode.value}
)


@pytest.mark.asyncio
async def test_set_defrost_mode_with_invalid_mode(vallox):
"""Test setting defrost mode with invalid mode."""
with pytest.raises(ValloxInvalidInputException):
await vallox.set_defrost_mode(5)


@pytest.mark.parametrize(
"metrics_response",
[
{
"A_CYC_HOME_RH_CTRL_ENABLED": 1,
"A_CYC_AWAY_RH_CTRL_ENABLED": 1,
"A_CYC_BOOST_RH_CTRL_ENABLED": 1,
"A_CYC_HOME_CO2_CTRL_ENABLED": 1,
"A_CYC_AWAY_CO2_CTRL_ENABLED": 1,
"A_CYC_BOOST_CO2_CTRL_ENABLED": 1,
"A_CYC_RH_LEVEL_MODE": 0,
"A_CYC_RH_BASIC_LEVEL": 58,
"A_CYC_CO2_THRESHOLD": 800,
"A_CYC_SUPPLY_HEATING_ADJUST_MODE": 0,
"A_CYC_DEFROST_MODE": 0,
},
{
"A_CYC_HOME_RH_CTRL_ENABLED": 0,
"A_CYC_AWAY_RH_CTRL_ENABLED": 0,
"A_CYC_BOOST_RH_CTRL_ENABLED": 0,
"A_CYC_HOME_CO2_CTRL_ENABLED": 0,
"A_CYC_AWAY_CO2_CTRL_ENABLED": 0,
"A_CYC_BOOST_CO2_CTRL_ENABLED": 0,
"A_CYC_RH_LEVEL_MODE": 1,
"A_CYC_RH_BASIC_LEVEL": 50,
"A_CYC_CO2_THRESHOLD": 750,
"A_CYC_SUPPLY_HEATING_ADJUST_MODE": 1,
"A_CYC_DEFROST_MODE": 1,
},
{
"A_CYC_HOME_RH_CTRL_ENABLED": 0,
"A_CYC_AWAY_RH_CTRL_ENABLED": 1,
"A_CYC_BOOST_RH_CTRL_ENABLED": 0,
"A_CYC_HOME_CO2_CTRL_ENABLED": 1,
"A_CYC_AWAY_CO2_CTRL_ENABLED": 0,
"A_CYC_BOOST_CO2_CTRL_ENABLED": 1,
"A_CYC_RH_LEVEL_MODE": 0,
"A_CYC_RH_BASIC_LEVEL": 60,
"A_CYC_CO2_THRESHOLD": 850,
"A_CYC_SUPPLY_HEATING_ADJUST_MODE": 2,
"A_CYC_DEFROST_MODE": 1,
},
{
"A_CYC_HOME_RH_CTRL_ENABLED": 1,
"A_CYC_AWAY_RH_CTRL_ENABLED": 0,
"A_CYC_BOOST_RH_CTRL_ENABLED": 1,
"A_CYC_HOME_CO2_CTRL_ENABLED": 0,
"A_CYC_AWAY_CO2_CTRL_ENABLED": 1,
"A_CYC_BOOST_CO2_CTRL_ENABLED": 0,
"A_CYC_RH_LEVEL_MODE": 1,
"A_CYC_RH_BASIC_LEVEL": 55,
"A_CYC_CO2_THRESHOLD": 900,
"A_CYC_SUPPLY_HEATING_ADJUST_MODE": 0,
"A_CYC_DEFROST_MODE": 0,
},
],
)
@pytest.mark.asyncio
async def test_get_sensor_controls_and_modes(vallox, metrics_response):
"""Test getting sensor controls and modes."""
# Mock the metrics response
vallox.fetch_metrics = mock.AsyncMock(return_value=metrics_response)

data = await vallox.fetch_metric_data()

# Test RH sensor control status
assert isinstance(data.get_rh_sensor_control(Profile.HOME), bool)
assert (
data.get_rh_sensor_control(Profile.HOME)
== metrics_response["A_CYC_HOME_RH_CTRL_ENABLED"]
)
assert (
data.get_rh_sensor_control(Profile.AWAY)
== metrics_response["A_CYC_AWAY_RH_CTRL_ENABLED"]
)
assert (
data.get_rh_sensor_control(Profile.BOOST)
== metrics_response["A_CYC_BOOST_RH_CTRL_ENABLED"]
)

# Test CO2 sensor control status
assert isinstance(data.get_co2_sensor_control(Profile.HOME), bool)
assert (
data.get_co2_sensor_control(Profile.HOME)
== metrics_response["A_CYC_HOME_CO2_CTRL_ENABLED"]
)
assert (
data.get_co2_sensor_control(Profile.AWAY)
== metrics_response["A_CYC_AWAY_CO2_CTRL_ENABLED"]
)
assert (
data.get_co2_sensor_control(Profile.BOOST)
== metrics_response["A_CYC_BOOST_CO2_CTRL_ENABLED"]
)

# Test sensor modes and limits
assert isinstance(data.rh_sensor_manual_control_mode, bool)
assert data.rh_sensor_manual_control_mode == bool(
metrics_response["A_CYC_RH_LEVEL_MODE"]
)
assert data.rh_sensor_limit == metrics_response["A_CYC_RH_BASIC_LEVEL"]
assert data.co2_sensor_limit == metrics_response["A_CYC_CO2_THRESHOLD"]

# Test supply heating adjust mode
assert isinstance(data.supply_heating_adjust_mode, SupplyHeatingAdjustMode)
assert data.supply_heating_adjust_mode == SupplyHeatingAdjustMode(
metrics_response["A_CYC_SUPPLY_HEATING_ADJUST_MODE"]
)
assert (
data.supply_heating_adjust_mode.name
== SupplyHeatingAdjustMode(
metrics_response["A_CYC_SUPPLY_HEATING_ADJUST_MODE"]
).name
)

# Test defrost mode
assert isinstance(data.defrost_mode, DefrostMode)
assert data.defrost_mode == DefrostMode(metrics_response["A_CYC_DEFROST_MODE"])
assert (
data.defrost_mode.name
== DefrostMode(metrics_response["A_CYC_DEFROST_MODE"]).name
)

# Test valid profiles without specific RH and CO2 sensor control metrics
with pytest.raises(ValloxInvalidInputException):
data.get_rh_sensor_control(Profile.FIREPLACE)
with pytest.raises(ValloxInvalidInputException):
data.get_rh_sensor_control(Profile.EXTRA)
with pytest.raises(ValloxInvalidInputException):
data.get_co2_sensor_control(Profile.FIREPLACE)
with pytest.raises(ValloxInvalidInputException):
data.get_co2_sensor_control(Profile.EXTRA)

vallox.fetch_metrics.assert_called_once()
15 changes: 13 additions & 2 deletions vallox_websocket_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
ValloxInvalidInputException,
ValloxWebsocketException,
)
from .vallox import Alarm, MetricData, Profile, Vallox
from .vallox import (
Alarm,
CellState,
DefrostMode,
MetricData,
Profile,
SupplyHeatingAdjustMode,
Vallox,
)

__all__ = [
"Alarm",
Expand All @@ -18,6 +26,9 @@
"ValloxInvalidInputException",
"ValloxApiException",
"ValloxWebsocketException",
"CellState",
"SupplyHeatingAdjustMode",
"DefrostMode",
]

__version__ = "5.3.0"
__version__ = "5.3.1"
11 changes: 11 additions & 0 deletions vallox_websocket_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ class Client:
re.compile(r"^A_CYC_FAULT_ACTIVITY(?:_\d{1,2})?$"),
re.compile(r"^A_CYC_BYPASS_LOCKED$"),
re.compile(r"^A_CYC_.*_SETPOINT$"),
re.compile(r"^A_CYC_(?:HOME|AWAY|BOOST)_RH_CTRL_ENABLED$"),
re.compile(r"^A_CYC_(?:HOME|AWAY|BOOST)_CO2_CTRL_ENABLED$"),
re.compile(r"^A_CYC_CO2_THRESHOLD$"),
re.compile(r"^A_CYC_RH_LEVEL_MODE$"),
re.compile(r"^A_CYC_RH_BASIC_LEVEL$"),
re.compile(r"^A_CYC_PARTIAL_BYPASS$"),
re.compile(r"^A_CYC_SUPPLY_HEATING_ADJUST_MODE$"),
re.compile(r"^A_CYC_POST_HEATER_WINTER_SETPOINT$"),
re.compile(r"^A_CYC_COOLRECOVERY_DISABLED$"),
re.compile(r"^A_CYC_DEFROST_MODE$"),
re.compile(r"^A_CYC_SUPPLY_AIR_DEFROST_TEMP$"),
}

_settable_addresses: Dict[int, type]
Expand Down
Loading