Update vallox_websocket_api to 5.0.2 (#110752)

Co-authored-by: Sebastian Lövdahl <slovdahl@hibox.fi>
This commit is contained in:
Jevgeni Kiski 2024-02-19 15:36:51 +02:00 committed by GitHub
parent efac3b0a60
commit 2250baab21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 349 additions and 445 deletions

View File

@ -1456,8 +1456,8 @@ build.json @home-assistant/supervisor
/tests/components/v2c/ @dgomes
/homeassistant/components/vacuum/ @home-assistant/core
/tests/components/vacuum/ @home-assistant/core
/homeassistant/components/vallox/ @andre-richter @slovdahl @viiru-
/tests/components/vallox/ @andre-richter @slovdahl @viiru-
/homeassistant/components/vallox/ @andre-richter @slovdahl @viiru- @yozik04
/tests/components/vallox/ @andre-richter @slovdahl @viiru- @yozik04
/homeassistant/components/valve/ @home-assistant/core
/tests/components/valve/ @home-assistant/core
/homeassistant/components/velbus/ @Cereal2nd @brefra

View File

@ -1,20 +1,11 @@
"""Support for Vallox ventilation units."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import date
import ipaddress
import logging
from typing import Any, NamedTuple
from uuid import UUID
from typing import NamedTuple
from vallox_websocket_api import PROFILE as VALLOX_PROFILE, Vallox, ValloxApiException
from vallox_websocket_api.vallox import (
get_model as _api_get_model,
get_next_filter_change_date as _api_get_next_filter_change_date,
get_sw_version as _api_get_sw_version,
get_uuid as _api_get_uuid,
)
from vallox_websocket_api import MetricData, Profile, Vallox, ValloxApiException
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
@ -22,7 +13,6 @@ from homeassistant.const import CONF_HOST, CONF_NAME, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
@ -35,9 +25,6 @@ from .const import (
DEFAULT_FAN_SPEED_HOME,
DEFAULT_NAME,
DOMAIN,
METRIC_KEY_PROFILE_FAN_SPEED_AWAY,
METRIC_KEY_PROFILE_FAN_SPEED_BOOST,
METRIC_KEY_PROFILE_FAN_SPEED_HOME,
STATE_SCAN_INTERVAL,
)
@ -104,58 +91,7 @@ SERVICE_TO_METHOD = {
}
@dataclass
class ValloxState:
"""Describes the current state of the unit."""
metric_cache: dict[str, Any] = field(default_factory=dict)
profile: VALLOX_PROFILE = VALLOX_PROFILE.NONE
def get_metric(self, metric_key: str) -> StateType:
"""Return cached state value."""
if (value := self.metric_cache.get(metric_key)) is None:
return None
if not isinstance(value, (str, int, float)):
return None
return value
@property
def model(self) -> str | None:
"""Return the model, if any."""
model = _api_get_model(self.metric_cache)
if model == "Unknown":
return None
return model
@property
def sw_version(self) -> str:
"""Return the SW version."""
return _api_get_sw_version(self.metric_cache)
@property
def uuid(self) -> UUID | None:
"""Return cached UUID value."""
uuid = _api_get_uuid(self.metric_cache)
if not isinstance(uuid, UUID):
raise TypeError
return uuid
def get_next_filter_change_date(self) -> date | None:
"""Return the next filter change date."""
next_filter_change_date = _api_get_next_filter_change_date(self.metric_cache)
if not isinstance(next_filter_change_date, date):
return None
return next_filter_change_date
class ValloxDataUpdateCoordinator(DataUpdateCoordinator[ValloxState]): # pylint: disable=hass-enforce-coordinator-module
class ValloxDataUpdateCoordinator(DataUpdateCoordinator[MetricData]): # pylint: disable=hass-enforce-coordinator-module
"""The DataUpdateCoordinator for Vallox."""
@ -166,19 +102,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
client = Vallox(host)
async def async_update_data() -> ValloxState:
async def async_update_data() -> MetricData:
"""Fetch state update."""
_LOGGER.debug("Updating Vallox state cache")
try:
metric_cache = await client.fetch_metrics()
profile = await client.get_profile()
return await client.fetch_metric_data()
except ValloxApiException as err:
raise UpdateFailed("Error during state cache update") from err
return ValloxState(metric_cache, profile)
coordinator = ValloxDataUpdateCoordinator(
hass,
_LOGGER,
@ -227,7 +159,7 @@ class ValloxServiceHandler:
"""Services implementation."""
def __init__(
self, client: Vallox, coordinator: DataUpdateCoordinator[ValloxState]
self, client: Vallox, coordinator: DataUpdateCoordinator[MetricData]
) -> None:
"""Initialize the proxy."""
self._client = client
@ -240,9 +172,7 @@ class ValloxServiceHandler:
_LOGGER.debug("Setting Home fan speed to: %d%%", fan_speed)
try:
await self._client.set_values(
{METRIC_KEY_PROFILE_FAN_SPEED_HOME: fan_speed}
)
await self._client.set_fan_speed(Profile.HOME, fan_speed)
return True
except ValloxApiException as err:
@ -256,9 +186,7 @@ class ValloxServiceHandler:
_LOGGER.debug("Setting Away fan speed to: %d%%", fan_speed)
try:
await self._client.set_values(
{METRIC_KEY_PROFILE_FAN_SPEED_AWAY: fan_speed}
)
await self._client.set_fan_speed(Profile.AWAY, fan_speed)
return True
except ValloxApiException as err:
@ -272,9 +200,7 @@ class ValloxServiceHandler:
_LOGGER.debug("Setting Boost fan speed to: %d%%", fan_speed)
try:
await self._client.set_values(
{METRIC_KEY_PROFILE_FAN_SPEED_BOOST: fan_speed}
)
await self._client.set_fan_speed(Profile.BOOST, fan_speed)
return True
except ValloxApiException as err:

View File

@ -38,7 +38,7 @@ class ValloxBinarySensorEntity(ValloxEntity, BinarySensorEntity):
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self.coordinator.data.get_metric(self.entity_description.metric_key) == 1
return self.coordinator.data.get(self.entity_description.metric_key) == 1
@dataclass(frozen=True)

View File

@ -32,7 +32,7 @@ async def validate_host(hass: HomeAssistant, host: str) -> None:
raise InvalidHost(f"Invalid IP address: {host}")
client = Vallox(host)
await client.get_info()
await client.fetch_metric_data()
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):

View File

@ -2,7 +2,7 @@
from datetime import timedelta
from vallox_websocket_api import PROFILE as VALLOX_PROFILE
from vallox_websocket_api import Profile as VALLOX_PROFILE
DOMAIN = "vallox"
DEFAULT_NAME = "Vallox"
@ -30,8 +30,11 @@ VALLOX_PROFILE_TO_PRESET_MODE_SETTABLE = {
}
VALLOX_PROFILE_TO_PRESET_MODE_REPORTABLE = {
VALLOX_PROFILE.HOME: "Home",
VALLOX_PROFILE.AWAY: "Away",
VALLOX_PROFILE.BOOST: "Boost",
VALLOX_PROFILE.FIREPLACE: "Fireplace",
VALLOX_PROFILE.EXTRA: "Extra",
**VALLOX_PROFILE_TO_PRESET_MODE_SETTABLE,
}
PRESET_MODE_TO_VALLOX_PROFILE_SETTABLE = {

View File

@ -4,12 +4,7 @@ from __future__ import annotations
from collections.abc import Mapping
from typing import Any, NamedTuple
from vallox_websocket_api import (
PROFILE_TO_SET_FAN_SPEED_METRIC_MAP,
Vallox,
ValloxApiException,
ValloxInvalidInputException,
)
from vallox_websocket_api import Vallox, ValloxApiException, ValloxInvalidInputException
from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.config_entries import ConfigEntry
@ -99,7 +94,7 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
@property
def is_on(self) -> bool:
"""Return if device is on."""
return self.coordinator.data.get_metric(METRIC_KEY_MODE) == MODE_ON
return self.coordinator.data.get(METRIC_KEY_MODE) == MODE_ON
@property
def preset_mode(self) -> str | None:
@ -112,19 +107,18 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
"""Return the current speed as a percentage."""
vallox_profile = self.coordinator.data.profile
metric_key = PROFILE_TO_SET_FAN_SPEED_METRIC_MAP.get(vallox_profile)
if not metric_key:
try:
return _convert_to_int(self.coordinator.data.get_fan_speed(vallox_profile))
except ValloxInvalidInputException:
return None
return _convert_to_int(self.coordinator.data.get_metric(metric_key))
@property
def extra_state_attributes(self) -> Mapping[str, int | None]:
"""Return device specific state attributes."""
data = self.coordinator.data
return {
attr.description: _convert_to_int(data.get_metric(attr.metric_key))
attr.description: _convert_to_int(data.get(attr.metric_key))
for attr in EXTRA_STATE_ATTRIBUTES
}
@ -153,7 +147,9 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
update_needed |= await self._async_set_preset_mode_internal(preset_mode)
if percentage is not None:
update_needed |= await self._async_set_percentage_internal(percentage)
update_needed |= await self._async_set_percentage_internal(
percentage, preset_mode
)
if update_needed:
# This state change affects other entities like sensors. Force an immediate update that
@ -202,19 +198,24 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
try:
profile = PRESET_MODE_TO_VALLOX_PROFILE_SETTABLE[preset_mode]
await self._client.set_profile(profile)
self.coordinator.data.profile = profile
except ValloxApiException as err:
raise HomeAssistantError(f"Failed to set profile: {preset_mode}") from err
return True
async def _async_set_percentage_internal(self, percentage: int) -> bool:
async def _async_set_percentage_internal(
self, percentage: int, preset_mode: str | None = None
) -> bool:
"""Set fan speed percentage for current profile.
Returns true if speed has been changed, false otherwise.
"""
vallox_profile = self.coordinator.data.profile
vallox_profile = (
PRESET_MODE_TO_VALLOX_PROFILE_SETTABLE[preset_mode]
if preset_mode is not None
else self.coordinator.data.profile
)
try:
await self._client.set_fan_speed(vallox_profile, percentage)

View File

@ -1,10 +1,10 @@
{
"domain": "vallox",
"name": "Vallox",
"codeowners": ["@andre-richter", "@slovdahl", "@viiru-"],
"codeowners": ["@andre-richter", "@slovdahl", "@viiru-", "@yozik04"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/vallox",
"iot_class": "local_polling",
"loggers": ["vallox_websocket_api"],
"requirements": ["vallox-websocket-api==4.0.3"]
"requirements": ["vallox-websocket-api==5.0.2"]
}

View File

@ -44,9 +44,7 @@ class ValloxNumberEntity(ValloxEntity, NumberEntity):
def native_value(self) -> float | None:
"""Return the value reported by the sensor."""
if (
value := self.coordinator.data.get_metric(
self.entity_description.metric_key
)
value := self.coordinator.data.get(self.entity_description.metric_key)
) is None:
return None

View File

@ -58,7 +58,7 @@ class ValloxSensorEntity(ValloxEntity, SensorEntity):
if (metric_key := self.entity_description.metric_key) is None:
return None
value = self.coordinator.data.get_metric(metric_key)
value = self.coordinator.data.get(metric_key)
if self.entity_description.round_ndigits is not None and isinstance(
value, float
@ -90,7 +90,7 @@ class ValloxFanSpeedSensor(ValloxSensorEntity):
@property
def native_value(self) -> StateType | datetime:
"""Return the value reported by the sensor."""
fan_is_on = self.coordinator.data.get_metric(METRIC_KEY_MODE) == MODE_ON
fan_is_on = self.coordinator.data.get(METRIC_KEY_MODE) == MODE_ON
return super().native_value if fan_is_on else 0
@ -100,7 +100,7 @@ class ValloxFilterRemainingSensor(ValloxSensorEntity):
@property
def native_value(self) -> StateType | datetime:
"""Return the value reported by the sensor."""
next_filter_change_date = self.coordinator.data.get_next_filter_change_date()
next_filter_change_date = self.coordinator.data.next_filter_change_date
if next_filter_change_date is None:
return None

View File

@ -41,9 +41,7 @@ class ValloxSwitchEntity(ValloxEntity, SwitchEntity):
def is_on(self) -> bool | None:
"""Return true if the switch is on."""
if (
value := self.coordinator.data.get_metric(
self.entity_description.metric_key
)
value := self.coordinator.data.get(self.entity_description.metric_key)
) is None:
return None
return value == 1
@ -93,12 +91,12 @@ async def async_setup_entry(
"""Set up the switches."""
data = hass.data[DOMAIN][entry.entry_id]
client = data["client"]
client.set_settable_address("A_CYC_BYPASS_LOCKED", int)
async_add_entities(
[
ValloxSwitchEntity(data["name"], data["coordinator"], description, client)
ValloxSwitchEntity(
data["name"], data["coordinator"], description, data["client"]
)
for description in SWITCH_ENTITIES
]
)

View File

@ -2786,7 +2786,7 @@ uvcclient==0.11.0
vacuum-map-parser-roborock==0.1.1
# homeassistant.components.vallox
vallox-websocket-api==4.0.3
vallox-websocket-api==5.0.2
# homeassistant.components.rdw
vehicle==2.2.1

View File

@ -2130,7 +2130,7 @@ uvcclient==0.11.0
vacuum-map-parser-roborock==0.1.1
# homeassistant.components.vallox
vallox-websocket-api==4.0.3
vallox-websocket-api==5.0.2
# homeassistant.components.rdw
vehicle==2.2.1

View File

@ -1,13 +1,9 @@
"""Common utilities for Vallox tests."""
import random
import string
from typing import Any
from unittest.mock import patch
from uuid import UUID
from unittest.mock import AsyncMock, patch
import pytest
from vallox_websocket_api.vallox import PROFILE
from vallox_websocket_api import MetricData
from homeassistant.components.vallox.const import DOMAIN
from homeassistant.const import CONF_HOST, CONF_NAME
@ -31,83 +27,80 @@ def mock_entry(hass: HomeAssistant) -> MockConfigEntry:
return vallox_mock_entry
def patch_metrics(metrics: dict[str, Any]):
@pytest.fixture
def default_metrics():
"""Return default Vallox metrics."""
return {
"A_CYC_MACHINE_MODEL": 3,
"A_CYC_APPL_SW_VERSION_1": 2,
"A_CYC_APPL_SW_VERSION_2": 0,
"A_CYC_APPL_SW_VERSION_3": 16,
"A_CYC_UUID0": 5,
"A_CYC_UUID1": 6,
"A_CYC_UUID2": 7,
"A_CYC_UUID3": 8,
"A_CYC_UUID4": 9,
"A_CYC_UUID5": 10,
"A_CYC_UUID6": 11,
"A_CYC_UUID7": 12,
"A_CYC_BOOST_TIMER": 30,
"A_CYC_FIREPLACE_TIMER": 30,
"A_CYC_EXTRA_TIMER": 30,
"A_CYC_MODE": 0,
"A_CYC_STATE": 0,
"A_CYC_FILTER_CHANGED_YEAR": 24,
"A_CYC_FILTER_CHANGED_MONTH": 2,
"A_CYC_FILTER_CHANGED_DAY": 16,
"A_CYC_FILTER_CHANGE_INTERVAL": 120,
"A_CYC_TOTAL_FAULT_COUNT": 0,
"A_CYC_FAULT_CODE": 0,
"A_CYC_FAULT_ACTIVITY": 0,
"A_CYC_FAULT_FIRST_DATE": 0,
"A_CYC_FAULT_LAST_DATE": 0,
"A_CYC_FAULT_SEVERITY": 0,
"A_CYC_FAULT_COUNT": 0,
"A_CYC_HOME_SPEED_SETTING": 30,
"A_CYC_AWAY_SPEED_SETTING": 10,
"A_CYC_BOOST_SPEED_SETTING": 80,
}
@pytest.fixture(autouse=True)
def fetch_metric_data_mock(default_metrics):
"""Stub the Vallox fetch_metric_data method."""
with patch(
"homeassistant.components.vallox.Vallox.fetch_metric_data",
new_callable=AsyncMock,
) as mock:
mock.return_value = MetricData(default_metrics)
yield mock
@pytest.fixture
def setup_fetch_metric_data_mock(fetch_metric_data_mock, default_metrics):
"""Patch the Vallox metrics response."""
return patch(
"homeassistant.components.vallox.Vallox.fetch_metrics",
return_value=metrics,
)
def _setup(metrics=None, metric_data_class=MetricData):
metrics = metrics or {}
fetch_metric_data_mock.return_value = metric_data_class(
{**default_metrics, **metrics}
)
return fetch_metric_data_mock
return _setup
def patch_profile(profile: PROFILE):
"""Patch the Vallox metrics response."""
return patch(
"homeassistant.components.vallox.Vallox.get_profile",
return_value=profile,
)
def patch_profile_set():
def patch_set_profile():
"""Patch the Vallox metrics set values."""
return patch("homeassistant.components.vallox.Vallox.set_profile")
def patch_metrics_set():
def patch_set_fan_speed():
"""Patch the Vallox metrics set values."""
return patch("homeassistant.components.vallox.Vallox.set_fan_speed")
def patch_set_values():
"""Patch the Vallox metrics set values."""
return patch("homeassistant.components.vallox.Vallox.set_values")
@pytest.fixture(autouse=True)
def patch_empty_metrics():
"""Patch the Vallox profile response."""
with patch(
"homeassistant.components.vallox.Vallox.fetch_metrics",
return_value={},
):
yield
@pytest.fixture(autouse=True)
def patch_default_profile():
"""Patch the Vallox profile response."""
with patch(
"homeassistant.components.vallox.Vallox.get_profile",
return_value=PROFILE.HOME,
):
yield
@pytest.fixture(autouse=True)
def patch_model():
"""Patch the Vallox model response."""
with patch(
"homeassistant.components.vallox._api_get_model",
return_value="Vallox Testmodel",
):
yield
@pytest.fixture(autouse=True)
def patch_sw_version():
"""Patch the Vallox SW version response."""
with patch(
"homeassistant.components.vallox._api_get_sw_version",
return_value="0.1.2",
):
yield
@pytest.fixture(autouse=True)
def patch_uuid():
"""Patch the Vallox UUID response."""
with patch(
"homeassistant.components.vallox._api_get_uuid",
return_value=_random_uuid(),
):
yield
def _random_uuid():
"""Generate a random UUID."""
uuid = "".join(random.choices(string.hexdigits, k=32))
return UUID(uuid)

View File

@ -5,8 +5,6 @@ import pytest
from homeassistant.core import HomeAssistant
from .conftest import patch_metrics
from tests.common import MockConfigEntry
@ -21,14 +19,19 @@ async def test_binary_sensor_entitity(
metrics: dict[str, Any],
expected_state: str,
mock_entry: MockConfigEntry,
setup_fetch_metric_data_mock,
hass: HomeAssistant,
):
) -> None:
"""Test binary sensor with metrics."""
# Arrange
fetch_metric_data_mock = setup_fetch_metric_data_mock(metrics)
# Act
with patch_metrics(metrics=metrics):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
fetch_metric_data_mock.assert_called_once()
sensor = hass.states.get("binary_sensor.vallox_post_heater")
assert sensor.state == expected_state

View File

@ -33,7 +33,7 @@ async def test_form_create_entry(hass: HomeAssistant) -> None:
assert init["errors"] is None
with patch(
"homeassistant.components.vallox.config_flow.Vallox.get_info",
"homeassistant.components.vallox.config_flow.Vallox.fetch_metric_data",
return_value=None,
), patch(
"homeassistant.components.vallox.async_setup_entry",
@ -74,7 +74,7 @@ async def test_form_vallox_api_exception_cannot_connect(hass: HomeAssistant) ->
)
with patch(
"homeassistant.components.vallox.config_flow.Vallox.get_info",
"homeassistant.components.vallox.config_flow.Vallox.fetch_metric_data",
side_effect=ValloxApiException,
):
result = await hass.config_entries.flow.async_configure(
@ -94,7 +94,7 @@ async def test_form_os_error_cannot_connect(hass: HomeAssistant) -> None:
)
with patch(
"homeassistant.components.vallox.config_flow.Vallox.get_info",
"homeassistant.components.vallox.config_flow.Vallox.fetch_metric_data",
side_effect=ValloxWebsocketException,
):
result = await hass.config_entries.flow.async_configure(
@ -114,7 +114,7 @@ async def test_form_unknown_exception(hass: HomeAssistant) -> None:
)
with patch(
"homeassistant.components.vallox.config_flow.Vallox.get_info",
"homeassistant.components.vallox.config_flow.Vallox.fetch_metric_data",
side_effect=Exception,
):
result = await hass.config_entries.flow.async_configure(

View File

@ -2,7 +2,7 @@
from unittest.mock import call
import pytest
from vallox_websocket_api import PROFILE, ValloxApiException
from vallox_websocket_api import MetricData, MetricValue, Profile, ValloxApiException
from homeassistant.components.fan import (
ATTR_PERCENTAGE,
@ -16,7 +16,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_O
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .conftest import patch_metrics, patch_metrics_set, patch_profile, patch_profile_set
from .conftest import patch_set_fan_speed, patch_set_profile, patch_set_values
from tests.common import MockConfigEntry
@ -26,45 +26,57 @@ from tests.common import MockConfigEntry
[({"A_CYC_MODE": 0}, "on"), ({"A_CYC_MODE": 5}, "off")],
)
async def test_fan_state(
metrics: dict[str, int],
metrics: dict[str, MetricValue],
expected_state: str,
mock_entry: MockConfigEntry,
setup_fetch_metric_data_mock,
hass: HomeAssistant,
) -> None:
"""Test fan on/off state."""
# Arrange
fetch_metric_data_mock = setup_fetch_metric_data_mock(metrics=metrics)
# Act
with patch_metrics(metrics=metrics):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
fetch_metric_data_mock.assert_called_once()
sensor = hass.states.get("fan.vallox")
assert sensor
assert sensor.state == expected_state
@pytest.mark.parametrize(
("profile", "expected_preset"),
("vallox_profile", "expected_preset"),
[
(PROFILE.HOME, "Home"),
(PROFILE.AWAY, "Away"),
(PROFILE.BOOST, "Boost"),
(PROFILE.FIREPLACE, "Fireplace"),
(Profile.HOME, "Home"),
(Profile.AWAY, "Away"),
(Profile.BOOST, "Boost"),
(Profile.FIREPLACE, "Fireplace"),
],
)
async def test_fan_profile(
profile: PROFILE,
vallox_profile: Profile,
expected_preset: str,
mock_entry: MockConfigEntry,
setup_fetch_metric_data_mock,
hass: HomeAssistant,
) -> None:
"""Test fan profile."""
# Arrange
class MockMetricData(MetricData):
@property
def profile(self):
return vallox_profile
setup_fetch_metric_data_mock(metric_data_class=MockMetricData)
# Act
with patch_profile(profile):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("fan.vallox")
@ -81,13 +93,16 @@ async def test_fan_profile(
)
async def test_turn_on_off(
service: str,
initial_metrics: dict[str, int],
expected_called_with: dict[str, int],
initial_metrics: dict[str, MetricValue],
expected_called_with: dict[str, MetricValue],
mock_entry: MockConfigEntry,
setup_fetch_metric_data_mock,
hass: HomeAssistant,
) -> None:
"""Test turn on/off."""
with patch_metrics(metrics=initial_metrics), patch_metrics_set() as metrics_set:
setup_fetch_metric_data_mock(metrics=initial_metrics)
with patch_set_values() as set_values:
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
@ -96,7 +111,7 @@ async def test_turn_on_off(
service_data={ATTR_ENTITY_ID: "fan.vallox"},
blocking=True,
)
metrics_set.assert_called_once_with(expected_called_with)
set_values.assert_called_once_with(expected_called_with)
@pytest.mark.parametrize(
@ -118,15 +133,17 @@ async def test_turn_on_off(
],
)
async def test_turn_on_with_parameters(
initial_metrics: dict[str, int],
initial_metrics: dict[str, MetricValue],
expected_call_args_list: list[tuple],
mock_entry: MockConfigEntry,
hass: HomeAssistant,
setup_fetch_metric_data_mock,
) -> None:
"""Test turn on/off."""
with patch_metrics(
metrics=initial_metrics
), patch_metrics_set() as metrics_set, patch_profile_set() as profile_set:
setup_fetch_metric_data_mock(metrics=initial_metrics)
with patch_set_values() as set_values, patch_set_profile() as set_profile:
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
@ -139,29 +156,38 @@ async def test_turn_on_with_parameters(
},
blocking=True,
)
assert metrics_set.call_args_list == expected_call_args_list
profile_set.assert_called_once_with(PROFILE.AWAY)
set_profile.assert_called_once_with(Profile.AWAY)
assert set_values.call_args_list == expected_call_args_list
@pytest.mark.parametrize(
("preset", "initial_profile", "expected_call_args_list"),
[
("Home", PROFILE.AWAY, [call(PROFILE.HOME)]),
("Away", PROFILE.HOME, [call(PROFILE.AWAY)]),
("Boost", PROFILE.HOME, [call(PROFILE.BOOST)]),
("Fireplace", PROFILE.HOME, [call(PROFILE.FIREPLACE)]),
("Home", PROFILE.HOME, []),
("Home", Profile.AWAY, [call(Profile.HOME)]),
("Away", Profile.HOME, [call(Profile.AWAY)]),
("Boost", Profile.HOME, [call(Profile.BOOST)]),
("Fireplace", Profile.HOME, [call(Profile.FIREPLACE)]),
("Home", Profile.HOME, []), # No change
],
)
async def test_set_preset_mode(
preset: str,
initial_profile: PROFILE,
initial_profile: Profile,
expected_call_args_list: list[tuple],
mock_entry: MockConfigEntry,
hass: HomeAssistant,
setup_fetch_metric_data_mock,
) -> None:
"""Test set preset mode."""
with patch_profile(initial_profile), patch_profile_set() as profile_set:
class MockMetricData(MetricData):
@property
def profile(self):
return initial_profile
setup_fetch_metric_data_mock(metric_data_class=MockMetricData)
with patch_set_profile() as set_profile:
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
@ -170,7 +196,7 @@ async def test_set_preset_mode(
service_data={ATTR_ENTITY_ID: "fan.vallox", ATTR_PRESET_MODE: preset},
blocking=True,
)
assert profile_set.call_args_list == expected_call_args_list
assert set_profile.call_args_list == expected_call_args_list
async def test_set_invalid_preset_mode(
@ -198,8 +224,8 @@ async def test_set_preset_mode_exception(
hass: HomeAssistant,
) -> None:
"""Test set preset mode."""
with patch_profile_set() as profile_set:
profile_set.side_effect = ValloxApiException("Fake exception")
with patch_set_profile() as set_profile:
set_profile.side_effect = ValloxApiException("Fake exception")
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
with pytest.raises(HomeAssistantError):
@ -212,25 +238,40 @@ async def test_set_preset_mode_exception(
@pytest.mark.parametrize(
("profile", "percentage", "expected_call_args_list"),
(
"initial_profile",
"percentage",
"expected_set_fan_speed_call",
"expected_set_values_call",
),
[
(PROFILE.HOME, 40, [call({"A_CYC_HOME_SPEED_SETTING": 40})]),
(PROFILE.AWAY, 30, [call({"A_CYC_AWAY_SPEED_SETTING": 30})]),
(PROFILE.BOOST, 60, [call({"A_CYC_BOOST_SPEED_SETTING": 60})]),
(PROFILE.HOME, 0, [call({"A_CYC_MODE": 5})]),
(Profile.HOME, 40, [call(Profile.HOME, 40)], []),
(Profile.AWAY, 30, [call(Profile.AWAY, 30)], []),
(Profile.BOOST, 60, [call(Profile.BOOST, 60)], []),
(Profile.HOME, 0, [], [call({"A_CYC_MODE": 5})]), # Turn off
],
)
async def test_set_fan_speed(
profile: PROFILE,
initial_profile: Profile,
percentage: int,
expected_call_args_list: list[tuple],
expected_set_fan_speed_call: list[tuple],
expected_set_values_call: list[tuple],
mock_entry: MockConfigEntry,
hass: HomeAssistant,
setup_fetch_metric_data_mock,
) -> None:
"""Test set fan speed percentage."""
with patch_profile(profile), patch_metrics_set() as metrics_set, patch_metrics(
{"A_CYC_MODE": 0}
):
class MockMetricData(MetricData):
@property
def profile(self):
return initial_profile
setup_fetch_metric_data_mock(
metrics={"A_CYC_MODE": 0}, metric_data_class=MockMetricData
)
with patch_set_fan_speed() as set_fan_speed, patch_set_values() as set_values:
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
@ -239,18 +280,20 @@ async def test_set_fan_speed(
service_data={ATTR_ENTITY_ID: "fan.vallox", ATTR_PERCENTAGE: percentage},
blocking=True,
)
assert metrics_set.call_args_list == expected_call_args_list
assert set_fan_speed.call_args_list == expected_set_fan_speed_call
assert set_values.call_args_list == expected_set_values_call
async def test_set_fan_speed_exception(
mock_entry: MockConfigEntry,
hass: HomeAssistant,
mock_entry: MockConfigEntry, hass: HomeAssistant, setup_fetch_metric_data_mock
) -> None:
"""Test set fan speed percentage."""
with patch_metrics_set() as metrics_set, patch_metrics(
{"A_CYC_MODE": 0, "A_CYC_HOME_SPEED_SETTING": 30}
):
metrics_set.side_effect = ValloxApiException("Fake failure")
setup_fetch_metric_data_mock(
metrics={"A_CYC_MODE": 0, "A_CYC_HOME_SPEED_SETTING": 30}
)
with patch_set_values() as set_values:
set_values.side_effect = ValloxApiException("Fake failure")
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
with pytest.raises(HomeAssistantError):

View File

@ -0,0 +1,49 @@
"""Tests for the Vallox integration."""
import pytest
from vallox_websocket_api import Profile
from homeassistant.components.vallox import (
ATTR_PROFILE_FAN_SPEED,
SERVICE_SET_PROFILE_FAN_SPEED_AWAY,
SERVICE_SET_PROFILE_FAN_SPEED_BOOST,
SERVICE_SET_PROFILE_FAN_SPEED_HOME,
)
from homeassistant.components.vallox.const import DOMAIN
from homeassistant.core import HomeAssistant
from .conftest import patch_set_fan_speed
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
("service", "profile"),
[
(SERVICE_SET_PROFILE_FAN_SPEED_HOME, Profile.HOME),
(SERVICE_SET_PROFILE_FAN_SPEED_AWAY, Profile.AWAY),
(SERVICE_SET_PROFILE_FAN_SPEED_BOOST, Profile.BOOST),
],
)
async def test_create_service(
hass: HomeAssistant,
mock_entry: MockConfigEntry,
service: str,
profile: Profile,
) -> None:
"""Test services for setting fan speed."""
# Act
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
with patch_set_fan_speed() as set_fan_speed:
await hass.services.async_call(
DOMAIN,
service,
service_data={ATTR_PROFILE_FAN_SPEED: 30},
)
await hass.async_block_till_done()
# Assert
set_fan_speed.assert_called_once_with(profile, 30)

View File

@ -9,7 +9,7 @@ from homeassistant.components.number.const import (
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from .conftest import patch_metrics, patch_metrics_set
from .conftest import patch_set_values
from tests.common import MockConfigEntry
@ -41,15 +41,15 @@ async def test_temperature_number_entities(
value: float,
mock_entry: MockConfigEntry,
hass: HomeAssistant,
setup_fetch_metric_data_mock,
) -> None:
"""Test temperature entities."""
# Arrange
metrics = {metric_key: value}
setup_fetch_metric_data_mock(metrics={metric_key: value})
# Act
with patch_metrics(metrics=metrics):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get(entity_id)
@ -66,10 +66,14 @@ async def test_temperature_number_entity_set(
value: float,
mock_entry: MockConfigEntry,
hass: HomeAssistant,
setup_fetch_metric_data_mock,
) -> None:
"""Test temperature set."""
# Arrange
setup_fetch_metric_data_mock(metrics={metric_key: value})
# Act
with patch_metrics(metrics={}), patch_metrics_set() as metrics_set:
with patch_set_values() as set_values:
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
@ -81,4 +85,4 @@ async def test_temperature_number_entity_set(
},
)
await hass.async_block_till_done()
metrics_set.assert_called_once_with({metric_key: value})
set_values.assert_called_once_with({metric_key: value})

View File

@ -1,15 +1,13 @@
"""Tests for Vallox sensor platform."""
from datetime import datetime, timedelta, tzinfo
from unittest.mock import patch
import pytest
from vallox_websocket_api import MetricData
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from .conftest import patch_metrics
from tests.common import MockConfigEntry
@ -45,34 +43,21 @@ def _now_at_13():
return dt_util.now().timetz().replace(hour=13, minute=0, second=0, microsecond=0)
async def test_remaining_filter_returns_timestamp(
mock_entry: MockConfigEntry, hass: HomeAssistant
) -> None:
"""Test that the remaining time for filter sensor returns a timestamp."""
# Act
with patch(
"homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=dt_util.now().date(),
), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_remaining_time_for_filter")
assert sensor.attributes["device_class"] == "timestamp"
async def test_remaining_time_for_filter_none_returned_from_vallox(
mock_entry: MockConfigEntry, hass: HomeAssistant
mock_entry: MockConfigEntry, hass: HomeAssistant, setup_fetch_metric_data_mock
) -> None:
"""Test that the remaining time for filter sensor returns 'unknown' when Vallox returns None."""
class MockMetricData(MetricData):
@property
def next_filter_change_date(self):
return None
# Arrange
setup_fetch_metric_data_mock(metric_data_class=MockMetricData)
# Act
with patch(
"homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=None,
), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_remaining_time_for_filter")
@ -80,166 +65,72 @@ async def test_remaining_time_for_filter_none_returned_from_vallox(
@pytest.mark.parametrize(
"set_tz",
("remaining_days", "set_tz"),
[
"utc",
"helsinki",
"new_york",
(112, "utc"),
(112, "helsinki"),
(112, "new_york"),
(0, "utc"),
(-3, "utc"),
],
indirect=True,
indirect=["set_tz"],
)
async def test_remaining_time_for_filter_in_the_future(
mock_entry: MockConfigEntry, set_tz: tzinfo, hass: HomeAssistant
async def test_remaining_time_for_filter(
remaining_days,
set_tz: tzinfo,
mock_entry: MockConfigEntry,
hass: HomeAssistant,
setup_fetch_metric_data_mock,
) -> None:
"""Test remaining time for filter when Vallox returns a date in the future."""
"""Test remaining time for filter when Vallox returns different dates."""
# Arrange
remaining_days = 112
mocked_filter_end_date = dt_util.now().date() + timedelta(days=remaining_days)
class MockMetricData(MetricData):
@property
def next_filter_change_date(self):
return mocked_filter_end_date
setup_fetch_metric_data_mock(metric_data_class=MockMetricData)
# Act
with patch(
"homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=mocked_filter_end_date,
), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_remaining_time_for_filter")
assert sensor.attributes["device_class"] == "timestamp"
assert _sensor_to_datetime(sensor) == datetime.combine(
mocked_filter_end_date,
_now_at_13(),
)
async def test_remaining_time_for_filter_today(
mock_entry: MockConfigEntry, hass: HomeAssistant
@pytest.mark.parametrize(
("metrics", "expected_state"),
[
({"A_CYC_CELL_STATE": 0}, "Heat Recovery"),
({"A_CYC_CELL_STATE": 1}, "Cool Recovery"),
({"A_CYC_CELL_STATE": 2}, "Bypass"),
({"A_CYC_CELL_STATE": 3}, "Defrosting"),
({"A_CYC_CELL_STATE": 4}, "unknown"),
],
)
async def test_cell_state_sensor(
metrics,
expected_state,
mock_entry: MockConfigEntry,
hass: HomeAssistant,
setup_fetch_metric_data_mock,
) -> None:
"""Test remaining time for filter when Vallox returns today."""
"""Test cell state sensor in different states."""
# Arrange
remaining_days = 0
mocked_filter_end_date = dt_util.now().date() + timedelta(days=remaining_days)
setup_fetch_metric_data_mock(metrics=metrics)
# Act
with patch(
"homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=mocked_filter_end_date,
), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_remaining_time_for_filter")
assert _sensor_to_datetime(sensor) == datetime.combine(
mocked_filter_end_date,
_now_at_13(),
)
async def test_remaining_time_for_filter_in_the_past(
mock_entry: MockConfigEntry, hass: HomeAssistant
) -> None:
"""Test remaining time for filter when Vallox returns a date in the past."""
# Arrange
remaining_days = -3
mocked_filter_end_date = dt_util.now().date() + timedelta(days=remaining_days)
# Act
with patch(
"homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=mocked_filter_end_date,
), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_remaining_time_for_filter")
assert _sensor_to_datetime(sensor) == datetime.combine(
mocked_filter_end_date,
_now_at_13(),
)
async def test_cell_state_sensor_heat_recovery(
mock_entry: MockConfigEntry, hass: HomeAssistant
) -> None:
"""Test cell state sensor in heat recovery state."""
# Arrange
metrics = {"A_CYC_CELL_STATE": 0}
# Act
with patch_metrics(metrics=metrics):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_cell_state")
assert sensor.state == "Heat Recovery"
async def test_cell_state_sensor_cool_recovery(
mock_entry: MockConfigEntry, hass: HomeAssistant
) -> None:
"""Test cell state sensor in cool recovery state."""
# Arrange
metrics = {"A_CYC_CELL_STATE": 1}
# Act
with patch_metrics(metrics=metrics):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_cell_state")
assert sensor.state == "Cool Recovery"
async def test_cell_state_sensor_bypass(
mock_entry: MockConfigEntry, hass: HomeAssistant
) -> None:
"""Test cell state sensor in bypass state."""
# Arrange
metrics = {"A_CYC_CELL_STATE": 2}
# Act
with patch_metrics(metrics=metrics):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_cell_state")
assert sensor.state == "Bypass"
async def test_cell_state_sensor_defrosting(
mock_entry: MockConfigEntry, hass: HomeAssistant
) -> None:
"""Test cell state sensor in defrosting state."""
# Arrange
metrics = {"A_CYC_CELL_STATE": 3}
# Act
with patch_metrics(metrics=metrics):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_cell_state")
assert sensor.state == "Defrosting"
async def test_cell_state_sensor_unknown_state(
mock_entry: MockConfigEntry, hass: HomeAssistant
) -> None:
"""Test cell state sensor in unknown state."""
# Arrange
metrics = {"A_CYC_CELL_STATE": 4}
# Act
with patch_metrics(metrics=metrics):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get("sensor.vallox_cell_state")
assert sensor.state == "unknown"
assert sensor.state == expected_state

View File

@ -1,5 +1,4 @@
"""Tests for Vallox switch platform."""
from unittest.mock import patch
import pytest
@ -7,7 +6,7 @@ from homeassistant.components.switch.const import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.core import HomeAssistant
from .conftest import patch_metrics, patch_metrics_set
from .conftest import patch_set_values
from tests.common import MockConfigEntry
@ -26,17 +25,15 @@ async def test_switch_entities(
expected_state: str,
mock_entry: MockConfigEntry,
hass: HomeAssistant,
setup_fetch_metric_data_mock,
) -> None:
"""Test switch entities."""
# Arrange
metrics = {metric_key: value}
setup_fetch_metric_data_mock(metrics={metric_key: value})
# Act
with patch_metrics(metrics=metrics), patch(
"homeassistant.components.vallox.Vallox.set_settable_address"
):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
# Assert
sensor = hass.states.get(entity_id)
@ -60,9 +57,7 @@ async def test_bypass_lock_switch_entitity_set(
) -> None:
"""Test bypass lock switch set."""
# Act
with patch_metrics(metrics={}), patch_metrics_set() as metrics_set, patch(
"homeassistant.components.vallox.Vallox.set_settable_address"
):
with patch_set_values() as set_values:
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
@ -71,4 +66,4 @@ async def test_bypass_lock_switch_entitity_set(
service_data={ATTR_ENTITY_ID: "switch.vallox_bypass_locked"},
)
await hass.async_block_till_done()
metrics_set.assert_called_once_with({metric_key: value})
set_values.assert_called_once_with({metric_key: value})