aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client.cfg2
-rw-r--r--client.py17
-rw-r--r--helpers.py14
-rw-r--r--templates/client.html34
-rw-r--r--webserver.py13
-rw-r--r--worker.py2
6 files changed, 51 insertions, 31 deletions
diff --git a/client.cfg b/client.cfg
index c407c4e..d86ba77 100644
--- a/client.cfg
+++ b/client.cfg
@@ -6,7 +6,7 @@ router_address = 127.0.0.1
router_port = 5569
[logging]
-logdir = logs
+logdir = /var/log/zmq_client
logfile = client.log
loglevel = INFO
diff --git a/client.py b/client.py
index 81c8f8b..7b7a697 100644
--- a/client.py
+++ b/client.py
@@ -35,6 +35,8 @@ from helpers import (
bytes_to_timestamp,
write_yappi_stats,
auth_service,
+ ComputingContoursException,
+ DetectMovementException,
)
###################################################
@@ -187,8 +189,7 @@ class ClientVideo(Thread):
List of contours to draw on a frame
"""
- raw_frame = self.frame_deque[-1][2]
- scaling_factor = self.frame_deque[-1][1]
+ _, scaling_factor, raw_frame = self.frame_deque[-1]
scaled_contours = scale_contours(contours, scaling_factor)
try:
frame_out = draw_contours(
@@ -262,15 +263,13 @@ class ClientVideo(Thread):
sample_frames = self.frame_deque[0][0], self.frame_deque[-1][0]
try:
contours = compute_contours(sample_frames)
- except Exception as exc:
- logger.error("ClientVideo %r: Error computing contours: %r", self.identity, exc)
- continue
- try:
movement_now = detect_movement(contours, min_area=self.device_threshold)
- except Exception as exc:
- logger.error("ClientVideo %r: Error detecting movement: %r", self.identity, exc)
+ except ComputingContoursException:
+ logger.error("ClientVideo %r: Error computing contours", self.identity)
+ continue
+ except DetectMovementException:
+ logger.error("ClientVideo %r: Error detecting movement", self.identity)
continue
-
# Only update movement start time and send start message
# if movement is detected and was not detected before:
if movement_now:
diff --git a/helpers.py b/helpers.py
index 8a47415..1ea057b 100644
--- a/helpers.py
+++ b/helpers.py
@@ -96,15 +96,13 @@ def bytes_to_timestamp(byte_data):
def compute_contours(sample_frames):
"""Compute contours between two frames"""
- all_contours = []
frame_0, frame_1 = sample_frames
frame_delta = cv2.absdiff(frame_0, frame_1)
threshold = cv2.threshold(frame_delta, 25, 255, cv2.THRESH_BINARY)[1]
threshold = cv2.dilate(threshold, None, iterations=2)
- contours, _ = cv2.findContours(threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- all_contours.extend(contours)
+ contours, _ = cv2.findContours(threshold.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- return all_contours
+ return contours
def scale_contours(contours, scaling_factor):
@@ -123,7 +121,7 @@ def scale_contours(contours, scaling_factor):
def draw_contours(frame, contours, min_contour_area=500):
"""Draw contours on the frame."""
for contour in contours:
- if cv2.contourArea(contour) > min_contour_area:
+ if cv2.contourArea(contour) >= min_contour_area:
(x, y, w, h) = cv2.boundingRect(contour)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
@@ -272,3 +270,9 @@ def auth_service(context, cert_dir):
auth.start()
auth.configure_curve(location=cert_dir)
zmq.auth.load_certificates(cert_dir)
+
+class ComputingContoursException(Exception):
+ pass
+
+class DetectMovementException(Exception):
+ pass \ No newline at end of file
diff --git a/templates/client.html b/templates/client.html
index 785fbcf..09365df 100644
--- a/templates/client.html
+++ b/templates/client.html
@@ -3,6 +3,7 @@
<head>
<title>Client {{ client_id }} - Camera Streams</title>
<link rel="stylesheet" href="{{ url_for('static', path='css/main.css') }}">
+ <link rel="icon" type="image/x-icon" href="images/favicon.ico">
<style>
.camera-stream { display: inline-block; margin: 10px; }
video { border: 1px solid #333; }
@@ -53,16 +54,16 @@
<button onclick="sendConfig('{{ camera_id }}', 'modify_camera_grace_pd')">Send</button>
</div>
<div>
- <h3>Recorded Videos for {{ camera_id }}</h3>
- <ul id="video-list-{{ camera_id }}" class="scroll-box">
- {% for filename, video_url, timestamp in client_videos[camera_id] %}
- <li><a href="{{ video_url }}">{{ filename }}</a> ({{ timestamp }})</li>
- <!-- <h3>video file: {{ filename }}</h3>
- <video src="{{ video_url }}" type="video/ogg" width="320" height="240" controls></video>
- <hr4>creation: {{ timestamp }}</h3>
- -->
- {% endfor %}
- </ul>
+ <h3>
+ <details>
+ <summary>Recorded videos for camera: {{ camera_id }}</summary>
+ <ul id="video-list-{{ camera_id }}" class="scroll-box">
+ {% for filename, video_url, timestamp in client_videos[camera_id] %}
+ <li><a href="{{ video_url }}">{{ filename }}</a> ({{ timestamp }})</li>
+ {% endfor %}
+ </ul>
+ </details>
+ </h3>
</div>
</div>
{% endfor %}
@@ -91,14 +92,23 @@
// For each camera, open a WebSocket and update the corresponding <img>
(function() {
const cameraId = '{{ camera_id }}';
- const ws = new WebSocket('ws://' + window.location.host + '/ws/{{ client_id }}/' + cameraId);
+ const wsUrl = window.location.origin.replace(/^http/, 'ws') + '/ws/{{ client_id }}/' + cameraId;
+ const ws = new WebSocket(wsUrl);
+ ws.binaryType = 'arraybuffer';
let currentUrl = null;
ws.onmessage = function(event) {
let image = document.getElementById('video-' + cameraId);
+ if (!image) {
+ return;
+ }
+
if (currentUrl) {
URL.revokeObjectURL(currentUrl);
}
- currentUrl = URL.createObjectURL(event.data);
+
+ // Ensure we always have a Blob for createObjectURL
+ const blob = event.data instanceof Blob ? event.data : new Blob([event.data], { type: 'image/jpeg' });
+ currentUrl = URL.createObjectURL(blob);
image.src = currentUrl;
};
ws.onclose = function(event) {
diff --git a/webserver.py b/webserver.py
index 3a95dca..1e1a068 100644
--- a/webserver.py
+++ b/webserver.py
@@ -76,7 +76,10 @@ auth_service(zmq_context, CERTIFICATE_DIR)
zmq_socket = zmq_context.socket(zmq.DEALER)
# Connect to ZMQ backend:
-zmq_socket.connect(WEB_BACKEND_ADDR)
+try:
+ zmq_socket.connect(WEB_BACKEND_ADDR)
+except Exception as exc:
+ logger.error("Could not connect to backend address: %r" % exc)
async def zmq_bridge():
@@ -125,7 +128,8 @@ async def zmq_bridge():
logger.error("Invalid control message on queue: %r", ve)
if client_id and camera_id and command and args_list is not None:
try:
- zmq_socket.send_multipart([
+ # TODO: check if this below is correct
+ await zmq_socket.send_multipart([
client_id,
camera_id,
command,
@@ -190,7 +194,7 @@ async def main_route(request: Request) -> HTMLResponse:
request : Request
The incoming HTTP request.
"""
- logger.debug("Main route visited")
+ logger.debug("Main route visited. CLIENTS_DICT: %s" % CLIENTS_DICT)
return templates.TemplateResponse(
"main.html",
{
@@ -454,6 +458,9 @@ def validate_camera_name(name, client_id, camera_id) -> bool:
if len(new_name) > MAX_CAMERA_NAME_LENGTH:
logger.error("Camera name is too long (max %s characters).", MAX_CAMERA_NAME_LENGTH)
retval = False
+ if ' ' in name:
+ logger.error("Camera name cannot contain spaces.")
+ retval = False
return retval
def validate_camera_threshold(threshold) -> bool:
diff --git a/worker.py b/worker.py
index 9f6086b..2e1bb6c 100644
--- a/worker.py
+++ b/worker.py
@@ -136,7 +136,7 @@ class MonitorTask(Thread):
# Resolve the received event type:
event_type = int.from_bytes(event[:2], "little")
if event_type in (zmq.EVENT_CLOSED, zmq.EVENT_DISCONNECTED):
- logger.warning("Monitor socket: closed | disconnected")
+ logger.debug("Monitor socket: closed | disconnected")
stop_event.set()
elif event_type == zmq.EVENT_CONNECT_DELAYED:
logger.debug("Monitor socket: event connect delayed")