From 8447bbf5f0f68a5bb309ac976120f018a1a6d252 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 11 Nov 2021 07:22:18 +0100 Subject: [PATCH] Add binary sensor platform to RDW Vehicle information (#59253) --- homeassistant/components/rdw/__init__.py | 3 +- homeassistant/components/rdw/binary_sensor.py | 101 ++++++++++++++++++ tests/components/rdw/test_binary_sensor.py | 51 +++++++++ 3 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/rdw/binary_sensor.py create mode 100644 tests/components/rdw/test_binary_sensor.py diff --git a/homeassistant/components/rdw/__init__.py b/homeassistant/components/rdw/__init__.py index 32f5c81e86a..bde83fffad3 100644 --- a/homeassistant/components/rdw/__init__.py +++ b/homeassistant/components/rdw/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations from vehicle import RDW, Vehicle +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -11,7 +12,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import CONF_LICENSE_PLATE, DOMAIN, LOGGER, SCAN_INTERVAL -PLATFORMS = (SENSOR_DOMAIN,) +PLATFORMS = (BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/rdw/binary_sensor.py b/homeassistant/components/rdw/binary_sensor.py new file mode 100644 index 00000000000..626afcf2b0a --- /dev/null +++ b/homeassistant/components/rdw/binary_sensor.py @@ -0,0 +1,101 @@ +"""Support for RDW binary sensors.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Callable + +from vehicle import Vehicle + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_PROBLEM, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) + +from .const import DOMAIN, ENTRY_TYPE_SERVICE + + +@dataclass +class RDWBinarySensorEntityDescriptionMixin: + """Mixin for required keys.""" + + is_on_fn: Callable[[Vehicle], bool | None] + + +@dataclass +class RDWBinarySensorEntityDescription( + BinarySensorEntityDescription, RDWBinarySensorEntityDescriptionMixin +): + """Describes RDW binary sensor entity.""" + + +BINARY_SENSORS: tuple[RDWBinarySensorEntityDescription, ...] = ( + RDWBinarySensorEntityDescription( + key="liability_insured", + name="Liability Insured", + icon="mdi:shield-car", + is_on_fn=lambda vehicle: vehicle.liability_insured, + ), + RDWBinarySensorEntityDescription( + key="pending_recall", + name="Pending Recall", + device_class=DEVICE_CLASS_PROBLEM, + is_on_fn=lambda vehicle: vehicle.pending_recall, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up RDW binary sensors based on a config entry.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + RDWBinarySensorEntity( + coordinator=coordinator, + description=description, + ) + for description in BINARY_SENSORS + if description.is_on_fn(coordinator.data) is not None + ) + + +class RDWBinarySensorEntity(CoordinatorEntity, BinarySensorEntity): + """Defines an RDW binary sensor.""" + + entity_description: RDWBinarySensorEntityDescription + + def __init__( + self, + *, + coordinator: DataUpdateCoordinator, + description: RDWBinarySensorEntityDescription, + ) -> None: + """Initialize RDW binary sensor.""" + super().__init__(coordinator=coordinator) + self.entity_description = description + self._attr_unique_id = f"{coordinator.data.license_plate}_{description.key}" + + self._attr_device_info = DeviceInfo( + entry_type=ENTRY_TYPE_SERVICE, + identifiers={(DOMAIN, coordinator.data.license_plate)}, + manufacturer=coordinator.data.brand, + name=f"{coordinator.data.brand}: {coordinator.data.license_plate}", + model=coordinator.data.model, + configuration_url=f"https://ovi.rdw.nl/default.aspx?kenteken={coordinator.data.license_plate}", + ) + + @property + def is_on(self) -> bool: + """Return the state of the sensor.""" + return bool(self.entity_description.is_on_fn(self.coordinator.data)) diff --git a/tests/components/rdw/test_binary_sensor.py b/tests/components/rdw/test_binary_sensor.py new file mode 100644 index 00000000000..39fc981424d --- /dev/null +++ b/tests/components/rdw/test_binary_sensor.py @@ -0,0 +1,51 @@ +"""Tests for the sensors provided by the RDW integration.""" +from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM +from homeassistant.components.rdw.const import DOMAIN, ENTRY_TYPE_SERVICE +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from tests.common import MockConfigEntry + + +async def test_vehicle_binary_sensors( + hass: HomeAssistant, + init_integration: MockConfigEntry, +) -> None: + """Test the RDW vehicle binary sensors.""" + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) + + state = hass.states.get("binary_sensor.liability_insured") + entry = entity_registry.async_get("binary_sensor.liability_insured") + assert entry + assert state + assert entry.unique_id == "11ZKZ3_liability_insured" + assert state.state == "off" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Liability Insured" + assert state.attributes.get(ATTR_ICON) == "mdi:shield-car" + assert ATTR_DEVICE_CLASS not in state.attributes + + state = hass.states.get("binary_sensor.pending_recall") + entry = entity_registry.async_get("binary_sensor.pending_recall") + assert entry + assert state + assert entry.unique_id == "11ZKZ3_pending_recall" + assert state.state == "off" + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Pending Recall" + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_PROBLEM + assert ATTR_ICON not in state.attributes + + assert entry.device_id + device_entry = device_registry.async_get(entry.device_id) + assert device_entry + assert device_entry.identifiers == {(DOMAIN, "11ZKZ3")} + assert device_entry.manufacturer == "Skoda" + assert device_entry.name == "Skoda: 11ZKZ3" + assert device_entry.entry_type == ENTRY_TYPE_SERVICE + assert device_entry.model == "Citigo" + assert ( + device_entry.configuration_url + == "https://ovi.rdw.nl/default.aspx?kenteken=11ZKZ3" + ) + assert not device_entry.sw_version