mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-12-19 19:06:16 +01:00
MQTT: Birdseye enabled/disabled and mode change support (#8291)
* support enabled and mode change for birdseye via mqtt * resolve feedback from PR review https://github.com/blakeblackshear/frigate/pull/8291#discussion_r1370083613 * change birdseye mode topic to set * type in the docs * these commented out lines should have never been in here
This commit is contained in:
parent
859ab0e7fa
commit
36c1e00a6b
@ -220,3 +220,29 @@ Topic to turn the PTZ autotracker for a camera on and off. Expected values are `
|
|||||||
### `frigate/<camera_name>/ptz_autotracker/state`
|
### `frigate/<camera_name>/ptz_autotracker/state`
|
||||||
|
|
||||||
Topic with current state of the PTZ autotracker for a camera. Published values are `ON` and `OFF`.
|
Topic with current state of the PTZ autotracker for a camera. Published values are `ON` and `OFF`.
|
||||||
|
|
||||||
|
### `frigate/<camera_name>/birdseye/set`
|
||||||
|
|
||||||
|
Topic to turn Birdseye for a camera on and off. Expected values are `ON` and `OFF`. Birdseye mode
|
||||||
|
must be enabled in the configuration.
|
||||||
|
|
||||||
|
### `frigate/<camera_name>/birdseye/state`
|
||||||
|
|
||||||
|
Topic with current state of Birdseye for a camera. Published values are `ON` and `OFF`.
|
||||||
|
|
||||||
|
### `frigate/<camera_name>/birdseye_mode/set`
|
||||||
|
|
||||||
|
Topic to set Birdseye mode for a camera. Birdseye offers different modes to customize under which circumstances the camera is shown.
|
||||||
|
|
||||||
|
_Note: Changing the value from `CONTINUOUS` -> `MOTION | OBJECTS` will take up to 30 seconds for
|
||||||
|
the camera to be removed from the view._
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| ------------ | ----------------------------------------------------------------- |
|
||||||
|
| `CONTINUOUS` | Always included |
|
||||||
|
| `MOTION` | Show when detected motion within the last 30 seconds are included |
|
||||||
|
| `OBJECTS` | Shown if an active object tracked within the last 30 seconds |
|
||||||
|
|
||||||
|
### `frigate/<camera_name>/birdseye_mode/state`
|
||||||
|
|
||||||
|
Topic with current state of the Birdseye mode for a camera. Published values are `CONTINUOUS`, `MOTION`, `OBJECTS`.
|
||||||
|
@ -21,7 +21,7 @@ from frigate.comms.dispatcher import Communicator, Dispatcher
|
|||||||
from frigate.comms.inter_process import InterProcessCommunicator
|
from frigate.comms.inter_process import InterProcessCommunicator
|
||||||
from frigate.comms.mqtt import MqttClient
|
from frigate.comms.mqtt import MqttClient
|
||||||
from frigate.comms.ws import WebSocketClient
|
from frigate.comms.ws import WebSocketClient
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import BirdseyeModeEnum, FrigateConfig
|
||||||
from frigate.const import (
|
from frigate.const import (
|
||||||
CACHE_DIR,
|
CACHE_DIR,
|
||||||
CLIPS_DIR,
|
CLIPS_DIR,
|
||||||
@ -169,6 +169,20 @@ class FrigateApp:
|
|||||||
"process": None,
|
"process": None,
|
||||||
"audio_rms": mp.Value("d", 0.0), # type: ignore[typeddict-item]
|
"audio_rms": mp.Value("d", 0.0), # type: ignore[typeddict-item]
|
||||||
"audio_dBFS": mp.Value("d", 0.0), # type: ignore[typeddict-item]
|
"audio_dBFS": mp.Value("d", 0.0), # type: ignore[typeddict-item]
|
||||||
|
"birdseye_enabled": mp.Value( # type: ignore[typeddict-item]
|
||||||
|
# issue https://github.com/python/typeshed/issues/8799
|
||||||
|
# from mypy 0.981 onwards
|
||||||
|
"i",
|
||||||
|
self.config.cameras[camera_name].birdseye.enabled,
|
||||||
|
),
|
||||||
|
"birdseye_mode": mp.Value( # type: ignore[typeddict-item]
|
||||||
|
# issue https://github.com/python/typeshed/issues/8799
|
||||||
|
# from mypy 0.981 onwards
|
||||||
|
"i",
|
||||||
|
BirdseyeModeEnum.get_index(
|
||||||
|
self.config.cameras[camera_name].birdseye.mode.value
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
self.ptz_metrics[camera_name] = {
|
self.ptz_metrics[camera_name] = {
|
||||||
"ptz_autotracker_enabled": mp.Value( # type: ignore[typeddict-item]
|
"ptz_autotracker_enabled": mp.Value( # type: ignore[typeddict-item]
|
||||||
@ -455,6 +469,7 @@ class FrigateApp:
|
|||||||
args=(
|
args=(
|
||||||
self.config,
|
self.config,
|
||||||
self.video_output_queue,
|
self.video_output_queue,
|
||||||
|
self.camera_metrics,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
output_processor.daemon = True
|
output_processor.daemon = True
|
||||||
|
@ -4,7 +4,7 @@ import logging
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import BirdseyeModeEnum, FrigateConfig
|
||||||
from frigate.const import INSERT_MANY_RECORDINGS, REQUEST_REGION_GRID
|
from frigate.const import INSERT_MANY_RECORDINGS, REQUEST_REGION_GRID
|
||||||
from frigate.models import Recordings
|
from frigate.models import Recordings
|
||||||
from frigate.ptz.onvif import OnvifCommandEnum, OnvifController
|
from frigate.ptz.onvif import OnvifCommandEnum, OnvifController
|
||||||
@ -63,6 +63,8 @@ class Dispatcher:
|
|||||||
"motion_threshold": self._on_motion_threshold_command,
|
"motion_threshold": self._on_motion_threshold_command,
|
||||||
"recordings": self._on_recordings_command,
|
"recordings": self._on_recordings_command,
|
||||||
"snapshots": self._on_snapshots_command,
|
"snapshots": self._on_snapshots_command,
|
||||||
|
"birdseye": self._on_birdseye_command,
|
||||||
|
"birdseye_mode": self._on_birdseye_mode_command,
|
||||||
}
|
}
|
||||||
|
|
||||||
for comm in self.comms:
|
for comm in self.comms:
|
||||||
@ -296,3 +298,43 @@ class Dispatcher:
|
|||||||
logger.info(f"Setting ptz command to {command} for {camera_name}")
|
logger.info(f"Setting ptz command to {command} for {camera_name}")
|
||||||
except KeyError as k:
|
except KeyError as k:
|
||||||
logger.error(f"Invalid PTZ command {payload}: {k}")
|
logger.error(f"Invalid PTZ command {payload}: {k}")
|
||||||
|
|
||||||
|
def _on_birdseye_command(self, camera_name: str, payload: str) -> None:
|
||||||
|
"""Callback for birdseye topic."""
|
||||||
|
birdseye_settings = self.config.cameras[camera_name].birdseye
|
||||||
|
|
||||||
|
if payload == "ON":
|
||||||
|
if not self.camera_metrics[camera_name]["birdseye_enabled"].value:
|
||||||
|
logger.info(f"Turning on birdseye for {camera_name}")
|
||||||
|
self.camera_metrics[camera_name]["birdseye_enabled"].value = True
|
||||||
|
birdseye_settings.enabled = True
|
||||||
|
|
||||||
|
elif payload == "OFF":
|
||||||
|
if self.camera_metrics[camera_name]["birdseye_enabled"].value:
|
||||||
|
logger.info(f"Turning off birdseye for {camera_name}")
|
||||||
|
self.camera_metrics[camera_name]["birdseye_enabled"].value = False
|
||||||
|
birdseye_settings.enabled = False
|
||||||
|
|
||||||
|
self.publish(f"{camera_name}/birdseye/state", payload, retain=True)
|
||||||
|
|
||||||
|
def _on_birdseye_mode_command(self, camera_name: str, payload: str) -> None:
|
||||||
|
"""Callback for birdseye mode topic."""
|
||||||
|
|
||||||
|
if payload not in ["CONTINUOUS", "MOTION", "OBJECTS"]:
|
||||||
|
logger.info(f"Invalid birdseye_mode command: {payload}")
|
||||||
|
return
|
||||||
|
|
||||||
|
birdseye_config = self.config.cameras[camera_name].birdseye
|
||||||
|
if not birdseye_config.enabled:
|
||||||
|
logger.info(f"Birdseye mode not enabled for {camera_name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
new_birdseye_mode = BirdseyeModeEnum(payload.lower())
|
||||||
|
logger.info(f"Setting birdseye mode for {camera_name} to {new_birdseye_mode}")
|
||||||
|
|
||||||
|
# update the metric (need the mode converted to an int)
|
||||||
|
self.camera_metrics[camera_name][
|
||||||
|
"birdseye_mode"
|
||||||
|
].value = BirdseyeModeEnum.get_index(new_birdseye_mode)
|
||||||
|
|
||||||
|
self.publish(f"{camera_name}/birdseye_mode/state", payload, retain=True)
|
||||||
|
@ -89,6 +89,18 @@ class MqttClient(Communicator): # type: ignore[misc]
|
|||||||
"OFF",
|
"OFF",
|
||||||
retain=False,
|
retain=False,
|
||||||
)
|
)
|
||||||
|
self.publish(
|
||||||
|
f"{camera_name}/birdseye/state",
|
||||||
|
"ON" if camera.birdseye.enabled else "OFF",
|
||||||
|
retain=True,
|
||||||
|
)
|
||||||
|
self.publish(
|
||||||
|
f"{camera_name}/birdseye_mode/state",
|
||||||
|
camera.birdseye.mode.value.upper()
|
||||||
|
if camera.birdseye.enabled
|
||||||
|
else "OFF",
|
||||||
|
retain=True,
|
||||||
|
)
|
||||||
|
|
||||||
self.publish("available", "online", retain=True)
|
self.publish("available", "online", retain=True)
|
||||||
|
|
||||||
@ -160,6 +172,8 @@ class MqttClient(Communicator): # type: ignore[misc]
|
|||||||
"ptz_autotracker",
|
"ptz_autotracker",
|
||||||
"motion_threshold",
|
"motion_threshold",
|
||||||
"motion_contour_area",
|
"motion_contour_area",
|
||||||
|
"birdseye",
|
||||||
|
"birdseye_mode",
|
||||||
]
|
]
|
||||||
|
|
||||||
for name in self.config.cameras.keys():
|
for name in self.config.cameras.keys():
|
||||||
|
@ -501,6 +501,14 @@ class BirdseyeModeEnum(str, Enum):
|
|||||||
motion = "motion"
|
motion = "motion"
|
||||||
continuous = "continuous"
|
continuous = "continuous"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_index(cls, type):
|
||||||
|
return list(cls).index(type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, index):
|
||||||
|
return list(cls)[index]
|
||||||
|
|
||||||
|
|
||||||
class BirdseyeConfig(FrigateBaseModel):
|
class BirdseyeConfig(FrigateBaseModel):
|
||||||
enabled: bool = Field(default=True, title="Enable birdseye view.")
|
enabled: bool = Field(default=True, title="Enable birdseye view.")
|
||||||
|
@ -24,6 +24,7 @@ from ws4py.websocket import WebSocket
|
|||||||
|
|
||||||
from frigate.config import BirdseyeModeEnum, FrigateConfig
|
from frigate.config import BirdseyeModeEnum, FrigateConfig
|
||||||
from frigate.const import BASE_DIR, BIRDSEYE_PIPE
|
from frigate.const import BASE_DIR, BIRDSEYE_PIPE
|
||||||
|
from frigate.types import CameraMetricsTypes
|
||||||
from frigate.util.image import (
|
from frigate.util.image import (
|
||||||
SharedMemoryFrameManager,
|
SharedMemoryFrameManager,
|
||||||
copy_yuv_to_position,
|
copy_yuv_to_position,
|
||||||
@ -238,6 +239,7 @@ class BirdsEyeFrameManager:
|
|||||||
config: FrigateConfig,
|
config: FrigateConfig,
|
||||||
frame_manager: SharedMemoryFrameManager,
|
frame_manager: SharedMemoryFrameManager,
|
||||||
stop_event: mp.Event,
|
stop_event: mp.Event,
|
||||||
|
camera_metrics: dict[str, CameraMetricsTypes],
|
||||||
):
|
):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.mode = config.birdseye.mode
|
self.mode = config.birdseye.mode
|
||||||
@ -248,6 +250,7 @@ class BirdsEyeFrameManager:
|
|||||||
self.frame = np.ndarray(self.yuv_shape, dtype=np.uint8)
|
self.frame = np.ndarray(self.yuv_shape, dtype=np.uint8)
|
||||||
self.canvas = Canvas(width, height)
|
self.canvas = Canvas(width, height)
|
||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
|
self.camera_metrics = camera_metrics
|
||||||
|
|
||||||
# initialize the frame as black and with the Frigate logo
|
# initialize the frame as black and with the Frigate logo
|
||||||
self.blank_frame = np.zeros(self.yuv_shape, np.uint8)
|
self.blank_frame = np.zeros(self.yuv_shape, np.uint8)
|
||||||
@ -579,9 +582,25 @@ class BirdsEyeFrameManager:
|
|||||||
if not camera_config.enabled:
|
if not camera_config.enabled:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# get our metrics (sync'd across processes)
|
||||||
|
# which allows us to control it via mqtt (or any other dispatcher)
|
||||||
|
camera_metrics = self.camera_metrics[camera]
|
||||||
|
|
||||||
|
# disabling birdseye is a little tricky
|
||||||
|
if not camera_metrics["birdseye_enabled"].value:
|
||||||
|
# if we've rendered a frame (we have a value for last_active_frame)
|
||||||
|
# then we need to set it to zero
|
||||||
|
if self.cameras[camera]["last_active_frame"] > 0:
|
||||||
|
self.cameras[camera]["last_active_frame"] = 0
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
# get the birdseye mode state from camera metrics
|
||||||
|
birdseye_mode = BirdseyeModeEnum.get(camera_metrics["birdseye_mode"].value)
|
||||||
|
|
||||||
# update the last active frame for the camera
|
# update the last active frame for the camera
|
||||||
self.cameras[camera]["current_frame"] = frame_time
|
self.cameras[camera]["current_frame"] = frame_time
|
||||||
if self.camera_active(camera_config.mode, object_count, motion_count):
|
if self.camera_active(birdseye_mode, object_count, motion_count):
|
||||||
self.cameras[camera]["last_active_frame"] = frame_time
|
self.cameras[camera]["last_active_frame"] = frame_time
|
||||||
|
|
||||||
now = datetime.datetime.now().timestamp()
|
now = datetime.datetime.now().timestamp()
|
||||||
@ -605,7 +624,11 @@ class BirdsEyeFrameManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def output_frames(config: FrigateConfig, video_output_queue):
|
def output_frames(
|
||||||
|
config: FrigateConfig,
|
||||||
|
video_output_queue,
|
||||||
|
camera_metrics: dict[str, CameraMetricsTypes],
|
||||||
|
):
|
||||||
threading.current_thread().name = "output"
|
threading.current_thread().name = "output"
|
||||||
setproctitle("frigate.output")
|
setproctitle("frigate.output")
|
||||||
|
|
||||||
@ -661,7 +684,10 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
|||||||
config.birdseye.restream,
|
config.birdseye.restream,
|
||||||
)
|
)
|
||||||
broadcasters["birdseye"] = BroadcastThread(
|
broadcasters["birdseye"] = BroadcastThread(
|
||||||
"birdseye", converters["birdseye"], websocket_server, stop_event
|
"birdseye",
|
||||||
|
converters["birdseye"],
|
||||||
|
websocket_server,
|
||||||
|
stop_event,
|
||||||
)
|
)
|
||||||
|
|
||||||
websocket_thread.start()
|
websocket_thread.start()
|
||||||
@ -669,7 +695,9 @@ def output_frames(config: FrigateConfig, video_output_queue):
|
|||||||
for t in broadcasters.values():
|
for t in broadcasters.values():
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
birdseye_manager = BirdsEyeFrameManager(config, frame_manager, stop_event)
|
birdseye_manager = BirdsEyeFrameManager(
|
||||||
|
config, frame_manager, stop_event, camera_metrics
|
||||||
|
)
|
||||||
|
|
||||||
if config.birdseye.restream:
|
if config.birdseye.restream:
|
||||||
birdseye_buffer = frame_manager.create(
|
birdseye_buffer = frame_manager.create(
|
||||||
|
@ -25,6 +25,8 @@ class CameraMetricsTypes(TypedDict):
|
|||||||
skipped_fps: Synchronized
|
skipped_fps: Synchronized
|
||||||
audio_rms: Synchronized
|
audio_rms: Synchronized
|
||||||
audio_dBFS: Synchronized
|
audio_dBFS: Synchronized
|
||||||
|
birdseye_enabled: Synchronized
|
||||||
|
birdseye_mode: Synchronized
|
||||||
|
|
||||||
|
|
||||||
class PTZMetricsTypes(TypedDict):
|
class PTZMetricsTypes(TypedDict):
|
||||||
|
Loading…
Reference in New Issue
Block a user