Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added field detection detection target and region id in bus event data #193

Merged
merged 3 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -63,7 +63,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 @@ -844,10 +845,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
Loading