mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 02:29:19 +03:00
add enabled field to camera profiles for enabling/disabling cameras
This commit is contained in:
parent
7c6926d1e6
commit
d15fc4e58e
@ -24,6 +24,7 @@ class CameraProfileConfig(FrigateBaseModel):
|
||||
explicitly-set fields are used as overrides via exclude_unset.
|
||||
"""
|
||||
|
||||
enabled: Optional[bool] = None
|
||||
audio: Optional[AudioConfig] = None
|
||||
birdseye: Optional[BirdseyeCameraConfig] = None
|
||||
detect: Optional[DetectConfig] = None
|
||||
|
||||
@ -43,12 +43,14 @@ class ProfileManager:
|
||||
self.config: FrigateConfig = config
|
||||
self.config_updater = config_updater
|
||||
self._base_configs: dict[str, dict[str, dict]] = {}
|
||||
self._base_enabled: dict[str, bool] = {}
|
||||
self._snapshot_base_configs()
|
||||
|
||||
def _snapshot_base_configs(self) -> None:
|
||||
"""Snapshot each camera's current section configs as the base."""
|
||||
"""Snapshot each camera's current section configs and enabled state."""
|
||||
for cam_name, cam_config in self.config.cameras.items():
|
||||
self._base_configs[cam_name] = {}
|
||||
self._base_enabled[cam_name] = cam_config.enabled
|
||||
for section in PROFILE_SECTION_UPDATES:
|
||||
section_config = getattr(cam_config, section, None)
|
||||
if section_config is not None:
|
||||
@ -97,6 +99,13 @@ class ProfileManager:
|
||||
def _reset_to_base(self, changed: dict[str, set[str]]) -> None:
|
||||
"""Reset all cameras to their base (no-profile) config."""
|
||||
for cam_name, cam_config in self.config.cameras.items():
|
||||
# Restore enabled state
|
||||
base_enabled = self._base_enabled.get(cam_name)
|
||||
if base_enabled is not None and cam_config.enabled != base_enabled:
|
||||
cam_config.enabled = base_enabled
|
||||
changed.setdefault(cam_name, set()).add("enabled")
|
||||
|
||||
# Restore section configs
|
||||
base = self._base_configs.get(cam_name, {})
|
||||
for section in PROFILE_SECTION_UPDATES:
|
||||
base_data = base.get(section)
|
||||
@ -122,6 +131,11 @@ class ProfileManager:
|
||||
if profile is None:
|
||||
continue
|
||||
|
||||
# Apply enabled override
|
||||
if profile.enabled is not None and cam_config.enabled != profile.enabled:
|
||||
cam_config.enabled = profile.enabled
|
||||
changed.setdefault(cam_name, set()).add("enabled")
|
||||
|
||||
base = self._base_configs.get(cam_name, {})
|
||||
|
||||
for section in PROFILE_SECTION_UPDATES:
|
||||
@ -152,6 +166,15 @@ class ProfileManager:
|
||||
continue
|
||||
|
||||
for section in sections:
|
||||
if section == "enabled":
|
||||
self.config_updater.publish_update(
|
||||
CameraConfigUpdateTopic(
|
||||
CameraConfigUpdateEnum.enabled, cam_name
|
||||
),
|
||||
cam_config.enabled,
|
||||
)
|
||||
continue
|
||||
|
||||
update_enum = PROFILE_SECTION_UPDATES.get(section)
|
||||
if update_enum is None:
|
||||
continue
|
||||
|
||||
@ -53,6 +53,23 @@ class TestCameraProfileConfig(unittest.TestCase):
|
||||
assert profile.review is not None
|
||||
assert profile.review.alerts.labels == ["person", "car"]
|
||||
|
||||
def test_enabled_field(self):
|
||||
"""Profile with enabled set to False."""
|
||||
profile = CameraProfileConfig(enabled=False)
|
||||
assert profile.enabled is False
|
||||
dumped = profile.model_dump(exclude_unset=True)
|
||||
assert dumped == {"enabled": False}
|
||||
|
||||
def test_enabled_field_true(self):
|
||||
"""Profile with enabled set to True."""
|
||||
profile = CameraProfileConfig(enabled=True)
|
||||
assert profile.enabled is True
|
||||
|
||||
def test_enabled_default_none(self):
|
||||
"""Enabled defaults to None when not set."""
|
||||
profile = CameraProfileConfig()
|
||||
assert profile.enabled is None
|
||||
|
||||
def test_none_sections_not_in_dump(self):
|
||||
"""Sections left as None should not appear in exclude_unset dump."""
|
||||
profile = CameraProfileConfig(detect={"enabled": False})
|
||||
@ -330,6 +347,66 @@ class TestProfileManager(unittest.TestCase):
|
||||
# Back camera has no "disarmed" profile, should be unchanged
|
||||
assert self.config.cameras["back"].notifications.enabled == back_base_notifications
|
||||
|
||||
@patch.object(ProfileManager, "_persist_active_profile")
|
||||
def test_activate_profile_disables_camera(self, mock_persist):
|
||||
"""Profile with enabled=false disables the camera."""
|
||||
# Add a profile that disables the front camera
|
||||
from frigate.config.camera.profile import CameraProfileConfig
|
||||
|
||||
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
||||
enabled=False
|
||||
)
|
||||
# Re-create manager to pick up new profile
|
||||
self.manager = ProfileManager(self.config, self.mock_updater)
|
||||
|
||||
assert self.config.cameras["front"].enabled is True
|
||||
err = self.manager.activate_profile("away")
|
||||
assert err is None
|
||||
assert self.config.cameras["front"].enabled is False
|
||||
|
||||
@patch.object(ProfileManager, "_persist_active_profile")
|
||||
def test_deactivate_restores_enabled(self, mock_persist):
|
||||
"""Deactivating a profile restores the camera's base enabled state."""
|
||||
from frigate.config.camera.profile import CameraProfileConfig
|
||||
|
||||
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
||||
enabled=False
|
||||
)
|
||||
self.manager = ProfileManager(self.config, self.mock_updater)
|
||||
|
||||
self.manager.activate_profile("away")
|
||||
assert self.config.cameras["front"].enabled is False
|
||||
|
||||
self.manager.activate_profile(None)
|
||||
assert self.config.cameras["front"].enabled is True
|
||||
|
||||
@patch.object(ProfileManager, "_persist_active_profile")
|
||||
def test_enabled_zmq_published(self, mock_persist):
|
||||
"""ZMQ update is published for enabled state change."""
|
||||
from frigate.config.camera.profile import CameraProfileConfig
|
||||
from frigate.config.camera.updater import (
|
||||
CameraConfigUpdateEnum,
|
||||
CameraConfigUpdateTopic,
|
||||
)
|
||||
|
||||
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
||||
enabled=False
|
||||
)
|
||||
self.manager = ProfileManager(self.config, self.mock_updater)
|
||||
self.mock_updater.reset_mock()
|
||||
|
||||
self.manager.activate_profile("away")
|
||||
|
||||
# Find the enabled update call
|
||||
enabled_calls = [
|
||||
call
|
||||
for call in self.mock_updater.publish_update.call_args_list
|
||||
if call[0][0]
|
||||
== CameraConfigUpdateTopic(CameraConfigUpdateEnum.enabled, "front")
|
||||
]
|
||||
assert len(enabled_calls) == 1
|
||||
assert enabled_calls[0][0][1] is False
|
||||
|
||||
@patch.object(ProfileManager, "_persist_active_profile")
|
||||
def test_zmq_updates_published(self, mock_persist):
|
||||
"""ZMQ updates are published when a profile is activated."""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user