From 4e86bc47a42ef27b534f4ec45dcd426960cec928 Mon Sep 17 00:00:00 2001 From: fschwarz86 Date: Fri, 10 Jul 2026 12:29:32 +0200 Subject: [PATCH 1/3] Add support for automatic profile (firmware v3.1.4) --- tests/test_vallox_fan_speed.py | 4 ++++ tests/test_vallox_profile.py | 20 ++++++++++++++++++++ tests/test_vallox_sensors.py | 4 ++++ tests/test_vallox_temperatures.py | 4 ++++ vallox_websocket_api/vallox.py | 13 +++++++++++++ 5 files changed, 45 insertions(+) diff --git a/tests/test_vallox_fan_speed.py b/tests/test_vallox_fan_speed.py index 0f3e6d5..e4108dd 100644 --- a/tests/test_vallox_fan_speed.py +++ b/tests/test_vallox_fan_speed.py @@ -34,6 +34,10 @@ async def test_set_fan_speed_boost(vallox: Vallox): async def test_set_fan_speed_wrong(vallox: Vallox): with pytest.raises(ValloxInvalidInputException): await vallox.set_fan_speed(Profile.FIREPLACE, 19) + with pytest.raises(ValloxInvalidInputException): + await vallox.set_fan_speed(Profile.EXTRA, 19) + with pytest.raises(ValloxInvalidInputException): + await vallox.set_fan_speed(Profile.AUTO, 19) async def test_set_fan_speed_home_invalid_percentage(vallox: Vallox): diff --git a/tests/test_vallox_profile.py b/tests/test_vallox_profile.py index a7f002f..33ab1fb 100644 --- a/tests/test_vallox_profile.py +++ b/tests/test_vallox_profile.py @@ -55,6 +55,16 @@ "A_CYC_EXTRA_TIMER": 30, }, ), + ( + Profile.AUTO, + None, + { + "A_CYC_STATE": 2, + "A_CYC_BOOST_TIMER": 0, + "A_CYC_FIREPLACE_TIMER": 0, + "A_CYC_EXTRA_TIMER": 0, + }, + ), ], ) async def test_set_profile(vallox: Vallox, profile, fetch_metrics, expected_values): @@ -155,6 +165,16 @@ async def test_set_profile(vallox: Vallox, profile, fetch_metrics, expected_valu Profile.EXTRA, 31, ), + ( + { + "A_CYC_STATE": 2, + "A_CYC_BOOST_TIMER": 0, + "A_CYC_FIREPLACE_TIMER": 0, + "A_CYC_EXTRA_TIMER": 0, + }, + Profile.AUTO, + None, + ), ], ) async def test_profiles( diff --git a/tests/test_vallox_sensors.py b/tests/test_vallox_sensors.py index 517bc0e..15d3851 100644 --- a/tests/test_vallox_sensors.py +++ b/tests/test_vallox_sensors.py @@ -41,6 +41,8 @@ async def test_set_rh_sensor_control_for_invalid_profile(vallox): await vallox.set_rh_sensor_control(Profile.FIREPLACE, True) with pytest.raises(ValloxInvalidInputException): await vallox.set_rh_sensor_control(Profile.EXTRA, True) + with pytest.raises(ValloxInvalidInputException): + await vallox.set_rh_sensor_control(Profile.AUTO, True) @pytest.mark.parametrize( @@ -71,6 +73,8 @@ async def test_set_co2_sensor_control_with_invalid_profile(vallox): await vallox.set_co2_sensor_control(Profile.FIREPLACE, True) with pytest.raises(ValloxInvalidInputException): await vallox.set_co2_sensor_control(Profile.EXTRA, True) + with pytest.raises(ValloxInvalidInputException): + await vallox.set_co2_sensor_control(Profile.AUTO, True) @pytest.mark.parametrize( diff --git a/tests/test_vallox_temperatures.py b/tests/test_vallox_temperatures.py index 7a4da4d..669150a 100644 --- a/tests/test_vallox_temperatures.py +++ b/tests/test_vallox_temperatures.py @@ -28,6 +28,10 @@ async def test_vallox_set_temperature(vallox, profile, metric, value): async def test_vallox_set_temperature_not_supported(vallox): with pytest.raises(ValloxInvalidInputException): await vallox.set_temperature(Profile.FIREPLACE, 19) + with pytest.raises(ValloxInvalidInputException): + await vallox.set_temperature(Profile.EXTRA, 19) + with pytest.raises(ValloxInvalidInputException): + await vallox.set_temperature(Profile.AUTO, 19) async def test_vallox_get_temperature(vallox): diff --git a/vallox_websocket_api/vallox.py b/vallox_websocket_api/vallox.py index 5073a24..6c4d299 100644 --- a/vallox_websocket_api/vallox.py +++ b/vallox_websocket_api/vallox.py @@ -21,6 +21,7 @@ class Profile(IntEnum): BOOST = 3 FIREPLACE = 4 EXTRA = 5 + AUTO = 6 PROFILE_TO_SET_TEMPERATURE_METRIC_MAP = { @@ -247,6 +248,8 @@ def profile(self) -> Profile: return Profile.AWAY elif self.get("A_CYC_STATE") == 0: return Profile.HOME + elif self.get("A_CYC_STATE") == 2: + return Profile.AUTO return Profile.NONE @property @@ -511,6 +514,16 @@ async def lazy_fetch_metric_value(metric: str) -> int: "A_CYC_EXTRA_TIMER": 0, } ) + elif profile == Profile.AUTO: + logger.info("Setting unit to AUTO profile") + await self.set_values( + { + "A_CYC_STATE": 2, + "A_CYC_BOOST_TIMER": 0, + "A_CYC_FIREPLACE_TIMER": 0, + "A_CYC_EXTRA_TIMER": 0, + } + ) elif profile == Profile.FIREPLACE: dur = set_duration or await lazy_fetch_metric_value("A_CYC_FIREPLACE_TIME") logger.info(f"Setting unit to FIREPLACE profile for {dur} minutes") From 1d45ddfd0f7a5741709e414f4c4fe292845a41b3 Mon Sep 17 00:00:00 2001 From: yozik04 Date: Fri, 10 Jul 2026 18:05:50 +0300 Subject: [PATCH 2/3] Prettify --- vallox_websocket_api/vallox.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vallox_websocket_api/vallox.py b/vallox_websocket_api/vallox.py index 6c4d299..ddb4cf3 100644 --- a/vallox_websocket_api/vallox.py +++ b/vallox_websocket_api/vallox.py @@ -240,14 +240,14 @@ def profile(self) -> Profile: """Get the current profile of the unit""" if self.get("A_CYC_BOOST_TIMER", 0) > 0: return Profile.BOOST - if self.get("A_CYC_FIREPLACE_TIMER", 0) > 0: + elif self.get("A_CYC_FIREPLACE_TIMER", 0) > 0: return Profile.FIREPLACE - if self.get("A_CYC_EXTRA_TIMER", 0) > 0: + elif self.get("A_CYC_EXTRA_TIMER", 0) > 0: return Profile.EXTRA - if self.get("A_CYC_STATE") == 1: - return Profile.AWAY elif self.get("A_CYC_STATE") == 0: return Profile.HOME + elif self.get("A_CYC_STATE") == 1: + return Profile.AWAY elif self.get("A_CYC_STATE") == 2: return Profile.AUTO return Profile.NONE From 0353d14e9cf909bdb67558fa1cf4d3c6eec9c56a Mon Sep 17 00:00:00 2001 From: yozik04 Date: Fri, 10 Jul 2026 18:57:43 +0300 Subject: [PATCH 3/3] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4c479b4..b259e80 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ async def run(): profile = data.profile # RETURNS a Profile.* value await client.set_profile(Profile.HOME) # Permanently HOME profile await client.set_profile(Profile.AWAY) # Permanently AWAY profile + await client.set_profile(Profile.AUTO) # Permanently AUTO profile (requires firmware v3.1.4+) await client.set_profile(Profile.FIREPLACE) # FIREPLACE mode for configured timeout await client.set_profile(Profile.FIREPLACE, 120) # FIREPLACE mode for 120 min await client.set_profile(Profile.FIREPLACE, 65535) # FIREPLACE mode, never TIMEOUT