mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-21 03:41:55 +03:00
republish MQTT switch states when a profile is activated or deactivated
This commit is contained in:
parent
47a06c8b30
commit
bc5fbe5eba
@ -5,7 +5,7 @@ import json
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from frigate.config.camera.updater import (
|
||||
CameraConfigUpdateEnum,
|
||||
@ -34,6 +34,45 @@ PROFILE_SECTION_UPDATES: dict[str, CameraConfigUpdateEnum] = {
|
||||
"zones": CameraConfigUpdateEnum.zones,
|
||||
}
|
||||
|
||||
# Retained MQTT switch topics per profile section, with a payload getter.
|
||||
# Republished on profile change so MQTT/HA don't show a stale toggle.
|
||||
SECTION_STATE_TOPICS: dict[str, list[tuple[str, Callable[[Any], Any]]]] = {
|
||||
"audio": [("audio", lambda c: "ON" if c.audio.enabled else "OFF")],
|
||||
"birdseye": [
|
||||
("birdseye", lambda c: "ON" if c.birdseye.enabled else "OFF"),
|
||||
(
|
||||
"birdseye_mode",
|
||||
lambda c: c.birdseye.mode.value.upper() if c.birdseye.enabled else "OFF",
|
||||
),
|
||||
],
|
||||
"detect": [("detect", lambda c: "ON" if c.detect.enabled else "OFF")],
|
||||
"motion": [
|
||||
("motion", lambda c: "ON" if c.motion.enabled else "OFF"),
|
||||
("improve_contrast", lambda c: "ON" if c.motion.improve_contrast else "OFF"),
|
||||
("motion_threshold", lambda c: c.motion.threshold),
|
||||
("motion_contour_area", lambda c: c.motion.contour_area),
|
||||
],
|
||||
"notifications": [
|
||||
("notifications", lambda c: "ON" if c.notifications.enabled else "OFF"),
|
||||
],
|
||||
"objects": [
|
||||
("object_descriptions", lambda c: "ON" if c.objects.genai.enabled else "OFF"),
|
||||
],
|
||||
"record": [("recordings", lambda c: "ON" if c.record.enabled else "OFF")],
|
||||
"review": [
|
||||
("review_alerts", lambda c: "ON" if c.review.alerts.enabled else "OFF"),
|
||||
(
|
||||
"review_detections",
|
||||
lambda c: "ON" if c.review.detections.enabled else "OFF",
|
||||
),
|
||||
(
|
||||
"review_descriptions",
|
||||
lambda c: "ON" if c.review.genai.enabled else "OFF",
|
||||
),
|
||||
],
|
||||
"snapshots": [("snapshots", lambda c: "ON" if c.snapshots.enabled else "OFF")],
|
||||
}
|
||||
|
||||
PERSISTENCE_FILE = Path(CONFIG_DIR) / ".profiles"
|
||||
|
||||
|
||||
@ -310,6 +349,15 @@ class ProfileManager:
|
||||
settings,
|
||||
)
|
||||
|
||||
# republish MQTT switch states
|
||||
if self.dispatcher is not None:
|
||||
for suffix, get_payload in SECTION_STATE_TOPICS.get(section, ()):
|
||||
self.dispatcher.publish(
|
||||
f"{cam_name}/{suffix}/state",
|
||||
get_payload(cam_config),
|
||||
retain=True,
|
||||
)
|
||||
|
||||
def _persist_active_profile(self, profile_name: Optional[str]) -> None:
|
||||
"""Persist the active profile state to disk as JSON."""
|
||||
try:
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"""Tests for the profiles system."""
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
@ -746,6 +747,36 @@ class TestProfileManager(unittest.TestCase):
|
||||
manager.activate_profile(None)
|
||||
dispatcher.clear_runtime_state.assert_called_once_with()
|
||||
|
||||
@patch.object(ProfileManager, "_persist_active_profile")
|
||||
def test_profile_change_republishes_switch_states(self, mock_persist):
|
||||
"""Profile changes republish MQTT switch states so HA stays in sync.
|
||||
|
||||
Regression: activating/deactivating a profile updated the in-memory
|
||||
config (and Frigate's behavior) but left the retained MQTT state
|
||||
topics stale, so external integrations like Home Assistant kept
|
||||
showing the pre-profile toggle position.
|
||||
"""
|
||||
config_data = copy.deepcopy(self.config_data)
|
||||
config_data["cameras"]["front"]["profiles"]["disarmed"]["review"] = {
|
||||
"alerts": {"enabled": False},
|
||||
}
|
||||
config = FrigateConfig(**config_data)
|
||||
dispatcher = MagicMock()
|
||||
manager = ProfileManager(config, self.mock_updater, dispatcher)
|
||||
|
||||
# Activating disarmed turns alerts off -> MQTT state must follow
|
||||
manager.activate_profile("disarmed")
|
||||
dispatcher.publish.assert_any_call(
|
||||
"front/review_alerts/state", "OFF", retain=True
|
||||
)
|
||||
|
||||
# Deactivating restores the base (alerts on) -> MQTT state must follow
|
||||
dispatcher.publish.reset_mock()
|
||||
manager.activate_profile(None)
|
||||
dispatcher.publish.assert_any_call(
|
||||
"front/review_alerts/state", "ON", retain=True
|
||||
)
|
||||
|
||||
@patch.object(ProfileManager, "_persist_active_profile")
|
||||
def test_startup_replay_does_not_clear_runtime_state(self, mock_persist):
|
||||
"""Startup callers pass clear_runtime_overrides=False to preserve state."""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user