mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 18:27:09 +00:00
Add diagnostics to ONVIF (#69708)
This commit is contained in:
parent
d08b751a85
commit
22db21b9d4
@ -43,45 +43,19 @@ async def async_setup_entry(
|
|||||||
class ONVIFBinarySensor(ONVIFBaseEntity, BinarySensorEntity):
|
class ONVIFBinarySensor(ONVIFBaseEntity, BinarySensorEntity):
|
||||||
"""Representation of a binary ONVIF event."""
|
"""Representation of a binary ONVIF event."""
|
||||||
|
|
||||||
|
_attr_should_poll = False
|
||||||
|
|
||||||
def __init__(self, uid, device):
|
def __init__(self, uid, device):
|
||||||
"""Initialize the ONVIF binary sensor."""
|
"""Initialize the ONVIF binary sensor."""
|
||||||
ONVIFBaseEntity.__init__(self, device)
|
event = device.events.get_uid(uid)
|
||||||
BinarySensorEntity.__init__(self)
|
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
|
super().__init__(device)
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Connect to dispatcher listening for entity data notifications."""
|
"""Connect to dispatcher listening for entity data notifications."""
|
||||||
|
32
homeassistant/components/onvif/diagnostics.py
Normal file
32
homeassistant/components/onvif/diagnostics.py
Normal file
@ -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
|
@ -4,6 +4,8 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DeviceInfo:
|
class DeviceInfo:
|
||||||
@ -72,4 +74,5 @@ class Event:
|
|||||||
device_class: str = None
|
device_class: str = None
|
||||||
unit_of_measurement: str = None
|
unit_of_measurement: str = None
|
||||||
value: Any = None
|
value: Any = None
|
||||||
|
entity_category: EntityCategory | None = None
|
||||||
entity_enabled: bool = True
|
entity_enabled: bool = True
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from collections.abc import Callable, Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ async def async_parse_image_too_blurry(uid: str, msg) -> Event:
|
|||||||
"problem",
|
"problem",
|
||||||
None,
|
None,
|
||||||
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
return None
|
return None
|
||||||
@ -72,6 +74,7 @@ async def async_parse_image_too_dark(uid: str, msg) -> Event:
|
|||||||
"problem",
|
"problem",
|
||||||
None,
|
None,
|
||||||
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
return None
|
return None
|
||||||
@ -95,6 +98,7 @@ async def async_parse_image_too_bright(uid: str, msg) -> Event:
|
|||||||
"problem",
|
"problem",
|
||||||
None,
|
None,
|
||||||
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
return None
|
return None
|
||||||
@ -274,6 +278,7 @@ async def async_parse_tamper_detector(uid: str, msg) -> Event:
|
|||||||
"problem",
|
"problem",
|
||||||
None,
|
None,
|
||||||
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
return None
|
return None
|
||||||
@ -295,6 +300,7 @@ async def async_parse_storage_failure(uid: str, msg) -> Event:
|
|||||||
"problem",
|
"problem",
|
||||||
None,
|
None,
|
||||||
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
msg.Message._value_1.Data.SimpleItem[0].Value == "true",
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
return None
|
return None
|
||||||
@ -319,6 +325,7 @@ async def async_parse_processor_usage(uid: str, msg) -> Event:
|
|||||||
None,
|
None,
|
||||||
"percent",
|
"percent",
|
||||||
int(usage),
|
int(usage),
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
return None
|
return None
|
||||||
@ -341,6 +348,7 @@ async def async_parse_last_reboot(uid: str, msg) -> Event:
|
|||||||
dt_util.as_local(
|
dt_util.as_local(
|
||||||
dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value)
|
dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value)
|
||||||
),
|
),
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError, ValueError):
|
except (AttributeError, KeyError, ValueError):
|
||||||
return None
|
return None
|
||||||
@ -363,6 +371,7 @@ async def async_parse_last_reset(uid: str, msg) -> Event:
|
|||||||
dt_util.as_local(
|
dt_util.as_local(
|
||||||
dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value)
|
dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value)
|
||||||
),
|
),
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
entity_enabled=False,
|
entity_enabled=False,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError, ValueError):
|
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.as_local(
|
||||||
dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value)
|
dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value)
|
||||||
),
|
),
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
entity_enabled=False,
|
entity_enabled=False,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError, ValueError):
|
except (AttributeError, KeyError, ValueError):
|
||||||
@ -409,6 +419,7 @@ async def async_parse_jobstate(uid: str, msg) -> Event:
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
msg.Message._value_1.Data.SimpleItem[0].Value == "Active",
|
msg.Message._value_1.Data.SimpleItem[0].Value == "Active",
|
||||||
|
EntityCategory.DIAGNOSTIC,
|
||||||
)
|
)
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
return None
|
return None
|
||||||
|
@ -43,50 +43,21 @@ async def async_setup_entry(
|
|||||||
class ONVIFSensor(ONVIFBaseEntity, SensorEntity):
|
class ONVIFSensor(ONVIFBaseEntity, SensorEntity):
|
||||||
"""Representation of a ONVIF sensor event."""
|
"""Representation of a ONVIF sensor event."""
|
||||||
|
|
||||||
|
_attr_should_poll = False
|
||||||
|
|
||||||
def __init__(self, uid, device):
|
def __init__(self, uid, device):
|
||||||
"""Initialize the ONVIF binary sensor."""
|
"""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)
|
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):
|
async def async_added_to_hass(self):
|
||||||
"""Connect to dispatcher listening for entity data notifications."""
|
"""Connect to dispatcher listening for entity data notifications."""
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
|
@ -6,7 +6,7 @@ from zeep.exceptions import Fault
|
|||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.onvif import config_flow
|
from homeassistant.components.onvif import config_flow
|
||||||
from homeassistant.components.onvif.const import CONF_SNAPSHOT_AUTH
|
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 homeassistant.const import HTTP_DIGEST_AUTHENTICATION
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@ -95,6 +95,8 @@ def setup_mock_device(mock_device):
|
|||||||
SERIAL_NUMBER,
|
SERIAL_NUMBER,
|
||||||
MAC,
|
MAC,
|
||||||
)
|
)
|
||||||
|
mock_device.capabilities = Capabilities()
|
||||||
|
mock_device.profiles = []
|
||||||
|
|
||||||
def mock_constructor(hass, config):
|
def mock_constructor(hass, config):
|
||||||
"""Fake the controller constructor."""
|
"""Fake the controller constructor."""
|
||||||
|
54
tests/components/onvif/test_diagnostics.py
Normal file
54
tests/components/onvif/test_diagnostics.py
Normal file
@ -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": [],
|
||||||
|
},
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user