Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MQTT topic for active autotracking #8419

Merged
merged 4 commits into from Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/docs/integrations/mqtt.md
Expand Up @@ -221,6 +221,10 @@ Topic to turn the PTZ autotracker for a camera on and off. Expected values are `

Topic with current state of the PTZ autotracker for a camera. Published values are `ON` and `OFF`.

### `frigate/<camera_name>/ptz_autotracker/active`

Topic to determine if PTZ autotracker is actively tracking an object. 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
Expand Down
6 changes: 4 additions & 2 deletions frigate/app.py
Expand Up @@ -191,7 +191,8 @@ def init_config(self) -> None:
"i",
self.config.cameras[camera_name].onvif.autotracking.enabled,
),
"ptz_stopped": mp.Event(),
"ptz_tracking_active": mp.Event(),
"ptz_motor_stopped": mp.Event(),
"ptz_reset": mp.Event(),
"ptz_start_time": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
Expand All @@ -212,7 +213,7 @@ def init_config(self) -> None:
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
}
self.ptz_metrics[camera_name]["ptz_stopped"].set()
self.ptz_metrics[camera_name]["ptz_motor_stopped"].set()
self.feature_metrics[camera_name] = {
"audio_enabled": mp.Value( # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
Expand Down Expand Up @@ -444,6 +445,7 @@ def start_ptz_autotracker(self) -> None:
self.config,
self.onvif_controller,
self.ptz_metrics,
self.dispatcher,
self.stop_event,
)
self.ptz_autotracker_thread.start()
Expand Down
44 changes: 29 additions & 15 deletions frigate/ptz/autotrack.py
Expand Up @@ -18,6 +18,7 @@
TranslationTransformationGetter,
)

from frigate.comms.dispatcher import Dispatcher
from frigate.config import CameraConfig, FrigateConfig, ZoomingModeEnum
from frigate.const import (
AUTOTRACKING_MAX_AREA_RATIO,
Expand Down Expand Up @@ -144,11 +145,12 @@ def __init__(
config: FrigateConfig,
onvif: OnvifController,
ptz_metrics: dict[str, PTZMetricsTypes],
dispatcher: Dispatcher,
stop_event: MpEvent,
) -> None:
threading.Thread.__init__(self)
self.name = "ptz_autotracker"
self.ptz_autotracker = PtzAutoTracker(config, onvif, ptz_metrics)
self.ptz_autotracker = PtzAutoTracker(config, onvif, ptz_metrics, dispatcher)
self.stop_event = stop_event
self.config = config

Expand All @@ -175,10 +177,12 @@ def __init__(
config: FrigateConfig,
onvif: OnvifController,
ptz_metrics: PTZMetricsTypes,
dispatcher: Dispatcher,
) -> None:
self.config = config
self.onvif = onvif
self.ptz_metrics = ptz_metrics
self.dispatcher = dispatcher
self.tracked_object: dict[str, object] = {}
self.tracked_object_history: dict[str, object] = {}
self.tracked_object_metrics: dict[str, object] = {}
Expand Down Expand Up @@ -338,7 +342,7 @@ def _calibrate_camera(self, camera):
1,
)

while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)

zoom_out_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
Expand All @@ -349,7 +353,7 @@ def _calibrate_camera(self, camera):
1,
)

while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)

zoom_in_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
Expand All @@ -367,7 +371,7 @@ def _calibrate_camera(self, camera):
1,
)

while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)

zoom_out_values.append(
Expand All @@ -383,7 +387,7 @@ def _calibrate_camera(self, camera):
1,
)

while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)

zoom_in_values.append(
Expand All @@ -406,10 +410,10 @@ def _calibrate_camera(self, camera):
self.config.cameras[camera].onvif.autotracking.return_preset.lower(),
)
self.ptz_metrics[camera]["ptz_reset"].set()
self.ptz_metrics[camera]["ptz_stopped"].clear()
self.ptz_metrics[camera]["ptz_motor_stopped"].clear()

# Wait until the camera finishes moving
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)

for step in range(num_steps):
Expand All @@ -420,7 +424,7 @@ def _calibrate_camera(self, camera):
self.onvif._move_relative(camera, pan, tilt, 0, 1)

# Wait until the camera finishes moving
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)
stop_time = time.time()

Expand All @@ -438,10 +442,10 @@ def _calibrate_camera(self, camera):
self.config.cameras[camera].onvif.autotracking.return_preset.lower(),
)
self.ptz_metrics[camera]["ptz_reset"].set()
self.ptz_metrics[camera]["ptz_stopped"].clear()
self.ptz_metrics[camera]["ptz_motor_stopped"].clear()

# Wait until the camera finishes moving
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)

logger.info(
Expand Down Expand Up @@ -606,7 +610,9 @@ def _process_move_queue(self, camera):
self.onvif._move_relative(camera, pan, tilt, 0, 1)

# Wait until the camera finishes moving
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
while not self.ptz_metrics[camera][
"ptz_motor_stopped"
].is_set():
self.onvif.get_camera_status(camera)

if (
Expand All @@ -616,7 +622,7 @@ def _process_move_queue(self, camera):
self.onvif._zoom_absolute(camera, zoom, 1)

# Wait until the camera finishes moving
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)

if self.config.cameras[camera].onvif.autotracking.movement_weights:
Expand Down Expand Up @@ -1118,6 +1124,10 @@ def autotrack_object(self, camera, obj):
logger.debug(
f"{camera}: New object: {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}"
)
self.ptz_metrics[camera]["ptz_tracking_active"].set()
self.dispatcher.publish(
f"{camera}/ptz_autotracker/active", "ON", retain=False
)
self.tracked_object[camera] = obj

self.tracked_object_history[camera].append(copy.deepcopy(obj.obj_data))
Expand Down Expand Up @@ -1220,7 +1230,7 @@ def camera_maintenance(self, camera):
if not self.autotracker_init[camera]:
self._autotracker_setup(self.config.cameras[camera], camera)
# regularly update camera status
if not self.ptz_metrics[camera]["ptz_stopped"].is_set():
if not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)

# return to preset if tracking is over
Expand All @@ -1243,7 +1253,7 @@ def camera_maintenance(self, camera):
while not self.move_queues[camera].empty():
self.move_queues[camera].get()

self.ptz_metrics[camera]["ptz_stopped"].wait()
self.ptz_metrics[camera]["ptz_motor_stopped"].wait()
logger.debug(
f"{camera}: Time is {self.ptz_metrics[camera]['ptz_frame_time'].value}, returning to preset: {autotracker_config.return_preset}"
)
Expand All @@ -1253,7 +1263,11 @@ def camera_maintenance(self, camera):
)

# update stored zoom level from preset
if not self.ptz_metrics[camera]["ptz_stopped"].is_set():
if not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
self.onvif.get_camera_status(camera)

self.ptz_metrics[camera]["ptz_tracking_active"].clear()
self.dispatcher.publish(
f"{camera}/ptz_autotracker/active", "OFF", retain=False
)
self.ptz_metrics[camera]["ptz_reset"].set()
16 changes: 8 additions & 8 deletions frigate/ptz/onvif.py
Expand Up @@ -299,7 +299,7 @@ def _move_relative(self, camera_name: str, pan, tilt, zoom, speed) -> None:
return

self.cams[camera_name]["active"] = True
self.ptz_metrics[camera_name]["ptz_stopped"].clear()
self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear()
logger.debug(
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
)
Expand Down Expand Up @@ -366,7 +366,7 @@ def _move_to_preset(self, camera_name: str, preset: str) -> None:
return

self.cams[camera_name]["active"] = True
self.ptz_metrics[camera_name]["ptz_stopped"].clear()
self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear()
self.ptz_metrics[camera_name]["ptz_start_time"].value = 0
self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0
move_request = self.cams[camera_name]["move_request"]
Expand Down Expand Up @@ -413,7 +413,7 @@ def _zoom_absolute(self, camera_name: str, zoom, speed) -> None:
return

self.cams[camera_name]["active"] = True
self.ptz_metrics[camera_name]["ptz_stopped"].clear()
self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear()
logger.debug(
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
)
Expand Down Expand Up @@ -543,8 +543,8 @@ def get_camera_status(self, camera_name: str) -> None:
zoom_status is None or zoom_status.lower() == "idle"
):
self.cams[camera_name]["active"] = False
if not self.ptz_metrics[camera_name]["ptz_stopped"].is_set():
self.ptz_metrics[camera_name]["ptz_stopped"].set()
if not self.ptz_metrics[camera_name]["ptz_motor_stopped"].is_set():
self.ptz_metrics[camera_name]["ptz_motor_stopped"].set()

logger.debug(
f"{camera_name}: PTZ stop time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
Expand All @@ -555,8 +555,8 @@ def get_camera_status(self, camera_name: str) -> None:
]["ptz_frame_time"].value
else:
self.cams[camera_name]["active"] = True
if self.ptz_metrics[camera_name]["ptz_stopped"].is_set():
self.ptz_metrics[camera_name]["ptz_stopped"].clear()
if self.ptz_metrics[camera_name]["ptz_motor_stopped"].is_set():
self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear()

logger.debug(
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
Expand Down Expand Up @@ -586,7 +586,7 @@ def get_camera_status(self, camera_name: str) -> None:

# some hikvision cams won't update MoveStatus, so warn if it hasn't changed
if (
not self.ptz_metrics[camera_name]["ptz_stopped"].is_set()
not self.ptz_metrics[camera_name]["ptz_motor_stopped"].is_set()
and not self.ptz_metrics[camera_name]["ptz_reset"].is_set()
and self.ptz_metrics[camera_name]["ptz_start_time"].value != 0
and self.ptz_metrics[camera_name]["ptz_frame_time"].value
Expand Down
3 changes: 2 additions & 1 deletion frigate/types.py
Expand Up @@ -31,7 +31,8 @@ class CameraMetricsTypes(TypedDict):

class PTZMetricsTypes(TypedDict):
ptz_autotracker_enabled: Synchronized
ptz_stopped: Event
ptz_tracking_active: Event
ptz_motor_stopped: Event
ptz_reset: Event
ptz_start_time: Synchronized
ptz_stop_time: Synchronized
Expand Down