From 4873d36194cb9294621a8e0c8db1d4a36e79806d Mon Sep 17 00:00:00 2001 From: "Aaron D. Lee" Date: Fri, 3 Apr 2026 19:18:38 -0400 Subject: [PATCH] fix: wire package/visitor events, bbox payloads, reel/timelapse scheduling - Add `requests` to pyproject.toml dependencies (used by detection/weather.py) - Wire PACKAGE_DELIVERED/REMINDER/COLLECTED and KNOWN/UNKNOWN_VISITOR event types in EventProcessor._classify_event - Add normalized bbox to all detection payloads in camera worker (domestic_animal, wildlife, person, vehicle) - Integrate highlight reel and timelapse scheduling in HealthMonitor.run() Co-Authored-By: Claude Sonnet 4.6 --- pyproject.toml | 1 + vigilar/camera/worker.py | 10 ++++++++++ vigilar/events/processor.py | 14 ++++++++++++++ vigilar/health/monitor.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 13873f4..5e5241f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ dependencies = [ "py-vapid>=1.9.0", "ultralytics>=8.2.0", "torchvision>=0.18.0", + "requests>=2.32.0", ] [project.optional-dependencies] diff --git a/vigilar/camera/worker.py b/vigilar/camera/worker.py index 0781478..445174c 100644 --- a/vigilar/camera/worker.py +++ b/vigilar/camera/worker.py @@ -275,6 +275,12 @@ def run_camera_worker( detections = yolo_detector.detect(frame) for det in detections: category = YOLODetector.classify(det) + frame_h, frame_w = frame.shape[:2] + bx, by, bw, bh = det.bbox + norm_bbox = [ + round(bx / frame_w, 4), round(by / frame_h, 4), + round(bw / frame_w, 4), round(bh / frame_h, 4), + ] if category == "domestic_animal": # Crop for pet ID and staging x, y, w, h = det.bbox @@ -295,6 +301,7 @@ def run_camera_worker( "confidence": round(det.confidence, 3), "camera_location": camera_cfg.location, "crop_path": crop_path, + "bbox": norm_bbox, } if pet_result and pet_result.is_identified: payload["pet_id"] = pet_result.pet_id @@ -320,6 +327,7 @@ def run_camera_worker( confidence=round(det.confidence, 3), camera_location=camera_cfg.location, crop_path=None, + bbox=norm_bbox, ) elif category == "person": @@ -327,6 +335,7 @@ def run_camera_worker( Topics.camera_motion_start(camera_id), detection="person", confidence=round(det.confidence, 3), + bbox=norm_bbox, ) elif category == "vehicle": @@ -334,6 +343,7 @@ def run_camera_worker( Topics.camera_motion_start(camera_id), detection="vehicle", confidence=round(det.confidence, 3), + bbox=norm_bbox, ) # Heartbeat every 10 seconds diff --git a/vigilar/events/processor.py b/vigilar/events/processor.py index c09e77d..462f97b 100644 --- a/vigilar/events/processor.py +++ b/vigilar/events/processor.py @@ -183,6 +183,20 @@ class EventProcessor: else: return EventType.WILDLIFE_PASSIVE, Severity.INFO, camera_id + # Package detection + if suffix == "package/delivered": + return EventType.PACKAGE_DELIVERED, Severity.INFO, camera_id + if suffix == "package/reminder": + return EventType.PACKAGE_REMINDER, Severity.WARNING, camera_id + if suffix == "package/collected": + return EventType.PACKAGE_COLLECTED, Severity.INFO, camera_id + + # Visitor recognition + if suffix == "visitor/known": + return EventType.KNOWN_VISITOR, Severity.INFO, camera_id + if suffix == "visitor/unknown": + return EventType.UNKNOWN_VISITOR, Severity.INFO, camera_id + # Ignore heartbeats etc. return None, None, None diff --git a/vigilar/health/monitor.py b/vigilar/health/monitor.py index af24ab0..a4b5b01 100644 --- a/vigilar/health/monitor.py +++ b/vigilar/health/monitor.py @@ -73,6 +73,9 @@ class HealthMonitor: log.info("Health monitor started") last_disk_check = 0 last_mqtt_check = 0 + last_highlight_check = 0 + last_timelapse_check = 0 + highlight_generated_today = False while not shutdown: now = time.monotonic() @@ -91,6 +94,37 @@ class HealthMonitor: self._update_check(mqtt) last_mqtt_check = now + # Highlight reel generation (daily) + if hasattr(self._cfg, 'highlights') and self._cfg.highlights.enabled: + if now - last_highlight_check >= 60: + last_highlight_check = now + import datetime + current_time = datetime.datetime.now().strftime("%H:%M") + if current_time == self._cfg.highlights.generate_time and not highlight_generated_today: + highlight_generated_today = True + try: + from vigilar.highlights.reel import generate_daily_reel + from vigilar.storage.db import get_db_path, get_engine + engine = get_engine(get_db_path(self._cfg.system.data_dir)) + yesterday = datetime.date.today() - datetime.timedelta(days=1) + generate_daily_reel(engine, self._cfg.system.recordings_dir, + yesterday, self._cfg.highlights) + except Exception: + log.exception("Highlight reel generation failed") + if current_time == "00:00": + highlight_generated_today = False + + # Timelapse schedule check (every 60s) + if now - last_timelapse_check >= 60: + last_timelapse_check = now + try: + from vigilar.highlights.timelapse import check_schedules + from vigilar.storage.db import get_db_path, get_engine + engine = get_engine(get_db_path(self._cfg.system.data_dir)) + check_schedules(engine, self._cfg.system.recordings_dir) + except Exception: + log.exception("Timelapse schedule check failed") + self._publish_status() time.sleep(10)