Add diagnostic sensor to Radarr (#79044)

* Add diagnostic sensor to Radarr

* coverage
This commit is contained in:
Robert Hillis 2022-09-25 10:11:53 -04:00 committed by GitHub
parent ef30ebd9e1
commit 42bd664305
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 38 additions and 32 deletions

View file

@ -1000,7 +1000,6 @@ omit =
homeassistant/components/rachio/entity.py homeassistant/components/rachio/entity.py
homeassistant/components/rachio/switch.py homeassistant/components/rachio/switch.py
homeassistant/components/rachio/webhooks.py homeassistant/components/rachio/webhooks.py
homeassistant/components/radarr/sensor.py
homeassistant/components/radio_browser/__init__.py homeassistant/components/radio_browser/__init__.py
homeassistant/components/radio_browser/media_source.py homeassistant/components/radio_browser/media_source.py
homeassistant/components/radiotherm/__init__.py homeassistant/components/radiotherm/__init__.py

View file

@ -7,6 +7,7 @@ from aiopyarr.radarr_client import RadarrClient
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_SW_VERSION,
CONF_API_KEY, CONF_API_KEY,
CONF_PLATFORM, CONF_PLATFORM,
CONF_URL, CONF_URL,
@ -77,13 +78,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"disk_space": DiskSpaceDataUpdateCoordinator(hass, host_configuration, radarr), "disk_space": DiskSpaceDataUpdateCoordinator(hass, host_configuration, radarr),
"movie": MoviesDataUpdateCoordinator(hass, host_configuration, radarr), "movie": MoviesDataUpdateCoordinator(hass, host_configuration, radarr),
} }
# Temporary, until we add diagnostic entities
_version = None
for coordinator in coordinators.values(): for coordinator in coordinators.values():
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
if isinstance(coordinator, StatusDataUpdateCoordinator):
_version = coordinator.data
coordinator.system_version = _version
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinators hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinators
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@ -105,11 +101,13 @@ class RadarrEntity(CoordinatorEntity[RadarrDataUpdateCoordinator]):
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about the Radarr instance.""" """Return device information about the Radarr instance."""
return DeviceInfo( device_info = DeviceInfo(
configuration_url=self.coordinator.host_configuration.url, configuration_url=self.coordinator.host_configuration.url,
entry_type=DeviceEntryType.SERVICE, entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self.coordinator.config_entry.entry_id)}, identifiers={(DOMAIN, self.coordinator.config_entry.entry_id)},
manufacturer=DEFAULT_NAME, manufacturer=DEFAULT_NAME,
name=self.coordinator.config_entry.title, name=self.coordinator.config_entry.title,
sw_version=self.coordinator.system_version,
) )
if isinstance(self.coordinator, StatusDataUpdateCoordinator):
device_info[ATTR_SW_VERSION] = self.coordinator.data.version
return device_info

View file

@ -5,7 +5,7 @@ from abc import abstractmethod
from datetime import timedelta from datetime import timedelta
from typing import Generic, TypeVar, cast from typing import Generic, TypeVar, cast
from aiopyarr import RootFolder, exceptions from aiopyarr import RootFolder, SystemStatus, exceptions
from aiopyarr.models.host_configuration import PyArrHostConfiguration from aiopyarr.models.host_configuration import PyArrHostConfiguration
from aiopyarr.radarr_client import RadarrClient from aiopyarr.radarr_client import RadarrClient
@ -16,7 +16,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import DOMAIN, LOGGER from .const import DOMAIN, LOGGER
T = TypeVar("T", str, list[RootFolder], int) T = TypeVar("T", SystemStatus, list[RootFolder], int)
class RadarrDataUpdateCoordinator(DataUpdateCoordinator, Generic[T]): class RadarrDataUpdateCoordinator(DataUpdateCoordinator, Generic[T]):
@ -39,7 +39,6 @@ class RadarrDataUpdateCoordinator(DataUpdateCoordinator, Generic[T]):
) )
self.api_client = api_client self.api_client = api_client
self.host_configuration = host_configuration self.host_configuration = host_configuration
self.system_version: str | None = None
async def _async_update_data(self) -> T: async def _async_update_data(self) -> T:
"""Get the latest data from Radarr.""" """Get the latest data from Radarr."""
@ -62,9 +61,9 @@ class RadarrDataUpdateCoordinator(DataUpdateCoordinator, Generic[T]):
class StatusDataUpdateCoordinator(RadarrDataUpdateCoordinator): class StatusDataUpdateCoordinator(RadarrDataUpdateCoordinator):
"""Status update coordinator for Radarr.""" """Status update coordinator for Radarr."""
async def _fetch_data(self) -> str: async def _fetch_data(self) -> SystemStatus:
"""Fetch the data.""" """Fetch the data."""
return (await self.api_client.async_get_system_status()).version return await self.api_client.async_get_system_status()
class DiskSpaceDataUpdateCoordinator(RadarrDataUpdateCoordinator): class DiskSpaceDataUpdateCoordinator(RadarrDataUpdateCoordinator):

View file

@ -4,13 +4,15 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from copy import deepcopy from copy import deepcopy
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timezone
from typing import Generic from typing import Generic
from aiopyarr import RootFolder from aiopyarr import Diskspace, RootFolder
import voluptuous as vol import voluptuous as vol
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
) )
@ -28,6 +30,7 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
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 ConfigType, DiscoveryInfoType, StateType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
@ -36,11 +39,11 @@ from .const import DEFAULT_NAME, DOMAIN
from .coordinator import RadarrDataUpdateCoordinator, T from .coordinator import RadarrDataUpdateCoordinator, T
def get_space(coordinator: RadarrDataUpdateCoordinator, name: str) -> str: def get_space(data: list[Diskspace], name: str) -> str:
"""Get space.""" """Get space."""
space = [ space = [
mount.freeSpace / 1024 ** BYTE_SIZES.index(DATA_GIGABYTES) mount.freeSpace / 1024 ** BYTE_SIZES.index(DATA_GIGABYTES)
for mount in coordinator.data for mount in data
if name in mount.path if name in mount.path
] ]
return f"{space[0]:.2f}" return f"{space[0]:.2f}"
@ -61,7 +64,7 @@ def get_modified_description(
class RadarrSensorEntityDescriptionMixIn(Generic[T]): class RadarrSensorEntityDescriptionMixIn(Generic[T]):
"""Mixin for required keys.""" """Mixin for required keys."""
value: Callable[[RadarrDataUpdateCoordinator[T], str], str] value_fn: Callable[[T, str], str]
@dataclass @dataclass
@ -82,7 +85,7 @@ SENSOR_TYPES: dict[str, RadarrSensorEntityDescription] = {
name="Disk space", name="Disk space",
native_unit_of_measurement=DATA_GIGABYTES, native_unit_of_measurement=DATA_GIGABYTES,
icon="mdi:harddisk", icon="mdi:harddisk",
value=get_space, value_fn=get_space,
description_fn=get_modified_description, description_fn=get_modified_description,
), ),
"movie": RadarrSensorEntityDescription( "movie": RadarrSensorEntityDescription(
@ -91,7 +94,15 @@ SENSOR_TYPES: dict[str, RadarrSensorEntityDescription] = {
native_unit_of_measurement="Movies", native_unit_of_measurement="Movies",
icon="mdi:television", icon="mdi:television",
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
value=lambda coordinator, _: coordinator.data, value_fn=lambda data, _: data,
),
"status": RadarrSensorEntityDescription(
key="start_time",
name="Start time",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=lambda data, _: data.startTime.replace(tzinfo=timezone.utc),
), ),
} }
@ -182,4 +193,4 @@ class RadarrSensor(RadarrEntity, SensorEntity):
@property @property
def native_value(self) -> StateType: def native_value(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self.entity_description.value(self.coordinator, self.folder_name) return self.entity_description.value_fn(self.coordinator.data, self.folder_name)

View file

@ -1,33 +1,32 @@
"""The tests for Radarr sensor platform.""" """The tests for Radarr sensor platform."""
from datetime import timedelta from unittest.mock import AsyncMock
from homeassistant.components.radarr.sensor import SENSOR_TYPES from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
import homeassistant.util.dt as dt_util
from . import setup_integration from . import setup_integration
from tests.common import async_fire_time_changed
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
async def test_sensors(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker): async def test_sensors(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
entity_registry_enabled_by_default: AsyncMock,
):
"""Test for successfully setting up the Radarr platform.""" """Test for successfully setting up the Radarr platform."""
for description in SENSOR_TYPES.values():
description.entity_registry_enabled_default = True
await setup_integration(hass, aioclient_mock) await setup_integration(hass, aioclient_mock)
next_update = dt_util.utcnow() + timedelta(seconds=30)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
state = hass.states.get("sensor.radarr_disk_space_downloads") state = hass.states.get("sensor.radarr_disk_space_downloads")
assert state.state == "263.10" assert state.state == "263.10"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "GB" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "GB"
state = hass.states.get("sensor.radarr_movies") state = hass.states.get("sensor.radarr_movies")
assert state.state == "1" assert state.state == "1"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Movies" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Movies"
state = hass.states.get("sensor.radarr_start_time")
assert state.state == "2020-09-01T23:50:20+00:00"
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP
async def test_windows(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker): async def test_windows(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker):