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 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee 2026-04-03 19:18:38 -04:00
parent 3289f874ab
commit 4873d36194
4 changed files with 59 additions and 0 deletions

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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)