frigate/frigate/config/camera/onvif.py
Dmitry Ilyin b420efdebd Native ONVIF cell-motion ingest
Adds a per-camera ONVIF subscriber that lets cameras with native
hardware motion detection (e.g. OpenIPC firmware for HiSilicon,
Ingenic and SigmaStar SoCs; many ONVIF Profile-M devices) replace
Frigate's per-frame CPU motion analysis. Two standard ONVIF
transports are consumed in parallel:

- WS-BaseNotification PullPoint for the binary motion state
  (tns1:RuleEngine/CellMotionDetector/Motion IsMotion=true|false,
   with tns1:VideoSource/MotionAlarm State=true|false accepted as
   a fallback for cameras that only publish the legacy topic).
- RTSP analytics metadata stream (application/vnd.onvif.metadata)
  for the per-frame cell grid (tt:MotionInCells, base64 + PackBits
  bit-packed bitmap). Cell layout is discovered once at startup via
  AnalyticsService.GetAnalyticsModules and the camera's CellLayout
  transformation is used to map cells to detect-frame pixel
  rectangles via connected-components.

New config:
  onvif.events.{enabled, subscription_timeout, use_metadata_stream}
  motion.source: internal (default) | onvif

When motion.source: onvif, ImprovedMotionDetector is skipped and
motion_boxes come from the camera. Internal motion remains the
default; the new path is fully opt-in.
2026-05-30 19:43:33 +03:00

165 lines
5.9 KiB
Python

from enum import Enum
from typing import Optional, Union
from pydantic import Field, field_validator
from ..base import FrigateBaseModel
from ..env import EnvString
from .objects import DEFAULT_TRACKED_OBJECTS
__all__ = [
"OnvifConfig",
"OnvifEventsConfig",
"PtzAutotrackConfig",
"ZoomingModeEnum",
]
class ZoomingModeEnum(str, Enum):
disabled = "disabled"
absolute = "absolute"
relative = "relative"
class PtzAutotrackConfig(FrigateBaseModel):
enabled: bool = Field(
default=False,
title="Enable Autotracking",
description="Enable or disable automatic PTZ camera tracking of detected objects.",
)
calibrate_on_startup: bool = Field(
default=False,
title="Calibrate on start",
description="Measure PTZ motor speeds on startup to improve tracking accuracy. Frigate will update config with movement_weights after calibration.",
)
zooming: ZoomingModeEnum = Field(
default=ZoomingModeEnum.disabled,
title="Zoom mode",
description="Control zoom behavior: disabled (pan/tilt only), absolute (most compatible), or relative (concurrent pan/tilt/zoom).",
)
zoom_factor: float = Field(
default=0.3,
title="Zoom factor",
description="Control zoom level on tracked objects. Lower values keep more scene in view; higher values zoom in closer but may lose tracking. Values between 0.1 and 0.75.",
ge=0.1,
le=0.75,
)
track: list[str] = Field(
default=DEFAULT_TRACKED_OBJECTS,
title="Tracked objects",
description="List of object types that should trigger autotracking.",
)
required_zones: list[str] = Field(
default_factory=list,
title="Required zones",
description="Objects must enter one of these zones before autotracking begins.",
)
return_preset: str = Field(
default="home",
title="Return preset",
description="ONVIF preset name configured in camera firmware to return to after tracking ends.",
)
timeout: int = Field(
default=10,
title="Return timeout",
description="Wait this many seconds after losing tracking before returning camera to preset position.",
)
movement_weights: Optional[Union[str, list[str]]] = Field(
default_factory=list,
title="Movement weights",
description="Calibration values automatically generated by camera calibration. Do not modify manually.",
)
enabled_in_config: Optional[bool] = Field(
default=None,
title="Original autotrack state",
description="Internal field to track whether autotracking was enabled in configuration.",
)
@field_validator("movement_weights", mode="before")
@classmethod
def validate_weights(cls, v):
if v is None:
return None
if isinstance(v, str):
weights = list(map(str, map(float, v.split(","))))
elif isinstance(v, list):
weights = [str(float(val)) for val in v]
else:
raise ValueError("Invalid type for movement_weights")
if len(weights) != 6:
raise ValueError(
"movement_weights must have exactly 6 floats, remove this line from your config and run autotracking calibration"
)
return weights
class OnvifEventsConfig(FrigateBaseModel):
enabled: bool = Field(
default=False,
title="Enable ONVIF events",
description="Subscribe to the camera's ONVIF cell-motion notifications and use them as Frigate's motion signal.",
)
subscription_timeout: int = Field(
default=60,
ge=10,
le=600,
title="Subscription timeout",
description="Seconds before the PullPoint subscription expires and is renewed.",
)
use_metadata_stream: bool = Field(
default=True,
title="Use metadata stream",
description="Open the ONVIF analytics RTSP metadata stream to receive per-cell motion coordinates. Falls back to a full-frame box when disabled or when the camera does not advertise the track.",
)
class OnvifConfig(FrigateBaseModel):
host: EnvString = Field(
default="",
title="ONVIF host",
description="Host (and optional scheme) for the ONVIF service for this camera.",
)
port: int = Field(
default=8000,
title="ONVIF port",
description="Port number for the ONVIF service.",
)
user: Optional[EnvString] = Field(
default=None,
title="ONVIF username",
description="Username for ONVIF authentication; some devices require admin user for ONVIF.",
)
password: Optional[EnvString] = Field(
default=None,
title="ONVIF password",
description="Password for ONVIF authentication.",
)
tls_insecure: bool = Field(
default=False,
title="Disable TLS verify",
description="Skip TLS verification and disable digest auth for ONVIF (unsafe; use in safe networks only).",
)
profile: Optional[str] = Field(
default=None,
title="ONVIF profile",
description="Specific ONVIF media profile to use for PTZ control, matched by token or name. If not set, the first profile with valid PTZ configuration is selected automatically.",
)
autotracking: PtzAutotrackConfig = Field(
default_factory=PtzAutotrackConfig,
title="Autotracking",
description="Automatically track moving objects and keep them centered in the frame using PTZ camera movements.",
)
events: OnvifEventsConfig = Field(
default_factory=OnvifEventsConfig,
title="ONVIF events",
description="Consume camera-side ONVIF motion notifications instead of Frigate's CPU motion detector.",
)
ignore_time_mismatch: bool = Field(
default=False,
title="Ignore time mismatch",
description="Ignore time synchronization differences between camera and Frigate server for ONVIF communication.",
)