Add device_info and entity_category to Vallox (#67353)

* Add device_info and entity_category to Vallox

* Fix DeviceInfo

* Address review comments 1

* vallox suggested changes

Co-authored-by: Sebastian Lövdahl <slovdahl@hibox.fi>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Andre Richter 2022-05-10 00:00:31 +02:00 committed by GitHub
parent 64636a4310
commit 3a00c95113
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 93 additions and 37 deletions

View file

@ -5,14 +5,16 @@ from dataclasses import dataclass, field
from datetime import date from datetime import date
import ipaddress import ipaddress
import logging import logging
from typing import Any, NamedTuple from typing import Any, NamedTuple, cast
from uuid import UUID 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 ( from vallox_websocket_api.vallox import (
get_next_filter_change_date as calculate_next_filter_change_date, get_model as _api_get_model,
get_uuid as calculate_uuid, 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,
) )
import voluptuous as vol import voluptuous as vol
@ -20,8 +22,13 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, Platform from homeassistant.const import CONF_HOST, CONF_NAME, Platform
from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.typing import ConfigType, StateType from homeassistant.helpers.typing import ConfigType, StateType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from .const import ( from .const import (
DEFAULT_FAN_SPEED_AWAY, DEFAULT_FAN_SPEED_AWAY,
@ -114,16 +121,32 @@ class ValloxState:
return value return value
def get_uuid(self) -> UUID | None: @property
def model(self) -> str | None:
"""Return the model, if any."""
model = cast(str, _api_get_model(self.metric_cache))
if model == "Unknown":
return None
return model
@property
def sw_version(self) -> str:
"""Return the SW version."""
return cast(str, _api_get_sw_version(self.metric_cache))
@property
def uuid(self) -> UUID | None:
"""Return cached UUID value.""" """Return cached UUID value."""
uuid = calculate_uuid(self.metric_cache) uuid = _api_get_uuid(self.metric_cache)
if not isinstance(uuid, UUID): if not isinstance(uuid, UUID):
raise ValueError raise ValueError
return uuid return uuid
def get_next_filter_change_date(self) -> date | None: def get_next_filter_change_date(self) -> date | None:
"""Return the next filter change date.""" """Return the next filter change date."""
next_filter_change_date = calculate_next_filter_change_date(self.metric_cache) next_filter_change_date = _api_get_next_filter_change_date(self.metric_cache)
if not isinstance(next_filter_change_date, date): if not isinstance(next_filter_change_date, date):
return None return None
@ -291,3 +314,22 @@ class ValloxServiceHandler:
# be observed by all parties involved. # be observed by all parties involved.
if result: if result:
await self._coordinator.async_request_refresh() await self._coordinator.async_request_refresh()
class ValloxEntity(CoordinatorEntity[ValloxDataUpdateCoordinator]):
"""Representation of a Vallox entity."""
def __init__(self, name: str, coordinator: ValloxDataUpdateCoordinator) -> None:
"""Initialize a Vallox entity."""
super().__init__(coordinator)
self._device_uuid = self.coordinator.data.uuid
assert self.coordinator.config_entry is not None
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, str(self._device_uuid))},
manufacturer=DEFAULT_NAME,
model=self.coordinator.data.model,
name=name,
sw_version=self.coordinator.data.sw_version,
configuration_url=f"http://{self.coordinator.config_entry.data[CONF_HOST]}",
)

View file

