From de244d6873398ea38d66614a73b5536297bf6bb3 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 15 May 2022 06:03:33 -0600 Subject: [PATCH 1/4] Send mqtt message when motion is detected (#3152) * Send mqtt message when motion is detected * Use object processing instead of passing mqtt client around * Cleanup * Formatting * add comment * Make off delay configurable. * Handle updating each camera based on config off delay * Formatting * Update docker-compose.yml * Fix processing issue * Update mqtt docs * Update main config docs * Make sure multiple True values aren't published for the same motion * Make sure multiple True values aren't published for the same motion * Update payload to fit existing HA standard values * Update docs to fit new values * Update docs * Update motion topic * Use datetime.datetime and remove unused imports * Cast to int * Clarify motion detector behavior in docs * Fix typo Co-authored-by: Blake Blackshear --- docs/docs/configuration/index.md | 4 +++- docs/docs/integrations/mqtt.md | 6 +++++ frigate/config.py | 4 ++++ frigate/object_processing.py | 39 ++++++++++++++++++++++++++------ 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index 06734c9d6..5fc9c57b8 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -246,12 +246,14 @@ motion: # Enables dynamic contrast improvement. This should help improve night detections at the cost of making motion detection more sensitive # for daytime. improve_contrast: False + # Optional: Delay when updating camera motion through MQTT from ON -> OFF (default: shown below). + mqtt_off_delay: 30 # Optional: Record configuration # NOTE: Can be overridden at the camera level record: # Optional: Enable recording (default: shown below) - # WARNING: If recording is disabled in the config, turning it on via + # WARNING: If recording is disabled in the config, turning it on via # the UI or MQTT later will have no effect. # WARNING: Frigate does not currently support limiting recordings based # on available disk space automatically. If using recordings, diff --git a/docs/docs/integrations/mqtt.md b/docs/docs/integrations/mqtt.md index cdf4fb637..fca590b61 100644 --- a/docs/docs/integrations/mqtt.md +++ b/docs/docs/integrations/mqtt.md @@ -123,6 +123,12 @@ Topic with current state of snapshots for a camera. Published values are `ON` an Topic to turn motion detection for a camera on and off. Expected values are `ON` and `OFF`. NOTE: Turning off motion detection will fail if detection is not disabled. +### `frigate//motion` + +Whether camera_name is currently detecting motion. Expected values are `ON` and `OFF`. +NOTE: After motion is initially detected, `ON` will be set until no motion has +been detected for `mqtt_off_delay` seconds (30 by default). + ### `frigate//motion/state` Topic with current state of motion detection for a camera. Published values are `ON` and `OFF`. diff --git a/frigate/config.py b/frigate/config.py index 58b064214..863538504 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -134,6 +134,10 @@ class MotionConfig(FrigateBaseModel): mask: Union[str, List[str]] = Field( default="", title="Coordinates polygon for the motion mask." ) + mqtt_off_delay: int = Field( + default=30, + title="Delay for updating MQTT with no motion detected.", + ) class RuntimeMotionConfig(MotionConfig): diff --git a/frigate/object_processing.py b/frigate/object_processing.py index a7509ef49..4206021e2 100644 --- a/frigate/object_processing.py +++ b/frigate/object_processing.py @@ -1,29 +1,24 @@ import base64 -import copy import datetime -import hashlib -import itertools import json import logging import os import queue import threading -import time from collections import Counter, defaultdict -from statistics import mean, median +from statistics import median from typing import Callable import cv2 import numpy as np from frigate.config import CameraConfig, SnapshotsConfig, RecordConfig, FrigateConfig -from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR +from frigate.const import CLIPS_DIR from frigate.util import ( SharedMemoryFrameManager, calculate_region, draw_box_with_label, draw_timestamp, - load_labels, ) logger = logging.getLogger(__name__) @@ -652,6 +647,7 @@ class TrackedObjectProcessor(threading.Thread): self.stop_event = stop_event self.camera_states: dict[str, CameraState] = {} self.frame_manager = SharedMemoryFrameManager() + self.last_motion_updates: dict[str, int] = {} def start(camera, obj: TrackedObject, current_frame_time): self.event_queue.put(("start", camera, obj.to_dict())) @@ -844,6 +840,33 @@ class TrackedObjectProcessor(threading.Thread): return True + def should_mqtt_motion(self, camera, motion_boxes): + # publish if motion is currently being detected + if motion_boxes: + # only send True if motion hasn't been detected recently + if self.last_motion_updates.get(camera, 0) == 0: + self.client.publish( + f"{self.topic_prefix}/{camera}/motion", + "ON", + retain=False, + ) + + # always updated latest motion + self.last_motion_updates[camera] = int(datetime.datetime.now().timestamp()) + elif not motion_boxes and self.last_motion_updates.get(camera, 0) != 0: + mqtt_delay = self.config.cameras[camera].motion.mqtt_off_delay + now = int(datetime.datetime.now().timestamp()) + + # If no motion, make sure the off_delay has passed + if now - self.last_motion_updates.get(camera, 0) >= mqtt_delay: + self.client.publish( + f"{self.topic_prefix}/{camera}/motion", + "OFF", + retain=False, + ) + # reset the last_motion so redundant `off` commands aren't sent + self.last_motion_updates[camera] = 0 + def get_best(self, camera, label): # TODO: need a lock here camera_state = self.camera_states[camera] @@ -879,6 +902,8 @@ class TrackedObjectProcessor(threading.Thread): frame_time, current_tracked_objects, motion_boxes, regions ) + self.should_mqtt_motion(camera, motion_boxes) + tracked_objects = [ o.to_dict() for o in camera_state.tracked_objects.values() ] From efe3f9622380d726390751f2bf6831f47523af7f Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Sun, 15 May 2022 06:42:18 -0500 Subject: [PATCH 2/4] update hwaccel docs for RPi --- docs/docs/configuration/hardware_acceleration.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/docs/configuration/hardware_acceleration.md b/docs/docs/configuration/hardware_acceleration.md index ecdc038b4..3514b44ef 100644 --- a/docs/docs/configuration/hardware_acceleration.md +++ b/docs/docs/configuration/hardware_acceleration.md @@ -5,22 +5,11 @@ title: Hardware Acceleration It is recommended to update your configuration to enable hardware accelerated decoding in ffmpeg. Depending on your system, these parameters may not be compatible. More information on hardware accelerated decoding for ffmpeg can be found here: https://trac.ffmpeg.org/wiki/HWAccelIntro -### Raspberry Pi 3/4 (32-bit OS) +### Raspberry Pi 3/4 Ensure you increase the allocated RAM for your GPU to at least 128 (raspi-config > Performance Options > GPU Memory). **NOTICE**: If you are using the addon, you may need to turn off `Protection mode` for hardware acceleration. -```yaml -ffmpeg: - hwaccel_args: - - -c:v - - h264_mmal -``` - -### Raspberry Pi 3/4 (64-bit OS) - -**NOTICE**: If you are using the addon, you may need to turn off `Protection mode` for hardware acceleration. - ```yaml ffmpeg: hwaccel_args: From 7c8142174ec69a9527fce772730d99d837573431 Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Sun, 15 May 2022 07:45:04 -0500 Subject: [PATCH 3/4] mqtt motion adjustments --- frigate/object_processing.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/frigate/object_processing.py b/frigate/object_processing.py index 4206021e2..3712d517c 100644 --- a/frigate/object_processing.py +++ b/frigate/object_processing.py @@ -647,7 +647,7 @@ class TrackedObjectProcessor(threading.Thread): self.stop_event = stop_event self.camera_states: dict[str, CameraState] = {} self.frame_manager = SharedMemoryFrameManager() - self.last_motion_updates: dict[str, int] = {} + self.last_motion_detected: dict[str, float] = {} def start(camera, obj: TrackedObject, current_frame_time): self.event_queue.put(("start", camera, obj.to_dict())) @@ -840,11 +840,11 @@ class TrackedObjectProcessor(threading.Thread): return True - def should_mqtt_motion(self, camera, motion_boxes): + def update_mqtt_motion(self, camera, frame_time, motion_boxes): # publish if motion is currently being detected if motion_boxes: - # only send True if motion hasn't been detected recently - if self.last_motion_updates.get(camera, 0) == 0: + # only send ON if motion isn't already active + if self.last_motion_detected.get(camera, 0) == 0: self.client.publish( f"{self.topic_prefix}/{camera}/motion", "ON", @@ -852,20 +852,19 @@ class TrackedObjectProcessor(threading.Thread): ) # always updated latest motion - self.last_motion_updates[camera] = int(datetime.datetime.now().timestamp()) - elif not motion_boxes and self.last_motion_updates.get(camera, 0) != 0: + self.last_motion_detected[camera] = frame_time + elif self.last_motion_detected.get(camera, 0) > 0: mqtt_delay = self.config.cameras[camera].motion.mqtt_off_delay - now = int(datetime.datetime.now().timestamp()) # If no motion, make sure the off_delay has passed - if now - self.last_motion_updates.get(camera, 0) >= mqtt_delay: + if frame_time - self.last_motion_detected.get(camera, 0) >= mqtt_delay: self.client.publish( f"{self.topic_prefix}/{camera}/motion", "OFF", retain=False, ) # reset the last_motion so redundant `off` commands aren't sent - self.last_motion_updates[camera] = 0 + self.last_motion_detected[camera] = 0 def get_best(self, camera, label): # TODO: need a lock here @@ -902,7 +901,7 @@ class TrackedObjectProcessor(threading.Thread): frame_time, current_tracked_objects, motion_boxes, regions ) - self.should_mqtt_motion(camera, motion_boxes) + self.update_mqtt_motion(camera, frame_time, motion_boxes) tracked_objects = [ o.to_dict() for o in camera_state.tracked_objects.values() From d2c3cdcf04619f3745869ab2f191256eb7a4c7f1 Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Mon, 16 May 2022 06:49:47 -0500 Subject: [PATCH 4/4] don't add 16x on every render --- web/src/components/VideoPlayer.jsx | 31 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/web/src/components/VideoPlayer.jsx b/web/src/components/VideoPlayer.jsx index df75ad5e4..2ef534464 100644 --- a/web/src/components/VideoPlayer.jsx +++ b/web/src/components/VideoPlayer.jsx @@ -6,24 +6,25 @@ import 'videojs-seek-buttons'; import 'video.js/dist/video-js.css'; import 'videojs-seek-buttons/dist/videojs-seek-buttons.css'; -const defaultOptions = { - controls: true, - playbackRates: [0.5, 1, 2, 4, 8], - fluid: true, -}; -const defaultSeekOptions = { - forward: 30, - back: 10, -}; - export default function VideoPlayer({ children, options, seekOptions = {}, onReady = () => {}, onDispose = () => {} }) { const playerRef = useRef(); - - if (!videojs.browser.IS_FIREFOX) { - defaultOptions.playbackRates.push(16); - } - + useEffect(() => { + const defaultOptions = { + controls: true, + playbackRates: [0.5, 1, 2, 4, 8], + fluid: true, + }; + + const defaultSeekOptions = { + forward: 30, + back: 10, + }; + + if (!videojs.browser.IS_FIREFOX) { + defaultOptions.playbackRates.push(16); + } + const player = videojs(playerRef.current, { ...defaultOptions, ...options }, () => { onReady(player); });