Skip to content

Commit

Permalink
Multiple Updates (#78)
Browse files Browse the repository at this point in the history
* fix reported camera firmware

* force int on CameraStreamInfo attributes

* Event triggers to use dispatcher

* correct camera stream name - issue #73

* match event ip to dns host entry for isapi

* correct stream type in CameraStreamInfo

* fix triggers list empty on single camera

* revert dispatching an event from notifications

* searching entity by unique_id

---------

Co-authored-by: maciej-or <maciej.orlowski@scalac.io>
  • Loading branch information
msp1974 and maciej-or authored Jul 4, 2023
1 parent d713e08 commit 05c81bb
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 21 deletions.
2 changes: 1 addition & 1 deletion custom_components/hikvision_next/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ def __init__(self, isapi, camera: AnalogCamera | IPCamera, event: EventInfo) ->
self._attr_unique_id = self.entity_id
self._attr_name = EVENTS[event.id]["label"]
self._attr_device_class = EVENTS[event.id]["device_class"]
self._attr_device_info = isapi.get_device_info(camera.id)
self._attr_device_info = isapi.get_device_info(camera.id)
2 changes: 1 addition & 1 deletion custom_components/hikvision_next/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(
Camera.__init__(self)

self._attr_device_info = isapi.get_device_info(camera.id)
self._attr_name = camera.name if stream_info.type_id == 1 else stream_info.type
self._attr_name = f"{camera.name} {stream_info.type}"
self._attr_unique_id = slugify(f"{isapi.device_info.serial_no.lower()}_{stream_info.id}")
self.entity_id = f"camera.{self.unique_id}"
self.isapi = isapi
Expand Down
2 changes: 1 addition & 1 deletion custom_components/hikvision_next/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ async def _async_get_diagnostics(
info.update(await get_isapi_data("RAW Capabilities Info", isapi.isapi.System.capabilities, "DeviceCap"))

# Add raw supported events
info.update(await get_isapi_data("RAW Events Info", isapi.isapi.Event.triggers, "EventTriggerList"))
info.update(await get_isapi_data("RAW Events Info", isapi.isapi.Event.triggers, ""))

# Add raw streams info
info.update(await get_isapi_data("RAW Streams Info", isapi.isapi.Streaming.channels, "StreamingChannelList"))
Expand Down
6 changes: 3 additions & 3 deletions custom_components/hikvision_next/isapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,9 @@ async def get_camera_streams(self, channel_id: int) -> list[CameraStreamInfo]:
)
streams.append(
CameraStreamInfo(
id=stream_info["id"],
id=int(stream_info["id"]),
name=stream_info["channelName"],
type_id=stream_info["id"],
type_id=stream_type_id,
type=stream_type,
enabled=stream_info["enabled"],
codec=stream_info["Video"]["videoCodecType"],
Expand Down Expand Up @@ -530,7 +530,7 @@ def get_device_info(self, device_id: int = 0) -> DeviceInfo:
identifiers={(DOMAIN, camera_info.serial_no)},
model=camera_info.model,
name=camera_info.name,
sw_version=self.device_info.firmware if is_ip_camera else "Unknown",
sw_version=camera_info.firmware if is_ip_camera else "Unknown",
via_device=(DOMAIN, self.device_info.serial_no) if self.device_info.is_nvr else None,
)

Expand Down
47 changes: 32 additions & 15 deletions custom_components/hikvision_next/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
from __future__ import annotations

from http import HTTPStatus
import ipaddress
import logging
import socket
from urllib.parse import urlparse

from aiohttp import web
from requests_toolbelt.multipart import MultipartDecoder

from homeassistant.components.http import HomeAssistantView
from homeassistant.const import CONTENT_TYPE_TEXT_PLAIN, STATE_ON
from homeassistant.const import CONTENT_TYPE_TEXT_PLAIN, STATE_ON, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.entity_registry import async_get_registry
from homeassistant.util import slugify

from .const import ALARM_SERVER_PATH, DATA_ISAPI, DOMAIN, HIKVISION_EVENT
Expand Down Expand Up @@ -49,7 +51,7 @@ async def post(self, request: web.Request):
self.isapi = self.get_isapi_instance(request.remote)
xml = await self.parse_event_request(request)
_LOGGER.debug("alert info: %s", xml)
self.trigger_sensor(request.app["hass"], xml)
await self.trigger_sensor(xml)
except Exception as ex: # pylint: disable=broad-except
_LOGGER.warning("Cannot process incoming event %s", ex)

Expand All @@ -63,7 +65,7 @@ def get_isapi_instance(self, device_ip) -> ISAPI:
entry = [
entry
for entry in self.hass.config_entries.async_entries(DOMAIN)
if not entry.disabled_by and urlparse(entry.data.get("host")).hostname == device_ip
if not entry.disabled_by and self.get_ip(urlparse(entry.data.get("host")).hostname) == device_ip
][0]

config = self.hass.data[DOMAIN][entry.entry_id]
Expand All @@ -73,6 +75,18 @@ def get_isapi_instance(self, device_ip) -> ISAPI:
except IndexError:
return None

def get_ip(self, ip_string: str) -> str:
"""Return an IP if either hostname or IP is provided"""

try:
ipaddress.ip_address(ip_string)
return ip_string
except ValueError:
resolved_hostname = socket.gethostbyname(ip_string)
_LOGGER.debug("Resolve host %s resolves to IP %s", ip_string, resolved_hostname)

return resolved_hostname

async def parse_event_request(self, request: web.Request) -> str:
"""Extract XML content from multipart request or from simple request"""

Expand Down Expand Up @@ -122,22 +136,25 @@ def get_alert_info(self, xml: str) -> AlertInfo:

return alert

def trigger_sensor(self, hass: HomeAssistant, xml: str) -> None:
async def trigger_sensor(self, xml: str) -> None:
"""Determine entity and set binary sensor state"""

alert = self.get_alert_info(xml)
_LOGGER.debug("Alert: %s", alert)

serial_no = self.isapi.device_info.serial_no.lower()
entity_id = f"binary_sensor.{slugify(serial_no)}_{alert.channel_id}" f"_{alert.event_id}"
entity = hass.states.get(entity_id)
if entity:
hass.states.async_set(entity_id, STATE_ON, entity.attributes)
self.fire_hass_event(hass, alert)
else:
raise ValueError(f"Entity not found {entity_id}")

def fire_hass_event(self, hass: HomeAssistant, alert: AlertInfo):
unique_id = f"binary_sensor.{slugify(serial_no)}_{alert.channel_id}" f"_{alert.event_id}"
entity_registry = await async_get_registry(self.hass)
entity_id = entity_registry.async_get_entity_id(Platform.BINARY_SENSOR, DOMAIN, unique_id)
if entity_id:
entity = self.hass.states.get(entity_id)
if entity:
self.hass.states.async_set(entity_id, STATE_ON, entity.attributes)
self.fire_hass_event(alert)
return
raise ValueError(f"Entity not found {entity_id}")

def fire_hass_event(self, alert: AlertInfo):
"""Fire HASS event"""
camera = self.isapi.get_camera_by_id(alert.channel_id)
message = {
Expand All @@ -146,7 +163,7 @@ def fire_hass_event(self, hass: HomeAssistant, alert: AlertInfo):
"event_id": alert.event_id,
}

hass.bus.fire(
self.hass.bus.fire(
HIKVISION_EVENT,
message,
)

0 comments on commit 05c81bb

Please sign in to comment.