mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
Improve Vallox filter remaining time sensor (#66763)
This commit is contained in:
parent
b19bf9b147
commit
744a2013cd
@ -2,6 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import date
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, NamedTuple
|
from typing import Any, NamedTuple
|
||||||
@ -9,7 +10,10 @@ from uuid import UUID
|
|||||||
|
|
||||||
from vallox_websocket_api import PROFILE as VALLOX_PROFILE, Vallox
|
from vallox_websocket_api import PROFILE as VALLOX_PROFILE, Vallox
|
||||||
from vallox_websocket_api.exceptions import ValloxApiException
|
from vallox_websocket_api.exceptions import ValloxApiException
|
||||||
from vallox_websocket_api.vallox import get_uuid as calculate_uuid
|
from vallox_websocket_api.vallox import (
|
||||||
|
get_next_filter_change_date as calculate_next_filter_change_date,
|
||||||
|
get_uuid as calculate_uuid,
|
||||||
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
@ -117,6 +121,15 @@ class ValloxState:
|
|||||||
raise ValueError
|
raise ValueError
|
||||||
return uuid
|
return uuid
|
||||||
|
|
||||||
|
def get_next_filter_change_date(self) -> date | None:
|
||||||
|
"""Return the next filter change date."""
|
||||||
|
next_filter_change_date = calculate_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):
|
class ValloxDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
"""The DataUpdateCoordinator for Vallox."""
|
"""The DataUpdateCoordinator for Vallox."""
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "vallox",
|
"domain": "vallox",
|
||||||
"name": "Vallox",
|
"name": "Vallox",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/vallox",
|
"documentation": "https://www.home-assistant.io/integrations/vallox",
|
||||||
"requirements": ["vallox-websocket-api==2.9.0"],
|
"requirements": ["vallox-websocket-api==2.11.0"],
|
||||||
"codeowners": ["@andre-richter", "@slovdahl", "@viiru-"],
|
"codeowners": ["@andre-richter", "@slovdahl", "@viiru-"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, time
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt
|
||||||
|
|
||||||
from . import ValloxDataUpdateCoordinator
|
from . import ValloxDataUpdateCoordinator
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -95,18 +95,15 @@ class ValloxFilterRemainingSensor(ValloxSensor):
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType | datetime:
|
def native_value(self) -> StateType | datetime:
|
||||||
"""Return the value reported by the sensor."""
|
"""Return the value reported by the sensor."""
|
||||||
super_native_value = super().native_value
|
next_filter_change_date = self.coordinator.data.get_next_filter_change_date()
|
||||||
|
|
||||||
if not isinstance(super_native_value, (int, float)):
|
if next_filter_change_date is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Since only a delta of days is received from the device, fix the time so the timestamp does
|
return datetime.combine(
|
||||||
# not change with every update.
|
next_filter_change_date,
|
||||||
days_remaining = float(super_native_value)
|
time(hour=13, minute=0, second=0, tzinfo=dt.DEFAULT_TIME_ZONE),
|
||||||
days_remaining_delta = timedelta(days=days_remaining)
|
)
|
||||||
now = datetime.utcnow().replace(hour=13, minute=0, second=0, microsecond=0)
|
|
||||||
|
|
||||||
return (now + days_remaining_delta).astimezone(dt_util.UTC)
|
|
||||||
|
|
||||||
|
|
||||||
class ValloxCellStateSensor(ValloxSensor):
|
class ValloxCellStateSensor(ValloxSensor):
|
||||||
@ -150,7 +147,6 @@ SENSORS: tuple[ValloxSensorEntityDescription, ...] = (
|
|||||||
ValloxSensorEntityDescription(
|
ValloxSensorEntityDescription(
|
||||||
key="remaining_time_for_filter",
|
key="remaining_time_for_filter",
|
||||||
name="Remaining Time For Filter",
|
name="Remaining Time For Filter",
|
||||||
metric_key="A_CYC_REMAINING_TIME_FOR_FILTER",
|
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
sensor_type=ValloxFilterRemainingSensor,
|
sensor_type=ValloxFilterRemainingSensor,
|
||||||
),
|
),
|
||||||
|
@ -2425,7 +2425,7 @@ uscisstatus==0.1.1
|
|||||||
uvcclient==0.11.0
|
uvcclient==0.11.0
|
||||||
|
|
||||||
# homeassistant.components.vallox
|
# homeassistant.components.vallox
|
||||||
vallox-websocket-api==2.9.0
|
vallox-websocket-api==2.11.0
|
||||||
|
|
||||||
# homeassistant.components.rdw
|
# homeassistant.components.rdw
|
||||||
vehicle==0.3.1
|
vehicle==0.3.1
|
||||||
|
@ -1495,7 +1495,7 @@ url-normalize==1.4.1
|
|||||||
uvcclient==0.11.0
|
uvcclient==0.11.0
|
||||||
|
|
||||||
# homeassistant.components.vallox
|
# homeassistant.components.vallox
|
||||||
vallox-websocket-api==2.9.0
|
vallox-websocket-api==2.11.0
|
||||||
|
|
||||||
# homeassistant.components.rdw
|
# homeassistant.components.rdw
|
||||||
vehicle==0.3.1
|
vehicle==0.3.1
|
||||||
|
65
tests/components/vallox/conftest.py
Normal file
65
tests/components/vallox/conftest.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"""Common utilities for Vallox tests."""
|
||||||
|
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import patch
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from vallox_websocket_api.vallox import PROFILE
|
||||||
|
|
||||||
|
from homeassistant.components.vallox.const import DOMAIN
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_entry(hass: HomeAssistant) -> MockConfigEntry:
|
||||||
|
"""Create mocked Vallox config entry."""
|
||||||
|
vallox_mock_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={
|
||||||
|
CONF_HOST: "192.168.100.50",
|
||||||
|
CONF_NAME: "Vallox",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
vallox_mock_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
return vallox_mock_entry
|
||||||
|
|
||||||
|
|
||||||
|
def patch_metrics(metrics: dict[str, Any]):
|
||||||
|
"""Patch the Vallox metrics response."""
|
||||||
|
return patch(
|
||||||
|
"homeassistant.components.vallox.Vallox.fetch_metrics",
|
||||||
|
return_value=metrics,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def patch_profile_home():
|
||||||
|
"""Patch the Vallox profile response."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.vallox.Vallox.get_profile",
|
||||||
|
return_value=PROFILE.HOME,
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def patch_uuid():
|
||||||
|
"""Patch the Vallox entity UUID."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.vallox.calculate_uuid",
|
||||||
|
return_value=_random_uuid(),
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
def _random_uuid():
|
||||||
|
"""Generate a random UUID."""
|
||||||
|
uuid = "".join(random.choices(string.hexdigits, k=32))
|
||||||
|
return UUID(uuid)
|
175
tests/components/vallox/test_sensor.py
Normal file
175
tests/components/vallox/test_sensor.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
"""Tests for Vallox sensor platform."""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta, tzinfo
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.util import dt
|
||||||
|
|
||||||
|
from .conftest import patch_metrics
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
ORIG_TZ = dt.DEFAULT_TIME_ZONE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def reset_tz():
|
||||||
|
"""Restore the default TZ after test runs."""
|
||||||
|
yield
|
||||||
|
dt.DEFAULT_TIME_ZONE = ORIG_TZ
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def set_tz(request):
|
||||||
|
"""Set the default TZ to the one requested."""
|
||||||
|
return request.getfixturevalue(request.param)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def utc() -> tzinfo:
|
||||||
|
"""Set the default TZ to UTC."""
|
||||||
|
tz = dt.get_time_zone("UTC")
|
||||||
|
dt.set_default_time_zone(tz)
|
||||||
|
return tz
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def helsinki() -> tzinfo:
|
||||||
|
"""Set the default TZ to Europe/Helsinki."""
|
||||||
|
tz = dt.get_time_zone("Europe/Helsinki")
|
||||||
|
dt.set_default_time_zone(tz)
|
||||||
|
return tz
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def new_york() -> tzinfo:
|
||||||
|
"""Set the default TZ to America/New_York."""
|
||||||
|
tz = dt.get_time_zone("America/New_York")
|
||||||
|
dt.set_default_time_zone(tz)
|
||||||
|
return tz
|
||||||
|
|
||||||
|
|
||||||
|
def _sensor_to_datetime(sensor):
|
||||||
|
return datetime.fromisoformat(sensor.state)
|
||||||
|
|
||||||
|
|
||||||
|
def _now_at_13():
|
||||||
|
return dt.now().timetz().replace(hour=13, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_remaining_filter_returns_timestamp(
|
||||||
|
mock_entry: MockConfigEntry, hass: HomeAssistant
|
||||||
|
):
|
||||||
|
"""Test that the remaining time for filter sensor returns a timestamp."""
|
||||||
|
# Act
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.vallox.calculate_next_filter_change_date",
|
||||||
|
return_value=dt.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
|
||||||
|
):
|
||||||
|
"""Test that the remaining time for filter sensor returns 'unknown' when Vallox returns None."""
|
||||||
|
# Act
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.vallox.calculate_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()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
sensor = hass.states.get("sensor.vallox_remaining_time_for_filter")
|
||||||
|
assert sensor.state == "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"set_tz",
|
||||||
|
[
|
||||||
|
"utc",
|
||||||
|
"helsinki",
|
||||||
|
"new_york",
|
||||||
|
],
|
||||||
|
indirect=True,
|
||||||
|
)
|
||||||
|
async def test_remaining_time_for_filter_in_the_future(
|
||||||
|
mock_entry: MockConfigEntry, set_tz: tzinfo, hass: HomeAssistant
|
||||||
|
):
|
||||||
|
"""Test remaining time for filter when Vallox returns a date in the future."""
|
||||||
|
# Arrange
|
||||||
|
remaining_days = 112
|
||||||
|
mocked_filter_end_date = dt.now().date() + timedelta(days=remaining_days)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.vallox.calculate_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_today(
|
||||||
|
mock_entry: MockConfigEntry, hass: HomeAssistant
|
||||||
|
):
|
||||||
|
"""Test remaining time for filter when Vallox returns today."""
|
||||||
|
# Arrange
|
||||||
|
remaining_days = 0
|
||||||
|
mocked_filter_end_date = dt.now().date() + timedelta(days=remaining_days)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.vallox.calculate_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
|
||||||
|
):
|
||||||
|
"""Test remaining time for filter when Vallox returns a date in the past."""
|
||||||
|
# Arrange
|
||||||
|
remaining_days = -3
|
||||||
|
mocked_filter_end_date = dt.now().date() + timedelta(days=remaining_days)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.vallox.calculate_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(),
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user