Add system diagnostic sensors to SFR Box (#85184)

* Add system diagnostic sensor

* Add tests
This commit is contained in:
epenet 2023-01-12 09:29:12 +01:00 committed by GitHub
parent b0d4b73874
commit 679e971131
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 26 deletions

View File

@ -1,6 +1,8 @@
"""SFR Box sensor platform."""
from collections.abc import Callable
from collections.abc import Callable, Iterable
from dataclasses import dataclass
from itertools import chain
from typing import Generic, TypeVar
from sfrbox_api.models import DslInfo, SystemInfo
@ -11,7 +13,12 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import SIGNAL_STRENGTH_DECIBELS, UnitOfDataRate
from homeassistant.const import (
SIGNAL_STRENGTH_DECIBELS,
UnitOfDataRate,
UnitOfElectricPotential,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -22,42 +29,44 @@ from .const import DOMAIN
from .coordinator import SFRDataUpdateCoordinator
from .models import DomainData
_T = TypeVar("_T")
@dataclass
class SFRBoxSensorMixin:
class SFRBoxSensorMixin(Generic[_T]):
"""Mixin for SFR Box sensors."""
value_fn: Callable[[DslInfo], StateType]
value_fn: Callable[[_T], StateType]
@dataclass
class SFRBoxSensorEntityDescription(SensorEntityDescription, SFRBoxSensorMixin):
class SFRBoxSensorEntityDescription(SensorEntityDescription, SFRBoxSensorMixin[_T]):
"""Description for SFR Box sensors."""
SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription, ...] = (
SFRBoxSensorEntityDescription(
DSL_SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription[DslInfo], ...] = (
SFRBoxSensorEntityDescription[DslInfo](
key="linemode",
name="Line mode",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=lambda x: x.linemode,
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="counter",
name="Counter",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=lambda x: x.counter,
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="crc",
name="CRC",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=lambda x: x.crc,
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="noise_down",
name="Noise down",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
@ -67,7 +76,7 @@ SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda x: x.noise_down,
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="noise_up",
name="Noise up",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
@ -77,7 +86,7 @@ SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda x: x.noise_up,
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="attenuation_down",
name="Attenuation down",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
@ -87,7 +96,7 @@ SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda x: x.attenuation_down,
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="attenuation_up",
name="Attenuation up",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
@ -97,7 +106,7 @@ SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda x: x.attenuation_up,
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="rate_down",
name="Rate down",
device_class=SensorDeviceClass.DATA_RATE,
@ -105,7 +114,7 @@ SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda x: x.rate_down,
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="rate_up",
name="Rate up",
device_class=SensorDeviceClass.DATA_RATE,
@ -113,7 +122,7 @@ SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda x: x.rate_up,
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="line_status",
name="Line status",
device_class=SensorDeviceClass.ENUM,
@ -130,7 +139,7 @@ SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription, ...] = (
translation_key="line_status",
value_fn=lambda x: x.line_status.lower().replace(" ", "_"),
),
SFRBoxSensorEntityDescription(
SFRBoxSensorEntityDescription[DslInfo](
key="training",
name="Training",
device_class=SensorDeviceClass.ENUM,
@ -152,6 +161,40 @@ SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription, ...] = (
value_fn=lambda x: x.training.lower().replace(" ", "_").replace(".", "_"),
),
)
SYSTEM_SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription[SystemInfo], ...] = (
SFRBoxSensorEntityDescription[SystemInfo](
key="net_infra",
name="Network infrastructure",
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
options=[
"adsl",
"ftth",
"gprs",
],
translation_key="net_infra",
value_fn=lambda x: x.net_infra,
),
SFRBoxSensorEntityDescription[SystemInfo](
key="alimvoltage",
name="Voltage",
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
value_fn=lambda x: x.alimvoltage,
),
SFRBoxSensorEntityDescription[SystemInfo](
key="temperature",
name="Temperature",
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
value_fn=lambda x: x.temperature / 1000,
),
)
async def async_setup_entry(
@ -160,29 +203,38 @@ async def async_setup_entry(
"""Set up the sensors."""
data: DomainData = hass.data[DOMAIN][entry.entry_id]
entities = [
SFRBoxSensor(data.dsl, description, data.system.data)
for description in SENSOR_TYPES
]
entities: Iterable[SFRBoxSensor] = chain(
(
SFRBoxSensor(data.dsl, description, data.system.data)
for description in DSL_SENSOR_TYPES
),
(
SFRBoxSensor(data.system, description, data.system.data)
for description in SYSTEM_SENSOR_TYPES
),
)
async_add_entities(entities)
class SFRBoxSensor(CoordinatorEntity[SFRDataUpdateCoordinator[DslInfo]], SensorEntity):
class SFRBoxSensor(CoordinatorEntity[SFRDataUpdateCoordinator[_T]], SensorEntity):
"""SFR Box sensor."""
entity_description: SFRBoxSensorEntityDescription
entity_description: SFRBoxSensorEntityDescription[_T]
_attr_has_entity_name = True
def __init__(
self,
coordinator: SFRDataUpdateCoordinator[DslInfo],
coordinator: SFRDataUpdateCoordinator[_T],
description: SFRBoxSensorEntityDescription,
system_info: SystemInfo,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{system_info.mac_addr}_dsl_{description.key}"
self._attr_unique_id = (
f"{system_info.mac_addr}_{coordinator.name}_{description.key}"
)
self._attr_device_info = {"identifiers": {(DOMAIN, system_info.mac_addr)}}
@property

View File

@ -26,6 +26,13 @@
"unknown": "Unknown"
}
},
"net_infra": {
"state": {
"adsl": "ADSL",
"ftth": "FTTH",
"gprs": "GPRS"
}
},
"training": {
"state": {
"idle": "Idle",

View File

@ -27,6 +27,13 @@
"unknown": "Unknown"
}
},
"net_infra": {
"state": {
"adsl": "ADSL",
"ftth": "FTTH",
"gprs": "GPRS"
}
},
"training": {
"state": {
"g_922_channel_analysis": "G.922 Channel Analysis",

View File

@ -1,6 +1,6 @@
"""Constants for SFR Box tests."""
from homeassistant.components.select.const import ATTR_OPTIONS
from homeassistant.components.sensor import (
ATTR_OPTIONS,
ATTR_STATE_CLASS,
SensorDeviceClass,
SensorStateClass,
@ -18,6 +18,8 @@ from homeassistant.const import (
SIGNAL_STRENGTH_DECIBELS,
Platform,
UnitOfDataRate,
UnitOfElectricPotential,
UnitOfTemperature,
)
ATTR_DEFAULT_DISABLED = "default_disabled"
@ -37,6 +39,30 @@ EXPECTED_ENTITIES = {
ATTR_SW_VERSION: "NB6VAC-MAIN-R4.0.44k",
},
Platform.SENSOR: [
{
ATTR_DEFAULT_DISABLED: True,
ATTR_DEVICE_CLASS: SensorDeviceClass.ENUM,
ATTR_ENTITY_ID: "sensor.sfr_box_network_infrastructure",
ATTR_OPTIONS: ["adsl", "ftth", "gprs"],
ATTR_STATE: "adsl",
ATTR_UNIQUE_ID: "e4:5d:51:00:11:22_system_net_infra",
},
{
ATTR_DEFAULT_DISABLED: True,
ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE,
ATTR_ENTITY_ID: "sensor.sfr_box_temperature",
ATTR_STATE: "27.56",
ATTR_UNIQUE_ID: "e4:5d:51:00:11:22_system_temperature",
ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS,
},
{
ATTR_DEFAULT_DISABLED: True,
ATTR_DEVICE_CLASS: SensorDeviceClass.VOLTAGE,
ATTR_ENTITY_ID: "sensor.sfr_box_voltage",
ATTR_STATE: "12251",
ATTR_UNIQUE_ID: "e4:5d:51:00:11:22_system_alimvoltage",
ATTR_UNIT_OF_MEASUREMENT: UnitOfElectricPotential.MILLIVOLT,
},
{
ATTR_DEFAULT_DISABLED: True,
ATTR_ENTITY_ID: "sensor.sfr_box_line_mode",

View File

@ -33,6 +33,7 @@ def _check_and_enable_disabled_entities(
if expected_entity.get(ATTR_DEFAULT_DISABLED):
entity_id = expected_entity[ATTR_ENTITY_ID]
registry_entry = entity_registry.entities.get(entity_id)
assert registry_entry, f"Registry entry not found for {entity_id}"
assert registry_entry.disabled
assert registry_entry.disabled_by is RegistryEntryDisabler.INTEGRATION
entity_registry.async_update_entity(entity_id, **{"disabled_by": None})