Add Home Assistant discovery icons
This commit is contained in:
@@ -14,6 +14,8 @@ through Home Assistant MQTT Discovery plus an emergency HTTP fallback GUI.
|
|||||||
can update MQTT Discovery without a container restart.
|
can update MQTT Discovery without a container restart.
|
||||||
- On startup it runs a safe `all-off` by default, sets switch entities to `OFF`,
|
- On startup it runs a safe `all-off` by default, sets switch entities to `OFF`,
|
||||||
and publishes retained MQTT state.
|
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
|
- 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,
|
300s, white light 600s. The GUI timer is rendered from a backend timestamp,
|
||||||
so page refreshes do not reset it.
|
so page refreshes do not reset it.
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ VERSION = "1.0"
|
|||||||
DEFAULT_CONFIG = "/etc/frigate-camera-control-bridge.json"
|
DEFAULT_CONFIG = "/etc/frigate-camera-control-bridge.json"
|
||||||
LOG = logging.getLogger("frigate-camera-control-bridge")
|
LOG = logging.getLogger("frigate-camera-control-bridge")
|
||||||
STREAM_URL_RE = re.compile(r"(?:rtsp|rtsps|http|https)://[^\s'\"<>]+", re.IGNORECASE)
|
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 .i18n import DEFAULT_LANGUAGE, available_languages, normalize_language, text_for
|
||||||
from .ui import read_static_file, render_page
|
from .ui import read_static_file, render_page
|
||||||
@@ -1401,6 +1407,9 @@ class CameraControlBridge:
|
|||||||
},
|
},
|
||||||
"qos": 0,
|
"qos": 0,
|
||||||
}
|
}
|
||||||
|
icon = self.home_assistant_icon(action)
|
||||||
|
if icon:
|
||||||
|
payload["icon"] = icon
|
||||||
if action.platform == "switch":
|
if action.platform == "switch":
|
||||||
payload.update(
|
payload.update(
|
||||||
{
|
{
|
||||||
@@ -1424,6 +1433,20 @@ class CameraControlBridge:
|
|||||||
)
|
)
|
||||||
return payload
|
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):
|
def execute_action(self, action_id, command, publish=True):
|
||||||
action = self.action_by_id(action_id)
|
action = self.action_by_id(action_id)
|
||||||
if action is None:
|
if action is None:
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user