Skip to content

Commit

Permalink
added field detection detection target and region id in bus event data (
Browse files Browse the repository at this point in the history
#193)

* detection_target and region id passed to hikvision_next_event on event bus

* saving image file from event notification

* take multiple snapshots blueprint
  • Loading branch information
maciej-or authored Aug 6, 2024
1 parent a7c82fc commit 3e22da8
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
blueprint:
name: Take Multiple Snapshots On Detection Event
description: |
Takes pictures from multiple cameras when motion is detected on sensors.
Pictures are saved into /media/hikvision_next/snapshots folder
domain: automation
input:
motion_entities:
name: Sensor
description: An event sensor
selector:
entity:
multiple: true
domain: binary_sensor
device_class:
- motion
integration: hikvision_next
camera_entities:
name: Camera
description: A camera that takes a photo
selector:
entity:
multiple: true
domain: camera
integration: hikvision_next
mode: single
variables:
camera_entities: !input camera_entities
month: "{{now().strftime('%Y-%m')}}"
day: "{{now().strftime('%d')}}"
timestamp: "{{ now().strftime('%Y-%m-%d__%H-%M-%S') }}"
trigger:
- platform: state
entity_id: !input motion_entities
to: "on"
condition: []
action:
- repeat:
count: "{{ camera_entities | count }}"
sequence:
- variables:
camera_entity: "{{ camera_entities[repeat.index - 1] }}"
camera_name: "{{ states[camera_entity].name }}"
- service: camera.snapshot
data:
filename: /media/hikvision_next/snapshots/{{month}}/{{day}}/{{timestamp}}__{{camera_name}}.jpg
target:
entity_id: "{{ camera_entity }}"
8 changes: 6 additions & 2 deletions custom_components/hikvision_next/isapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ class AlertInfo:
event_id: str
device_serial_no: Optional[str]
mac: str = ""

region_id: int = 0
detection_target: Optional[str] = None

@dataclass
class MutexIssue:
Expand Down Expand Up @@ -848,10 +849,13 @@ def parse_event_notification(xml: str) -> AlertInfo:
# <EventNotificationAlert version="2.0"
mac = alert.get("macAddress")

detection_target = deep_get(alert, "DetectionRegionList.DetectionRegionEntry.detectionTarget")
region_id = int(deep_get(alert, "DetectionRegionList.DetectionRegionEntry.regionID", 0))

if not EVENTS[event_id]:
raise ValueError(f"Unsupported event {event_id}")

return AlertInfo(channel_id, io_port_id, event_id, device_serial, mac)
return AlertInfo(channel_id, io_port_id, event_id, device_serial, mac, region_id, detection_target)

async def get_camera_image(
self,
Expand Down
14 changes: 12 additions & 2 deletions custom_components/hikvision_next/notifications.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def get_isapi_instance(self, device_ip) -> ISAPI:
break

if not entry:
raise ValueError("Cannot find ISAPI instance for device %s in %s", device_ip, instances_hosts)
raise ValueError(f"Cannot find ISAPI instance for device {device_ip} in {instances_hosts}")

config = self.hass.data[DOMAIN][entry.entry_id]
return config.get(DATA_ISAPI)
Expand Down Expand Up @@ -118,6 +118,14 @@ async def parse_event_request(self, request: web.Request) -> str:
xml = part.text
if headers.get(CONTENT_TYPE) == CONTENT_TYPE_IMAGE:
_LOGGER.debug("image found")
# Use camera.snapshot service instead
# from datetime import datetime
# import aiofiles
# now = datetime.now()
# filename = f"/media/{DOMAIN}/snapshots/{now.strftime('%Y-%m-%d_%H-%M-%S_%f')}.jpg"
# async with aiofiles.open(filename, "wb") as image_file:
# await image_file.write(part.content)
# await image_file.flush()

if not xml:
raise ValueError(f"Unexpected event Content-Type {content_type_header}")
Expand Down Expand Up @@ -157,7 +165,6 @@ def trigger_sensor(self, xml: str) -> None:

_LOGGER.debug("UNIQUE_ID: %s", unique_id)

# unique_id = f"binary_sensor.{slugify(serial_no)}_{alert.channel_id}" f"_{alert.event_id}"
entity_registry = async_get(self.hass)
entity_id = entity_registry.async_get_entity_id(Platform.BINARY_SENSOR, DOMAIN, unique_id)
if entity_id:
Expand All @@ -180,6 +187,9 @@ def fire_hass_event(self, alert: AlertInfo):
"camera_name": camera_name,
"event_id": alert.event_id,
}
if alert.detection_target:
message["detection_target"] = alert.detection_target
message["region_id"] = alert.region_id

self.hass.bus.fire(
HIKVISION_EVENT,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<EventNotificationAlert version="2.0"
xmlns="http://www.hikvision.com/ver20/XMLSchema">
<ipAddress>1.0.0.255</ipAddress>
<ipv6Address>::ffff:192.168.2.5</ipv6Address>
<portNo>80</portNo>
<protocol>HTTP</protocol>
<macAddress>ac:b9:2f:49:44:75</macAddress>
<channelID>1</channelID>
<dateTime>2022-01-31T13:53:05+01:00</dateTime>
<activePostCount>1</activePostCount>
<eventType>fielddetection</eventType>
<eventState>active</eventState>
<eventDescription>fielddetection alarm</eventDescription>
<DetectionRegionList>
<DetectionRegionEntry>
<regionID>3</regionID>
<sensitivityLevel>50</sensitivityLevel>
<RegionCoordinatesList>
<RegionCoordinates>
<positionX>572</positionX>
<positionY>100</positionY>
</RegionCoordinates>
<RegionCoordinates>
<positionX>572</positionX>
<positionY>995</positionY>
</RegionCoordinates>
<RegionCoordinates>
<positionX>999</positionX>
<positionY>998</positionY>
</RegionCoordinates>
<RegionCoordinates>
<positionX>992</positionX>
<positionY>210</positionY>
</RegionCoordinates>
</RegionCoordinatesList>
<detectionTarget>human</detectionTarget>
<TargetRect>
<X>0.6</X>
<Y>0.215278</Y>
<width>0.135937</width>
<height>0.505556</height>
</TargetRect>
</DetectionRegionEntry>
</DetectionRegionList>
<channelName>COUR NORD</channelName>
<detectionPictureTransType>binary</detectionPictureTransType>
<detectionPicturesNumber>1</detectionPicturesNumber>
<isDataRetransmission>false</isDataRetransmission>
</EventNotificationAlert>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<EventNotificationAlert version="2.0"
xmlns="http://www.hikvision.com/ver20/XMLSchema">
<ipAddress>1.0.0.255</ipAddress>
<ipv6Address>::ffff:192.168.2.5</ipv6Address>
<portNo>80</portNo>
<protocol>HTTP</protocol>
<macAddress>ac:b9:2f:49:44:75</macAddress>
<channelID>1</channelID>
<dateTime>2022-01-19T07:51:17+01:00</dateTime>
<activePostCount>1</activePostCount>
<eventType>fielddetection</eventType>
<eventState>active</eventState>
<eventDescription>fielddetection alarm</eventDescription>
<DetectionRegionList>
<DetectionRegionEntry>
<regionID>2</regionID>
<sensitivityLevel>50</sensitivityLevel>
<RegionCoordinatesList>
<RegionCoordinates>
<positionX>118</positionX>
<positionY>327</positionY>
</RegionCoordinates>
<RegionCoordinates>
<positionX>999</positionX>
<positionY>326</positionY>
</RegionCoordinates>
<RegionCoordinates>
<positionX>999</positionX>
<positionY>626</positionY>
</RegionCoordinates>
<RegionCoordinates>
<positionX>0</positionX>
<positionY>630</positionY>
</RegionCoordinates>
</RegionCoordinatesList>
<detectionTarget>vehicle</detectionTarget>
<TargetRect>
<X>0.779688</X>
<Y>0.409722</Y>
<width>0.209375</width>
<height>0.459722</height>
</TargetRect>
</DetectionRegionEntry>
</DetectionRegionList>
<channelName>COUR NORD</channelName>
<detectionPictureTransType>binary</detectionPictureTransType>
<detectionPicturesNumber>1</detectionPicturesNumber>
<isDataRetransmission>false</isDataRetransmission>
</EventNotificationAlert>
60 changes: 59 additions & 1 deletion tests/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import pytest
from http import HTTPStatus
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, Event
from custom_components.hikvision_next.notifications import EventNotificationsView
from custom_components.hikvision_next.const import HIKVISION_EVENT
from pytest_homeassistant_custom_component.common import MockConfigEntry
from unittest.mock import MagicMock
from tests.conftest import load_fixture, TEST_HOST_IP
Expand Down Expand Up @@ -35,6 +36,11 @@ async def test_nvr_intrusion_detection_alert(
"""Test incoming intrusion detection event alert from nvr."""

entity_id = "binary_sensor.ds_7608nxi_i0_0p_s0000000000ccrrj00000000wcvu_2_fielddetection"
bus_events = []
def bus_event_listener(event: Event) -> None:
bus_events.append(event)
hass.bus.async_listen(HIKVISION_EVENT, bus_event_listener)

assert (sensor := hass.states.get(entity_id))
assert sensor.state == STATE_OFF

Expand All @@ -46,6 +52,13 @@ async def test_nvr_intrusion_detection_alert(
assert (sensor := hass.states.get(entity_id))
assert sensor.state == STATE_ON

await hass.async_block_till_done()
assert len(bus_events) == 1
data = bus_events[0].data
assert data["channel_id"] == 2
assert data["event_id"] == "fielddetection"
assert data["camera_name"] == "home"


@pytest.mark.parametrize("init_integration", ["DS-2CD2386G2-IU"], indirect=True)
async def test_ipc_intrusion_detection_alert(
Expand All @@ -54,6 +67,7 @@ async def test_ipc_intrusion_detection_alert(
"""Test incoming intrusion detection event alert from ip camera."""

entity_id = "binary_sensor.ds_2cd2386g2_iu00000000aawrj00000000_1_fielddetection"

assert (sensor := hass.states.get(entity_id))
assert sensor.state == STATE_OFF

Expand All @@ -64,3 +78,47 @@ async def test_ipc_intrusion_detection_alert(
assert response.status == HTTPStatus.OK
assert (sensor := hass.states.get(entity_id))
assert sensor.state == STATE_ON


@pytest.mark.parametrize("init_integration", ["DS-2CD2146G2-ISU"], indirect=True)
async def test_field_detection_alert(
hass: HomeAssistant, init_integration: MockConfigEntry,
) -> None:
"""Test incoming field detection event with detection target."""

entity_id = "binary_sensor.ds_2cd2146g2_isu00000000aawrg00000000_1_fielddetection"
bus_events = []
def bus_event_listener(event: Event) -> None:
bus_events.append(event)
hass.bus.async_listen(HIKVISION_EVENT, bus_event_listener)

view = EventNotificationsView(hass)
mock_request = mock_event_notification("fielddetection_human")
response = await view.post(mock_request)

assert response.status == HTTPStatus.OK
assert (sensor := hass.states.get(entity_id))
assert sensor.state == STATE_ON

await hass.async_block_till_done()
assert len(bus_events) == 1
data = bus_events[0].data
assert data["channel_id"] == 1
assert data["event_id"] == "fielddetection"
assert data["detection_target"] == "human"
assert data["region_id"] == 3

mock_request = mock_event_notification("fielddetection_vehicle")
response = await view.post(mock_request)

assert response.status == HTTPStatus.OK
assert (sensor := hass.states.get(entity_id))
assert sensor.state == STATE_ON

await hass.async_block_till_done()
assert len(bus_events) == 2
data = bus_events[1].data
assert data["channel_id"] == 1
assert data["event_id"] == "fielddetection"
assert data["detection_target"] == "vehicle"
assert data["region_id"] == 2

0 comments on commit 3e22da8

Please sign in to comment.