mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-21 03:41:55 +03:00
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.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
"""Validator tests for `motion.source=onvif` interactions with
|
|
`onvif.events.enabled` and `motion.enabled`. Exercises `verify_motion_and_detect`
|
|
directly so we don't need the full FrigateConfig path (which mounts /config)."""
|
|
|
|
import unittest
|
|
|
|
from frigate.config.config import verify_motion_and_detect
|
|
|
|
|
|
class _Dummy:
|
|
"""Light shim for the nested config attributes the validator reads."""
|
|
|
|
def __init__(self, **kw):
|
|
for k, v in kw.items():
|
|
setattr(self, k, v)
|
|
|
|
|
|
def _camera(*, name, detect, motion, onvif):
|
|
return _Dummy(name=name, detect=detect, motion=motion, onvif=onvif)
|
|
|
|
|
|
class TestVerifyMotionAndDetect(unittest.TestCase):
|
|
def test_internal_motion_with_detect_passes(self):
|
|
cam = _camera(
|
|
name="c",
|
|
detect=_Dummy(enabled=True),
|
|
motion=_Dummy(enabled=True, source="internal"),
|
|
onvif=_Dummy(events=_Dummy(enabled=False)),
|
|
)
|
|
# No exception.
|
|
self.assertIsNone(verify_motion_and_detect(cam))
|
|
|
|
def test_detect_with_motion_disabled_rejected(self):
|
|
cam = _camera(
|
|
name="c",
|
|
detect=_Dummy(enabled=True),
|
|
motion=_Dummy(enabled=False, source="internal"),
|
|
onvif=_Dummy(events=_Dummy(enabled=False)),
|
|
)
|
|
with self.assertRaisesRegex(ValueError, "object detection requires motion"):
|
|
verify_motion_and_detect(cam)
|
|
|
|
def test_source_onvif_requires_events_enabled(self):
|
|
cam = _camera(
|
|
name="c",
|
|
detect=_Dummy(enabled=True),
|
|
motion=_Dummy(enabled=False, source="onvif"),
|
|
onvif=_Dummy(events=_Dummy(enabled=False)),
|
|
)
|
|
with self.assertRaisesRegex(ValueError, "onvif.events.enabled is false"):
|
|
verify_motion_and_detect(cam)
|
|
|
|
def test_source_onvif_with_events_passes_even_with_motion_disabled(self):
|
|
cam = _camera(
|
|
name="c",
|
|
detect=_Dummy(enabled=True),
|
|
motion=_Dummy(enabled=False, source="onvif"),
|
|
onvif=_Dummy(events=_Dummy(enabled=True)),
|
|
)
|
|
self.assertIsNone(verify_motion_and_detect(cam))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|