@ -9,19 +9,18 @@ from homeassistant.components.binary_sensor import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import ValloxDataUpdateCoordinator from . import ValloxDataUpdateCoordinator, ValloxEntity
from .const import DOMAIN from .const import DOMAIN
class ValloxBinarySensor( class ValloxBinarySensor(ValloxEntity, BinarySensorEntity):
CoordinatorEntity[ValloxDataUpdateCoordinator], BinarySensorEntity
):
"""Representation of a Vallox binary sensor.""" """Representation of a Vallox binary sensor."""
entity_description: ValloxBinarySensorEntityDescription entity_description: ValloxBinarySensorEntityDescription
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__( def __init__(
self, self,
@ -30,14 +29,12 @@ class ValloxBinarySensor(
description: ValloxBinarySensorEntityDescription, description: ValloxBinarySensorEntityDescription,
) -> None: ) -> None:
"""Initialize the Vallox binary sensor.""" """Initialize the Vallox binary sensor."""
super().__init__(coordinator) super().__init__(name, coordinator)
self.entity_description = description self.entity_description = description
self._attr_name = f"{name} {description.name}" self._attr_name = f"{name} {description.name}"
self._attr_unique_id = f"{self._device_uuid}-{description.key}"
uuid = self.coordinator.data.get_uuid()
self._attr_unique_id = f"{uuid}-{description.key}"
@property @property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:

View file

@ -17,9 +17,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant 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 . import ValloxDataUpdateCoordinator from . import ValloxDataUpdateCoordinator, ValloxEntity
from .const import ( from .const import (
DOMAIN, DOMAIN,
METRIC_KEY_MODE, METRIC_KEY_MODE,
@ -80,7 +79,7 @@ async def async_setup_entry(
async_add_entities([device]) async_add_entities([device])
class ValloxFan(CoordinatorEntity[ValloxDataUpdateCoordinator], FanEntity): class ValloxFan(ValloxEntity, FanEntity):
"""Representation of the fan.""" """Representation of the fan."""
_attr_supported_features = FanEntityFeature.PRESET_MODE _attr_supported_features = FanEntityFeature.PRESET_MODE
@ -92,13 +91,12 @@ class ValloxFan(CoordinatorEntity[ValloxDataUpdateCoordinator], FanEntity):
coordinator: ValloxDataUpdateCoordinator, coordinator: ValloxDataUpdateCoordinator,
) -> None: ) -> None:
"""Initialize the fan.""" """Initialize the fan."""
super().__init__(coordinator) super().__init__(name, coordinator)
self._client = client self._client = client
self._attr_name = name self._attr_name = name
self._attr_unique_id = str(self._device_uuid)
self._attr_unique_id = str(self.coordinator.data.get_uuid())
@property @property
def preset_modes(self) -> list[str]: def preset_modes(self) -> list[str]:

View file

@ -17,12 +17,12 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
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.util import dt from homeassistant.util import dt
from . import ValloxDataUpdateCoordinator from . import ValloxDataUpdateCoordinator, ValloxEntity
from .const import ( from .const import (
DOMAIN, DOMAIN,
METRIC_KEY_MODE, METRIC_KEY_MODE,
@ -32,10 +32,11 @@ from .const import (
) )
class ValloxSensor(CoordinatorEntity[ValloxDataUpdateCoordinator], SensorEntity): class ValloxSensor(ValloxEntity, SensorEntity):
"""Representation of a Vallox sensor.""" """Representation of a Vallox sensor."""
entity_description: ValloxSensorEntityDescription entity_description: ValloxSensorEntityDescription
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__( def __init__(
self, self,
@ -44,14 +45,12 @@ class ValloxSensor(CoordinatorEntity[ValloxDataUpdateCoordinator], SensorEntity)
description: ValloxSensorEntityDescription, description: ValloxSensorEntityDescription,
) -> None: ) -> None:
"""Initialize the Vallox sensor.""" """Initialize the Vallox sensor."""
super().__init__(coordinator) super().__init__(name, coordinator)
self.entity_description = description self.entity_description = description
self._attr_name = f"{name} {description.name}" self._attr_name = f"{name} {description.name}"
self._attr_unique_id = f"{self._device_uuid}-{description.key}"
uuid = self.coordinator.data.get_uuid()
self._attr_unique_id = f"{uuid}-{description.key}"
@property @property
def native_value(self) -> StateType | datetime: def native_value(self) -> StateType | datetime:

View file

@ -50,10 +50,30 @@ def patch_profile_home():
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def patch_uuid(): def patch_model():
"""Patch the Vallox entity UUID.""" """Patch the Vallox model response."""
with patch( with patch(
"homeassistant.components.vallox.calculate_uuid", "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(), return_value=_random_uuid(),
): ):
yield yield

View file

@ -51,7 +51,7 @@ async def test_remaining_filter_returns_timestamp(
"""Test that the remaining time for filter sensor returns a timestamp.""" """Test that the remaining time for filter sensor returns a timestamp."""
# Act # Act
with patch( with patch(
"homeassistant.components.vallox.calculate_next_filter_change_date", "homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=dt.now().date(), return_value=dt.now().date(),
), patch_metrics(metrics={}): ), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry_id)
@ -68,7 +68,7 @@ async def test_remaining_time_for_filter_none_returned_from_vallox(
"""Test that the remaining time for filter sensor returns 'unknown' when Vallox returns None.""" """Test that the remaining time for filter sensor returns 'unknown' when Vallox returns None."""
# Act # Act
with patch( with patch(
"homeassistant.components.vallox.calculate_next_filter_change_date", "homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=None, return_value=None,
), patch_metrics(metrics={}): ), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry_id)
@ -98,7 +98,7 @@ async def test_remaining_time_for_filter_in_the_future(
# Act # Act
with patch( with patch(
"homeassistant.components.vallox.calculate_next_filter_change_date", "homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=mocked_filter_end_date, return_value=mocked_filter_end_date,
), patch_metrics(metrics={}): ), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry_id)
@ -122,7 +122,7 @@ async def test_remaining_time_for_filter_today(
# Act # Act
with patch( with patch(
"homeassistant.components.vallox.calculate_next_filter_change_date", "homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=mocked_filter_end_date, return_value=mocked_filter_end_date,
), patch_metrics(metrics={}): ), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry_id)
@ -146,7 +146,7 @@ async def test_remaining_time_for_filter_in_the_past(
# Act # Act
with patch( with patch(
"homeassistant.components.vallox.calculate_next_filter_change_date", "homeassistant.components.vallox._api_get_next_filter_change_date",
return_value=mocked_filter_end_date, return_value=mocked_filter_end_date,
), patch_metrics(metrics={}): ), patch_metrics(metrics={}):
await hass.config_entries.async_setup(mock_entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry_id)