Skip to content

Commit 10f7c01

Browse files
authored
Merge pull request #16 from rroller/crossline
Add cross line detection support
2 parents 7dc0e3e + e5da89c commit 10f7c01

File tree

4 files changed

+81
-7
lines changed

4 files changed

+81
-7
lines changed

custom_components/dahua/__init__.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class DahuaDataUpdateCoordinator(DataUpdateCoordinator):
9898
def __init__(self, hass: HomeAssistant, dahua_client: DahuaClient, events: list) -> None:
9999
"""Initialize."""
100100
self.client: DahuaClient = dahua_client
101-
self.dahua_event: DahuaEventThread = None
101+
self.dahua_event: DahuaEventThread
102102
self.platforms = []
103103
self.initialized = False
104104
self.model = ""
@@ -107,16 +107,18 @@ def __init__(self, hass: HomeAssistant, dahua_client: DahuaClient, events: list)
107107
self.channels = {"1": "1"}
108108
self.events: list = events
109109
self.motion_timestamp_seconds = 0
110+
self.cross_line_detection_timestamp_seconds = 0
110111
self.motion_listener: CALLBACK_TYPE
112+
self.cross_line_detection_listener: CALLBACK_TYPE
111113
self._supports_coaxial_control = False
112114
self._supports_disarming_linkage = False
115+
self.dahua_event = DahuaEventThread(hass, dahua_client, self.on_receive, events)
113116

114117
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL_SECONDS)
115118

116119
async def async_start_event_listener(self):
117120
""" setup """
118121
if self.events is not None:
119-
self.dahua_event = DahuaEventThread(self.hass, self.machine_name, self.client, self.on_receive, self.events)
120122
self.dahua_event.start()
121123

122124
async def async_stop(self, event: Any):
@@ -224,6 +226,7 @@ def on_receive(self, data_bytes: bytes):
224226

225227
# When there's a motion start event we'll set a flag to the current timestmap in seconds.
226228
# We'll reset it when the motion stops. We'll use this elsewhere to know how long to trigger a motion sensor
229+
# TODO: Generalize events so we don't create a block for each one
227230
if event["Code"] == "VideoMotion":
228231
action = event["action"]
229232
if action == "Start":
@@ -234,13 +237,27 @@ def on_receive(self, data_bytes: bytes):
234237
self.motion_timestamp_seconds = 0
235238
if self.motion_listener:
236239
self.motion_listener()
240+
if event["Code"] == "CrossLineDetection":
241+
action = event["action"]
242+
if action == "Start":
243+
self.cross_line_detection_timestamp_seconds = int(time.time())
244+
if self.cross_line_detection_listener:
245+
self.cross_line_detection_listener()
246+
elif action == "Stop":
247+
self.cross_line_detection_timestamp_seconds = 0
248+
if self.cross_line_detection_listener:
249+
self.cross_line_detection_listener()
237250

238251
self.hass.bus.fire("dahua_event_received", event)
239252

240253
def add_motion_listener(self, listener: CALLBACK_TYPE):
241254
""" Adds the motion listener. This callback will be called on motion events """
242255
self.motion_listener = listener
243256

