diff --git a/homeassistant/components/sfr_box/binary_sensor.py b/homeassistant/components/sfr_box/binary_sensor.py new file mode 100644 index 00000000000..88066df15d1 --- /dev/null +++ b/homeassistant/components/sfr_box/binary_sensor.py @@ -0,0 +1,92 @@ +"""SFR Box sensor platform.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Generic, TypeVar + +from sfrbox_api.models import DslInfo, SystemInfo + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import SFRDataUpdateCoordinator +from .models import DomainData + +_T = TypeVar("_T") + + +@dataclass +class SFRBoxBinarySensorMixin(Generic[_T]): + """Mixin for SFR Box sensors.""" + + value_fn: Callable[[_T], bool | None] + + +@dataclass +class SFRBoxBinarySensorEntityDescription( + BinarySensorEntityDescription, SFRBoxBinarySensorMixin[_T] +): + """Description for SFR Box binary sensors.""" + + +DSL_SENSOR_TYPES: tuple[SFRBoxBinarySensorEntityDescription[DslInfo], ...] = ( + SFRBoxBinarySensorEntityDescription[DslInfo]( + key="status", + name="Status", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda x: x.status == "up", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the sensors.""" + data: DomainData = hass.data[DOMAIN][entry.entry_id] + + entities = [ + SFRBoxBinarySensor(data.dsl, description, data.system.data) + for description in DSL_SENSOR_TYPES + ] + + async_add_entities(entities) + + +class SFRBoxBinarySensor( + CoordinatorEntity[SFRDataUpdateCoordinator[_T]], BinarySensorEntity +): + """SFR Box sensor.""" + + entity_description: SFRBoxBinarySensorEntityDescription[_T] + _attr_has_entity_name = True + + def __init__( + self, + coordinator: SFRDataUpdateCoordinator[_T], + description: SFRBoxBinarySensorEntityDescription, + system_info: SystemInfo, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = ( + f"{system_info.mac_addr}_{coordinator.name}_{description.key}" + ) + self._attr_device_info = {"identifiers": {(DOMAIN, system_info.mac_addr)}} + + @property + def is_on(self) -> bool | None: + """Return the native value of the device.""" + return self.entity_description.value_fn(self.coordinator.data) diff --git a/homeassistant/components/sfr_box/const.py b/homeassistant/components/sfr_box/const.py index 2fd21571f34..bc7647bcc95 100644 --- a/homeassistant/components/sfr_box/const.py +++ b/homeassistant/components/sfr_box/const.py @@ -5,4 +5,4 @@ DEFAULT_HOST = "192.168.0.1" DOMAIN = "sfr_box" -PLATFORMS = [Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] diff --git a/tests/components/sfr_box/const.py b/tests/components/sfr_box/const.py index 9682e7002b5..b3ea9b97538 100644 --- a/tests/components/sfr_box/const.py +++ b/tests/components/sfr_box/const.py @@ -1,4 +1,5 @@ """Constants for SFR Box tests.""" +from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.sensor import ( ATTR_OPTIONS, ATTR_STATE_CLASS, @@ -16,6 +17,7 @@ from homeassistant.const import ( ATTR_SW_VERSION, ATTR_UNIT_OF_MEASUREMENT, SIGNAL_STRENGTH_DECIBELS, + STATE_ON, Platform, UnitOfDataRate, UnitOfElectricPotential, @@ -38,6 +40,14 @@ EXPECTED_ENTITIES = { ATTR_NAME: "SFR Box", ATTR_SW_VERSION: "NB6VAC-MAIN-R4.0.44k", }, + Platform.BINARY_SENSOR: [ + { + ATTR_DEVICE_CLASS: BinarySensorDeviceClass.CONNECTIVITY, + ATTR_ENTITY_ID: "binary_sensor.sfr_box_status", + ATTR_STATE: STATE_ON, + ATTR_UNIQUE_ID: "e4:5d:51:00:11:22_dsl_status", + }, + ], Platform.SENSOR: [ { ATTR_DEFAULT_DISABLED: True, diff --git a/tests/components/sfr_box/test_binary_sensor.py b/tests/components/sfr_box/test_binary_sensor.py new file mode 100644 index 00000000000..7cc60c4c537 --- /dev/null +++ b/tests/components/sfr_box/test_binary_sensor.py @@ -0,0 +1,39 @@ +"""Test the SFR Box sensors.""" +from collections.abc import Generator +from unittest.mock import patch + +import pytest + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +from . import check_device_registry, check_entities +from .const import EXPECTED_ENTITIES + +from tests.common import mock_device_registry, mock_registry + +pytestmark = pytest.mark.usefixtures("system_get_info", "dsl_get_info") + + +@pytest.fixture(autouse=True) +def override_platforms() -> Generator[None, None, None]: + """Override PLATFORMS.""" + with patch("homeassistant.components.sfr_box.PLATFORMS", [Platform.BINARY_SENSOR]): + yield + + +async def test_binary_sensors(hass: HomeAssistant, config_entry: ConfigEntry) -> None: + """Test for SFR Box binary sensors.""" + entity_registry = mock_registry(hass) + device_registry = mock_device_registry(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + check_device_registry(device_registry, EXPECTED_ENTITIES["expected_device"]) + + expected_entities = EXPECTED_ENTITIES[Platform.BINARY_SENSOR] + assert len(entity_registry.entities) == len(expected_entities) + + check_entities(hass, entity_registry, expected_entities)