diff --git a/README.md b/README.md index 5f37e96..8991376 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ through Home Assistant MQTT Discovery plus an emergency HTTP fallback GUI. can update MQTT Discovery without a container restart. - On startup it runs a safe `all-off` by default, sets switch entities to `OFF`, and publishes retained MQTT state. +- Home Assistant MQTT Discovery payloads include semantic MDI icons for all-off, + siren, red-blue/strobe, and white-light entities. - Alarms and lights have default auto-off timers: siren 180s, red-blue/strobe 300s, white light 600s. The GUI timer is rendered from a backend timestamp, so page refreshes do not reset it. diff --git a/frigate_camera_control_bridge/app.py b/frigate_camera_control_bridge/app.py index 30659b0..3b2accf 100644 --- a/frigate_camera_control_bridge/app.py +++ b/frigate_camera_control_bridge/app.py @@ -19,6 +19,12 @@ VERSION = "1.0" DEFAULT_CONFIG = "/etc/frigate-camera-control-bridge.json" LOG = logging.getLogger("frigate-camera-control-bridge") STREAM_URL_RE = re.compile(r"(?:rtsp|rtsps|http|https)://[^\s'\"<>]+", re.IGNORECASE) +HOME_ASSISTANT_ACTION_ICONS = { + "action.siren": "mdi:bullhorn", + "action.red_blue": "mdi:alarm-light", + "action.manual_white": "mdi:lightbulb-on", + "action.all_off": "mdi:power", +} from .i18n import DEFAULT_LANGUAGE, available_languages, normalize_language, text_for from .ui import read_static_file, render_page @@ -1401,6 +1407,9 @@ class CameraControlBridge: }, "qos": 0, } + icon = self.home_assistant_icon(action) + if icon: + payload["icon"] = icon if action.platform == "switch": payload.update( { @@ -1424,6 +1433,20 @@ class CameraControlBridge: ) return payload + @staticmethod + def home_assistant_icon(action): + # Home Assistant MQTT discovery accepts MDI icons on switches and + # buttons. Keep icons semantic and overrideable by static action config. + configured = action.cfg.get("icon") + if configured: + return configured + name_key = action.cfg.get("name_key") + if name_key in HOME_ASSISTANT_ACTION_ICONS: + return HOME_ASSISTANT_ACTION_ICONS[name_key] + if action.kind == "all_off": + return HOME_ASSISTANT_ACTION_ICONS["action.all_off"] + return None + def execute_action(self, action_id, command, publish=True): action = self.action_by_id(action_id) if action is None: diff --git a/tests/test_home_assistant_discovery.py b/tests/test_home_assistant_discovery.py new file mode 100644 index 0000000..466f824 --- /dev/null +++ b/tests/test_home_assistant_discovery.py @@ -0,0 +1,55 @@ +import unittest + +from frigate_camera_control_bridge.app import Action, CameraControlBridge + + +class HomeAssistantDiscoveryTest(unittest.TestCase): + def setUp(self): + self.bridge = object.__new__(CameraControlBridge) + self.bridge.config = {"mqtt": {"base_topic": "test/camera_control"}} + self.bridge.default_language = "en" + + def payload_for(self, cfg): + base_cfg = { + "id": "example_action", + "name": "Example action", + "platform": "switch", + "kind": "dahua_coaxial_io", + "device": {"identifiers": ["camera_example"], "name": "Example camera"}, + } + base_cfg.update(cfg) + action = Action(base_cfg, http_client=None) + return self.bridge.discovery_payload(action, "test/camera_control/status") + + def test_switch_discovery_includes_semantic_icons(self): + cases = { + "action.siren": "mdi:bullhorn", + "action.red_blue": "mdi:alarm-light", + "action.manual_white": "mdi:lightbulb-on", + } + + for name_key, icon in cases.items(): + with self.subTest(name_key=name_key): + payload = self.payload_for({"name_key": name_key}) + + self.assertEqual(payload["icon"], icon) + + def test_all_off_button_discovery_includes_power_icon(self): + payload = self.payload_for( + { + "platform": "button", + "kind": "all_off", + "name_key": "action.all_off", + } + ) + + self.assertEqual(payload["icon"], "mdi:power") + + def test_configured_icon_overrides_default_icon(self): + payload = self.payload_for({"name_key": "action.siren", "icon": "mdi:test-tube"}) + + self.assertEqual(payload["icon"], "mdi:test-tube") + + +if __name__ == "__main__": + unittest.main()