257+
def add_cross_line_detection_listener(self, listener: CALLBACK_TYPE):
258+
""" Adds the CrossLineDetection listener. This callback will be called on CrossLineDetection events """
259+
self.cross_line_detection_listener = listener
260+
244261
def supports_siren(self) -> bool:
245262
"""
246263
Returns true if this camera has a siren. For example, the IPC-HDW3849HP-AS-PV does

custom_components/dahua/binary_sensor.py

+56-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
async def async_setup_entry(hass: HomeAssistant, entry, async_add_devices):
1414
"""Setup binary_sensor platform."""
1515
coordinator = hass.data[DOMAIN][entry.entry_id]
16-
async_add_devices([DahuaMotionSensor(coordinator, entry)])
16+
17+
async_add_devices([
18+
DahuaMotionSensor(coordinator, entry),
19+
DahuaCrossLineDetectionSensor(coordinator, entry)
20+
])
1721

1822

1923
class DahuaMotionSensor(DahuaBaseEntity, BinarySensorEntity):
@@ -25,6 +29,7 @@ def __init__(self, coordinator: DahuaDataUpdateCoordinator, config_entry):
2529

2630
self._name = coordinator.get_device_name()
2731
self._coordinator = coordinator
32+
self._unique_id = coordinator.get_serial_number() + "_motion_alarm"
2833

2934
@property
3035
def name(self):
@@ -55,3 +60,53 @@ async def async_added_to_hass(self):
5560
def should_poll(self) -> bool:
5661
"""Return True if entity has to be polled for state. False if entity pushes its state to HA"""
5762
return False
63+
64+
65+
class DahuaCrossLineDetectionSensor(DahuaBaseEntity, BinarySensorEntity):
66+
"""
67+
dahua binary_sensor class to record 'cross line detection' events. This is also known as 'tripwire'. This is
68+
configured in the camera UI by going to Setting -> Event -> IVS -> and adding a tripwire rule.
69+
"""
70+
71+
def __init__(self, coordinator: DahuaDataUpdateCoordinator, config_entry):
72+
DahuaBaseEntity.__init__(self, coordinator, config_entry)
73+
BinarySensorEntity.__init__(self)
74+
75+
self._name = coordinator.get_device_name()
76+
self._coordinator = coordinator
77+
self._unique_id = coordinator.get_serial_number() + "_cross_line_alarm"
78+
79+
@ property
80+
def unique_id(self):
81+
"""Return the entity unique ID."""
82+
return self._unique_id
83+
84+
@property
85+
def name(self):
86+
"""Return the name of the binary_sensor."""
87+
return f"{self._name} Cross Line Alarm"
88+
89+
@property
90+
def device_class(self):
91+
"""Return the class of this binary_sensor."""
92+
return MOTION_SENSOR_DEVICE_CLASS
93+
94+
@property
95+
def is_on(self):
96+
"""
97+
Return true if a cross line detection event activated.
98+
99+
This is the magic part of this sensor along with the async_added_to_hass method below.
100+
The async_added_to_hass method adds a listener to the coordinator so when the event is started or stopped
101+
it calls the async_write_ha_state function. async_write_ha_state just gets the current value from this is_on method.
102+
"""
103+
return self._coordinator.cross_line_detection_timestamp_seconds > 0
104+
105+
async def async_added_to_hass(self):
106+
"""Connect to dispatcher listening for entity data notifications."""
107+
self._coordinator.add_cross_line_detection_listener(self.async_write_ha_state)
108+
109+
@property
110+
def should_poll(self) -> bool:
111+
"""Return True if entity has to be polled for state. False if entity pushes its state to HA"""
112+
return False

custom_components/dahua/client.py

+1
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ async def stream_events(self, on_receive, events: list):
394394
message to the client,the heartbeat message are "Heartbeat".
395395
Note: Heartbeat message must be sent before heartbeat timeout
396396
"""
397+
# Use codes=[All] for all codes
397398
codes = ",".join(events)
398399
url = "http://{0}/cgi-bin/eventManager.cgi?action=attach&codes=[{1}]&heartbeat=2".format(self._address_with_port, codes)
399400
if self._username is not None and self._password is not None:

custom_components/dahua/thread.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@
1414
class DahuaEventThread(threading.Thread):
1515
"""Connects to device and subscribes to events. Mainly to capture motion detection events. """
1616

17-
def __init__(self, hass: HomeAssistant, name: str, client: DahuaClient, on_receive, events: list):
17+
def __init__(self, hass: HomeAssistant, client: DahuaClient, on_receive, events: list):
1818
"""Construct a thread listening for events."""
1919
threading.Thread.__init__(self)
20-
self.name = name
2120
self.hass = hass
2221
self.stopped = threading.Event()
2322
self.on_receive = on_receive
@@ -28,6 +27,7 @@ def __init__(self, hass: HomeAssistant, name: str, client: DahuaClient, on_recei
2827
def run(self):
2928
"""Fetch events"""
3029
self.started = True
30+
_LOGGER.debug("Starting DahuaEventThread")
3131

3232
while 1:
3333
# submit the coroutine to the event loop thread
@@ -39,20 +39,21 @@ def run(self):
3939
# wait for the coroutine to finish
4040
future.result()
4141
except asyncio.TimeoutError as ex:
42-
_LOGGER.warning("TimeoutError connecting to %s", self.name)
42+
_LOGGER.warning("TimeoutError connecting to camera")
4343
future.cancel()
4444
except Exception as ex: # pylint: disable=broad-except
4545
_LOGGER.debug("%s", ex)
4646

4747
if not self.started:
48+
_LOGGER.debug("Exiting DahuaEventThread")
4849
return
4950

5051
end_time = int(time.time())
5152
if (end_time - start_time) < 10:
5253
# We are failing fast when trying to connect to the camera. Let's retry slowly
5354
time.sleep(60)
5455

55-
_LOGGER.debug("reconnecting to camera's %s event stream...", self.name)
56+
_LOGGER.debug("reconnecting to camera's event stream...")
5657

5758
def stop(self):
5859
""" Signals to the thread loop that we should stop """

0 commit comments

Comments
 (0)