diff --git a/.coveragerc b/.coveragerc index 2e10d1be257..912c472de3e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -991,6 +991,7 @@ omit = homeassistant/components/reolink/light.py homeassistant/components/reolink/number.py homeassistant/components/reolink/select.py + homeassistant/components/reolink/sensor.py homeassistant/components/reolink/siren.py homeassistant/components/reolink/switch.py homeassistant/components/reolink/update.py diff --git a/homeassistant/components/reolink/__init__.py b/homeassistant/components/reolink/__init__.py index 923df261d84..2de87659919 100644 --- a/homeassistant/components/reolink/__init__.py +++ b/homeassistant/components/reolink/__init__.py @@ -30,6 +30,7 @@ PLATFORMS = [ Platform.LIGHT, Platform.NUMBER, Platform.SELECT, + Platform.SENSOR, Platform.SIREN, Platform.SWITCH, Platform.UPDATE, diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index 81fbda63fef..dac02b91315 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -432,6 +432,15 @@ class ReolinkHost: webhook.async_unregister(self._hass, self.webhook_id) self.webhook_id = None + @property + def event_connection(self) -> str: + """Return the event connection type.""" + if self._webhook_reachable: + return "onvif_push" + if self._long_poll_received: + return "onvif_long_poll" + return "fast_poll" + async def _async_long_polling(self, *_) -> None: """Use ONVIF long polling to immediately receive events.""" # This task will be cancelled once _async_stop_long_polling is called diff --git a/homeassistant/components/reolink/sensor.py b/homeassistant/components/reolink/sensor.py new file mode 100644 index 00000000000..42758dc9929 --- /dev/null +++ b/homeassistant/components/reolink/sensor.py @@ -0,0 +1,121 @@ +"""Component providing support for Reolink sensors.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from datetime import date, datetime +from decimal import Decimal + +from reolink_aio.api import Host + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType + +from . import ReolinkData +from .const import DOMAIN +from .entity import ReolinkHostCoordinatorEntity + + +@dataclass +class ReolinkHostSensorEntityDescriptionMixin: + """Mixin values for Reolink host sensor entities.""" + + value: Callable[[Host], bool] + + +@dataclass +class ReolinkHostSensorEntityDescription( + SensorEntityDescription, ReolinkHostSensorEntityDescriptionMixin +): + """A class that describes host sensor entities.""" + + supported: Callable[[Host], bool] = lambda host: True + + +HOST_SENSORS = ( + ReolinkHostSensorEntityDescription( + key="wifi_signal", + translation_key="wifi_signal", + icon="mdi:wifi", + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + value=lambda api: api.wifi_signal, + supported=lambda api: api.supported(None, "wifi") and api.wifi_connection, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up a Reolink IP Camera.""" + reolink_data: ReolinkData = hass.data[DOMAIN][config_entry.entry_id] + + entities: list[ReolinkHostSensorEntity | EventConnectionSensorEntity] = [ + ReolinkHostSensorEntity(reolink_data, entity_description) + for entity_description in HOST_SENSORS + if entity_description.supported(reolink_data.host.api) + ] + entities.append(EventConnectionSensorEntity(reolink_data)) + async_add_entities(entities) + + +class ReolinkHostSensorEntity(ReolinkHostCoordinatorEntity, SensorEntity): + """Base sensor class for Reolink host sensors.""" + + entity_description: ReolinkHostSensorEntityDescription + + def __init__( + self, + reolink_data: ReolinkData, + entity_description: ReolinkHostSensorEntityDescription, + ) -> None: + """Initialize Reolink binary sensor.""" + super().__init__(reolink_data) + self.entity_description = entity_description + + self._attr_unique_id = f"{self._host.unique_id}_{entity_description.key}" + + @property + def native_value(self) -> StateType | date | datetime | Decimal: + """Return the value reported by the sensor.""" + return self.entity_description.value(self._host.api) + + +class EventConnectionSensorEntity(ReolinkHostCoordinatorEntity, SensorEntity): + """Reolink Event connection sensor.""" + + def __init__( + self, + reolink_data: ReolinkData, + ) -> None: + """Initialize Reolink binary sensor.""" + super().__init__(reolink_data) + self.entity_description = SensorEntityDescription( + key="event_connection", + translation_key="event_connection", + icon="mdi:swap-horizontal", + device_class=SensorDeviceClass.ENUM, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + options=["onvif_push", "onvif_long_poll", "fast_poll"], + ) + + self._attr_unique_id = f"{self._host.unique_id}_{self.entity_description.key}" + + @property + def native_value(self) -> str: + """Return the value reported by the sensor.""" + return self._host.event_connection diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index 53f2e57b97b..c0c2094eeb9 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -93,6 +93,19 @@ "alwaysonatnight": "Auto & always on at night" } } + }, + "sensor": { + "event_connection": { + "name": "Event connection", + "state": { + "onvif_push": "ONVIF push", + "onvif_long_poll": "ONVIF long poll", + "fast_poll": "Fast poll" + } + }, + "wifi_signal": { + "name": "Wi-Fi signal" + } } } }