diff --git a/homeassistant/components/onvif/binary_sensor.py b/homeassistant/components/onvif/binary_sensor.py index 2cc0f2c5d52..8da68bdf483 100644 --- a/homeassistant/components/onvif/binary_sensor.py +++ b/homeassistant/components/onvif/binary_sensor.py @@ -43,45 +43,19 @@ async def async_setup_entry( class ONVIFBinarySensor(ONVIFBaseEntity, BinarySensorEntity): """Representation of a binary ONVIF event.""" + _attr_should_poll = False + def __init__(self, uid, device): """Initialize the ONVIF binary sensor.""" - ONVIFBaseEntity.__init__(self, device) - BinarySensorEntity.__init__(self) + event = device.events.get_uid(uid) + self._attr_device_class = event.device_class + self._attr_entity_category = event.entity_category + self._attr_entity_registry_enabled_default = event.entity_enabled + self._attr_name = event.name + self._attr_is_on = event.value + self._attr_unique_id = uid - self.uid = uid - - @property - def is_on(self) -> bool: - """Return true if event is active.""" - return self.device.events.get_uid(self.uid).value - - @property - def name(self) -> str: - """Return the name of the event.""" - return self.device.events.get_uid(self.uid).name - - @property - def device_class(self) -> str | None: - """Return the class of this device, from component DEVICE_CLASSES.""" - return self.device.events.get_uid(self.uid).device_class - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self.uid - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - return self.device.events.get_uid(self.uid).entity_enabled - - @property - def should_poll(self) -> bool: - """Return True if entity has to be polled for state. - - False if entity pushes its state to HA. - """ - return False + super().__init__(device) async def async_added_to_hass(self): """Connect to dispatcher listening for entity data notifications.""" diff --git a/homeassistant/components/onvif/diagnostics.py b/homeassistant/components/onvif/diagnostics.py new file mode 100644 index 00000000000..eb818f53a3a --- /dev/null +++ b/homeassistant/components/onvif/diagnostics.py @@ -0,0 +1,32 @@ +"""Diagnostics support for ONVIF.""" +from __future__ import annotations + +from dataclasses import asdict +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .device import ONVIFDevice + +REDACT_CONFIG = {CONF_HOST, CONF_PASSWORD, CONF_USERNAME} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + device: ONVIFDevice = hass.data[DOMAIN][entry.unique_id] + data: dict[str, Any] = {} + + data["config"] = async_redact_data(entry.as_dict(), REDACT_CONFIG) + data["device"] = { + "info": asdict(device.info), + "capabilities": asdict(device.capabilities), + "profiles": [asdict(profile) for profile in device.profiles], + } + + return data diff --git a/homeassistant/components/onvif/models.py b/homeassistant/components/onvif/models.py index feda891f772..dea613e3c1c 100644 --- a/homeassistant/components/onvif/models.py +++ b/homeassistant/components/onvif/models.py @@ -4,6 +4,8 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any +from homeassistant.helpers.entity import EntityCategory + @dataclass class DeviceInfo: @@ -72,4 +74,5 @@ class Event: device_class: str = None unit_of_measurement: str = None value: Any = None + entity_category: EntityCategory | None = None entity_enabled: bool = True diff --git a/homeassistant/components/onvif/parsers.py b/homeassistant/components/onvif/parsers.py index b518dbbb451..b20126235c7 100644 --- a/homeassistant/components/onvif/parsers.py +++ b/homeassistant/components/onvif/parsers.py @@ -2,6 +2,7 @@ from collections.abc import Callable, Coroutine from typing import Any +from homeassistant.helpers.entity import EntityCategory from homeassistant.util import dt as dt_util from homeassistant.util.decorator import Registry @@ -49,6 +50,7 @@ async def async_parse_image_too_blurry(uid: str, msg) -> Event: "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -72,6 +74,7 @@ async def async_parse_image_too_dark(uid: str, msg) -> Event: "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -95,6 +98,7 @@ async def async_parse_image_too_bright(uid: str, msg) -> Event: "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -274,6 +278,7 @@ async def async_parse_tamper_detector(uid: str, msg) -> Event: "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -295,6 +300,7 @@ async def async_parse_storage_failure(uid: str, msg) -> Event: "problem", None, msg.Message._value_1.Data.SimpleItem[0].Value == "true", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -319,6 +325,7 @@ async def async_parse_processor_usage(uid: str, msg) -> Event: None, "percent", int(usage), + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None @@ -341,6 +348,7 @@ async def async_parse_last_reboot(uid: str, msg) -> Event: dt_util.as_local( dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value) ), + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError, ValueError): return None @@ -363,6 +371,7 @@ async def async_parse_last_reset(uid: str, msg) -> Event: dt_util.as_local( dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value) ), + EntityCategory.DIAGNOSTIC, entity_enabled=False, ) except (AttributeError, KeyError, ValueError): @@ -386,6 +395,7 @@ async def async_parse_last_clock_sync(uid: str, msg) -> Event: dt_util.as_local( dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value) ), + EntityCategory.DIAGNOSTIC, entity_enabled=False, ) except (AttributeError, KeyError, ValueError): @@ -409,6 +419,7 @@ async def async_parse_jobstate(uid: str, msg) -> Event: None, None, msg.Message._value_1.Data.SimpleItem[0].Value == "Active", + EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): return None diff --git a/homeassistant/components/onvif/sensor.py b/homeassistant/components/onvif/sensor.py index 6b219a8887d..b4f1833a74d 100644 --- a/homeassistant/components/onvif/sensor.py +++ b/homeassistant/components/onvif/sensor.py @@ -43,50 +43,21 @@ async def async_setup_entry( class ONVIFSensor(ONVIFBaseEntity, SensorEntity): """Representation of a ONVIF sensor event.""" + _attr_should_poll = False + def __init__(self, uid, device): """Initialize the ONVIF binary sensor.""" - self.uid = uid + event = device.events.get_uid(uid) + self._attr_device_class = event.device_class + self._attr_entity_category = event.entity_category + self._attr_entity_registry_enabled_default = event.entity_enabled + self._attr_name = event.name + self._attr_native_unit_of_measurement = event.unit_of_measurement + self._attr_native_value = event.value + self._attr_unique_id = uid super().__init__(device) - @property - def native_value(self) -> None | str | int | float: - """Return the state of the entity.""" - return self.device.events.get_uid(self.uid).value - - @property - def name(self): - """Return the name of the event.""" - return self.device.events.get_uid(self.uid).name - - @property - def device_class(self) -> str | None: - """Return the class of this device, from component DEVICE_CLASSES.""" - return self.device.events.get_uid(self.uid).device_class - - @property - def native_unit_of_measurement(self) -> str | None: - """Return the unit of measurement of this entity, if any.""" - return self.device.events.get_uid(self.uid).unit_of_measurement - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self.uid - - @property - def entity_registry_enabled_default(self) -> bool: - """Return if the entity should be enabled when first added to the entity registry.""" - return self.device.events.get_uid(self.uid).entity_enabled - - @property - def should_poll(self) -> bool: - """Return True if entity has to be polled for state. - - False if entity pushes its state to HA. - """ - return False - async def async_added_to_hass(self): """Connect to dispatcher listening for entity data notifications.""" self.async_on_remove( diff --git a/tests/components/onvif/__init__.py b/tests/components/onvif/__init__.py index 28413ae4d05..5bfc2ca53fd 100644 --- a/tests/components/onvif/__init__.py +++ b/tests/components/onvif/__init__.py @@ -6,7 +6,7 @@ from zeep.exceptions import Fault from homeassistant import config_entries from homeassistant.components.onvif import config_flow from homeassistant.components.onvif.const import CONF_SNAPSHOT_AUTH -from homeassistant.components.onvif.models import DeviceInfo +from homeassistant.components.onvif.models import Capabilities, DeviceInfo from homeassistant.const import HTTP_DIGEST_AUTHENTICATION from tests.common import MockConfigEntry @@ -95,6 +95,8 @@ def setup_mock_device(mock_device): SERIAL_NUMBER, MAC, ) + mock_device.capabilities = Capabilities() + mock_device.profiles = [] def mock_constructor(hass, config): """Fake the controller constructor.""" diff --git a/tests/components/onvif/test_diagnostics.py b/tests/components/onvif/test_diagnostics.py new file mode 100644 index 00000000000..ae22838b8c5 --- /dev/null +++ b/tests/components/onvif/test_diagnostics.py @@ -0,0 +1,54 @@ +"""Test ONVIF diagnostics.""" + +from . import ( + FIRMWARE_VERSION, + MAC, + MANUFACTURER, + MODEL, + SERIAL_NUMBER, + setup_onvif_integration, +) + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_diagnostics(hass, hass_client): + """Test generating diagnostics for a config entry.""" + + entry, _, _ = await setup_onvif_integration(hass) + + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + + assert diag == { + "config": { + "entry_id": "1", + "version": 1, + "domain": "onvif", + "title": "Mock Title", + "data": { + "name": "TestCamera", + "host": "**REDACTED**", + "port": 80, + "username": "**REDACTED**", + "password": "**REDACTED**", + "snapshot_auth": "digest", + }, + "options": {"extra_arguments": "-pred 1", "rtsp_transport": "tcp"}, + "pref_disable_new_entities": False, + "pref_disable_polling": False, + "source": "user", + "unique_id": "aa:bb:cc:dd:ee", + "disabled_by": None, + }, + "device": { + "info": { + "manufacturer": MANUFACTURER, + "model": MODEL, + "fw_version": FIRMWARE_VERSION, + "serial_number": SERIAL_NUMBER, + "mac": MAC, + }, + "capabilities": {"snapshot": False, "events": False, "ptz": False}, + "profiles": [], + }, + }