mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +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 dataclasses import dataclass, field
|
||||
from datetime import date
|
||||
import ipaddress
|
||||
import logging
|
||||
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.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
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
@ -117,6 +121,15 @@ class ValloxState:
|
||||
raise ValueError
|
||||
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):
|
||||
"""The DataUpdateCoordinator for Vallox."""
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "vallox",
|
||||
"name": "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-"],
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling",
|
||||
|
@ -2,7 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, time
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util import dt
|
||||
|
||||
from . import ValloxDataUpdateCoordinator
|
||||
from .const import (
|
||||
@ -95,18 +95,15 @@ class ValloxFilterRemainingSensor(ValloxSensor):
|
||||
@property
|
||||
def native_value(self) -> StateType | datetime:
|
||||
"""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
|
||||
|
||||
# Since only a delta of days is received from the device, fix the time so the timestamp does
|
||||
# not change with every update.
|
||||
days_remaining = float(super_native_value)
|
||||
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)
|
||||
return datetime.combine(
|
||||
next_filter_change_date,
|
||||
time(hour=13, minute=0, second=0, tzinfo=dt.DEFAULT_TIME_ZONE),
|
||||
)
|
||||
|
||||
|
||||
class ValloxCellStateSensor(ValloxSensor):
|
||||
@ -150,7 +147,6 @@ SENSORS: tuple[ValloxSensorEntityDescription, ...] = (
|
||||
ValloxSensorEntityDescription(
|
||||
key="remaining_time_for_filter",
|
||||
name="Remaining Time For Filter",
|
||||
metric_key="A_CYC_REMAINING_TIME_FOR_FILTER",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
sensor_type=ValloxFilterRemainingSensor,
|
||||
),
|
||||
|
@ -2425,7 +2425,7 @@ uscisstatus==0.1.1
|
||||
uvcclient==0.11.0
|
||||
|
||||
# homeassistant.components.vallox
|
||||
vallox-websocket-api==2.9.0
|
||||
vallox-websocket-api==2.11.0
|
||||
|
||||
# homeassistant.components.rdw
|
||||
vehicle==0.3.1
|
||||
|
@ -1495,7 +1495,7 @@ url-normalize==1.4.1
|
||||
uvcclient==0.11.0
|
||||
|
||||
# homeassistant.components.vallox
|
||||
vallox-websocket-api==2.9.0
|
||||
vallox-websocket-api==2.11.0
|
||||
|
||||
# homeassistant.components.rdw
|
||||
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